Repository: klange/toaruos Branch: master Commit: e67515e5d59a Files: 787 Total size: 3.8 MB Directory structure: gitextract_orn7wwbb/ ├── .github/ │ ├── FUNDING.yml │ ├── SECURITY.md │ └── workflows/ │ ├── aarch64.yml │ └── x86_64.yml ├── .gitignore ├── .gitmodules ├── .mailmap ├── AUTHORS ├── LICENSE ├── Makefile ├── README.md ├── apps/ │ ├── about.c │ ├── basename.c │ ├── beep.c │ ├── bim.c │ ├── bim.h │ ├── block-dev-stats.c │ ├── cal.c │ ├── calculator.c │ ├── cat-img.c │ ├── cat.c │ ├── chmod.c │ ├── chown.c │ ├── clear.c │ ├── cmp.c │ ├── color-picker.c │ ├── compositor.c │ ├── cp.c │ ├── cpu-name.krk │ ├── cpuwidget.c │ ├── crc32.c │ ├── cursor-off.c │ ├── date.c │ ├── dbg.c │ ├── demo.c │ ├── demo.krk │ ├── dhclient.c │ ├── dirname.c │ ├── drawlines.c │ ├── du.c │ ├── echo.c │ ├── env.c │ ├── esh.c │ ├── false.c │ ├── fetch.c │ ├── file-browser.c │ ├── find-timezone.c │ ├── font-preview.c │ ├── font-tool.c │ ├── free.c │ ├── getty.c │ ├── glogin-provider.c │ ├── glogin.c │ ├── grep.c │ ├── groups.c │ ├── gsudo.c │ ├── gunzip.c │ ├── head.c │ ├── hello.c │ ├── help-browser.c │ ├── hexify.c │ ├── highlight-source.krk │ ├── hostname.c │ ├── ifconfig.c │ ├── imgviewer.c │ ├── init.c │ ├── insmod.c │ ├── irc.c │ ├── json-test.c │ ├── julia.c │ ├── kbd-test.c │ ├── kcmdline.c │ ├── kill.c │ ├── killall.c │ ├── krk_test_noise.krk │ ├── krk_yutani_test.krk │ ├── live-session.c │ ├── ln.c │ ├── login-loop.c │ ├── login.c │ ├── logname.c │ ├── ls.c │ ├── lspci.c │ ├── maybe-pdfviewer.krk │ ├── migrate.c │ ├── mines.krk │ ├── misaka-test.c │ ├── mixerctl.c │ ├── mkdir.c │ ├── mktemp.c │ ├── more.c │ ├── mount.c │ ├── msk.c │ ├── mv.c │ ├── netty.c │ ├── nproc.c │ ├── nslookup.c │ ├── nyancat/ │ │ ├── animation.h │ │ └── telnet.h │ ├── nyancat.c │ ├── package-manager.c │ ├── panel.c │ ├── path_demo.krk │ ├── piano.c │ ├── pidof.c │ ├── ping.c │ ├── plasma.c │ ├── play.c │ ├── polygons.c │ ├── pong.c │ ├── prompt_and_delete.krk │ ├── ps.c │ ├── pstree.c │ ├── pwd.c │ ├── qemu-display-hack.c │ ├── qemu-fwcfg.c │ ├── readelf.c │ ├── readlink.c │ ├── reboot.c │ ├── reload_desktop.sh │ ├── reset.c │ ├── rm.c │ ├── rmdir.c │ ├── serial-console.c │ ├── session.c │ ├── set-resolution.c │ ├── set-wallpaper.sh │ ├── show-toasts.krk │ ├── show-tutorial.sh │ ├── showdialog.c │ ├── sleep.c │ ├── snow.c │ ├── sort.c │ ├── splash-log.c │ ├── stat.c │ ├── strace.c │ ├── strings.c │ ├── stty.c │ ├── sudo.c │ ├── sync.c │ ├── sysfunc.c │ ├── sysinfo.c │ ├── t_mbstowcs.c │ ├── tar.c │ ├── tee.c │ ├── terminal-font.h │ ├── terminal-palette.h │ ├── terminal-vga.c │ ├── terminal.c │ ├── test-badwrite.c │ ├── test-conf.c │ ├── test-fpclassify.c │ ├── test-ftruncate.c │ ├── test-localtime.c │ ├── test-lock.c │ ├── test-loop.c │ ├── test-printf.c │ ├── test-ptrace-syscall.c │ ├── test-sigsegv.c │ ├── test-sigsuspend.c │ ├── test-sigwait.c │ ├── test-syscall-sysret.c │ ├── test-tls.c │ ├── test-tty-read.c │ ├── test-udp-recv.krk │ ├── toaru_logo.h │ ├── toast.krk │ ├── toastd.c │ ├── toggle-abs-mouse.c │ ├── top.c │ ├── touch.c │ ├── true.c │ ├── tty.c │ ├── ttysize.c │ ├── tutorial.c │ ├── uname.c │ ├── ununicode.h │ ├── upload.krk │ ├── uptime.c │ ├── vga-palette.h │ ├── wallpaper-picker.c │ ├── wc.c │ ├── weather-configurator.c │ ├── weather-tool.c │ ├── which.c │ ├── whoami.c │ ├── yes.c │ ├── yutani-clipboard.c │ ├── yutani-kbd.c │ ├── yutani-query.c │ ├── yutani-test.c │ ├── yutani-tty-pipe.c │ └── zcat.c ├── base/ │ ├── etc/ │ │ ├── demo.conf │ │ ├── group │ │ ├── hostname │ │ ├── master.passwd │ │ ├── motd │ │ ├── msk.conf │ │ ├── panel.menu │ │ ├── passwd │ │ ├── startup.d/ │ │ │ ├── 00_startuplog.sh │ │ │ ├── 01_migrate.sh │ │ │ ├── 02_hostname.sh │ │ │ ├── 03_tmpfs.sh │ │ │ ├── 04_modprobe.sh │ │ │ ├── 05_mountcd.sh │ │ │ ├── 40_dhcp.sh │ │ │ ├── 50_msk.sh │ │ │ └── 99_runstart.sh │ │ ├── sudoers │ │ └── weather.json │ ├── home/ │ │ ├── guest/ │ │ │ └── hello │ │ ├── local/ │ │ │ ├── .bim3rc │ │ │ ├── .eshrc │ │ │ ├── .wallpaper.conf │ │ │ ├── .yutanirc │ │ │ ├── Desktop/ │ │ │ │ ├── 0_file_browser.launcher │ │ │ │ ├── 1_terminal.launcher │ │ │ │ ├── 2_packages.launcher │ │ │ │ └── 3_read_me.launcher │ │ │ ├── README.md │ │ │ └── text_layout.krk │ │ └── root/ │ │ ├── .bimrc │ │ └── hello │ ├── lib/ │ │ └── .dummy │ └── usr/ │ ├── include/ │ │ ├── _cheader.h │ │ ├── alloca.h │ │ ├── arpa/ │ │ │ └── inet.h │ │ ├── assert.h │ │ ├── bits/ │ │ │ ├── dirent.h │ │ │ └── timespec.h │ │ ├── ctype.h │ │ ├── dirent.h │ │ ├── dlfcn.h │ │ ├── errno.h │ │ ├── fcntl.h │ │ ├── getopt.h │ │ ├── iconv.h │ │ ├── inttypes.h │ │ ├── kernel/ │ │ │ ├── arch/ │ │ │ │ ├── aarch64/ │ │ │ │ │ ├── dtb.h │ │ │ │ │ ├── gic.h │ │ │ │ │ ├── pml.h │ │ │ │ │ ├── regs.h │ │ │ │ │ └── rpi.h │ │ │ │ └── x86_64/ │ │ │ │ ├── acpi.h │ │ │ │ ├── cmos.h │ │ │ │ ├── irq.h │ │ │ │ ├── pml.h │ │ │ │ ├── ports.h │ │ │ │ └── regs.h │ │ │ ├── args.h │ │ │ ├── assert.h │ │ │ ├── elf.h │ │ │ ├── generic.h │ │ │ ├── gzip.h │ │ │ ├── hashmap.h │ │ │ ├── ksym.h │ │ │ ├── list.h │ │ │ ├── misc.h │ │ │ ├── mmu.h │ │ │ ├── mod/ │ │ │ │ ├── rtl.h │ │ │ │ ├── shell.h │ │ │ │ ├── snd.h │ │ │ │ └── sound.h │ │ │ ├── module.h │ │ │ ├── mouse.h │ │ │ ├── multiboot.h │ │ │ ├── mutex.h │ │ │ ├── net/ │ │ │ │ ├── e1000.h │ │ │ │ ├── eth.h │ │ │ │ ├── ipv4.h │ │ │ │ └── netif.h │ │ │ ├── pci.h │ │ │ ├── pipe.h │ │ │ ├── printf.h │ │ │ ├── process.h │ │ │ ├── procfs.h │ │ │ ├── ptrace.h │ │ │ ├── pty.h │ │ │ ├── ramdisk.h │ │ │ ├── ringbuffer.h │ │ │ ├── shm.h │ │ │ ├── signal.h │ │ │ ├── spinlock.h │ │ │ ├── string.h │ │ │ ├── symboltable.h │ │ │ ├── syscall.h │ │ │ ├── time.h │ │ │ ├── tmpfs.h │ │ │ ├── tokenize.h │ │ │ ├── tree.h │ │ │ ├── types.h │ │ │ ├── version.h │ │ │ ├── vfs.h │ │ │ └── video.h │ │ ├── libgen.h │ │ ├── libintl.h │ │ ├── limits.h │ │ ├── locale.h │ │ ├── math.h │ │ ├── memory.h │ │ ├── net/ │ │ │ └── if.h │ │ ├── netdb.h │ │ ├── netinet/ │ │ │ └── in.h │ │ ├── poll.h │ │ ├── pthread.h │ │ ├── pty.h │ │ ├── pwd.h │ │ ├── sched.h │ │ ├── setjmp.h │ │ ├── signal.h │ │ ├── stdint.h │ │ ├── stdio.h │ │ ├── stdlib.h │ │ ├── string.h │ │ ├── strings.h │ │ ├── sys/ │ │ │ ├── fswait.h │ │ │ ├── ioctl.h │ │ │ ├── mman.h │ │ │ ├── mount.h │ │ │ ├── param.h │ │ │ ├── ptrace.h │ │ │ ├── shm.h │ │ │ ├── signal.h │ │ │ ├── signal_defs.h │ │ │ ├── socket.h │ │ │ ├── stat.h │ │ │ ├── sysfunc.h │ │ │ ├── termios.h │ │ │ ├── time.h │ │ │ ├── times.h │ │ │ ├── types.h │ │ │ ├── uregs.h │ │ │ ├── utsname.h │ │ │ └── wait.h │ │ ├── syscall.h │ │ ├── syscall_nums.h │ │ ├── termio.h │ │ ├── termios.h │ │ ├── time.h │ │ ├── toaru/ │ │ │ ├── auth.h │ │ │ ├── button.h │ │ │ ├── confreader.h │ │ │ ├── decodeutf8.h │ │ │ ├── decorations.h │ │ │ ├── drawstring.h │ │ │ ├── graphics.h │ │ │ ├── hashmap.h │ │ │ ├── icon_cache.h │ │ │ ├── inflate.h │ │ │ ├── jpeg.h │ │ │ ├── json.h │ │ │ ├── kbd.h │ │ │ ├── list.h │ │ │ ├── markup.h │ │ │ ├── markup_text.h │ │ │ ├── menu.h │ │ │ ├── mouse.h │ │ │ ├── panel.h │ │ │ ├── pex.h │ │ │ ├── png.h │ │ │ ├── rline.h │ │ │ ├── spinlock.h │ │ │ ├── termemu.h │ │ │ ├── text.h │ │ │ ├── trace.h │ │ │ ├── tree.h │ │ │ ├── yutani-internal.h │ │ │ ├── yutani-server.h │ │ │ └── yutani.h │ │ ├── unistd.h │ │ ├── utime.h │ │ ├── va_list.h │ │ ├── wait.h │ │ └── wchar.h │ └── share/ │ ├── bim/ │ │ ├── site/ │ │ │ └── __init__.krk │ │ ├── syntax/ │ │ │ ├── __init__.krk │ │ │ ├── bash.krk │ │ │ ├── biminfo.krk │ │ │ ├── c.krk │ │ │ ├── conf.krk │ │ │ ├── css.krk │ │ │ ├── ctags.krk │ │ │ ├── diff.krk │ │ │ ├── dirent.krk │ │ │ ├── dlang.krk │ │ │ ├── docker.krk │ │ │ ├── doxygen.krk │ │ │ ├── esh.krk │ │ │ ├── gas.krk │ │ │ ├── git.krk │ │ │ ├── graphql.krk │ │ │ ├── groovy.krk │ │ │ ├── hosts.krk │ │ │ ├── issue.krk │ │ │ ├── java.krk │ │ │ ├── javascript.krk │ │ │ ├── json.krk │ │ │ ├── krk.krk │ │ │ ├── latex.krk │ │ │ ├── ld.krk │ │ │ ├── lisp.krk │ │ │ ├── lua.krk │ │ │ ├── make.krk │ │ │ ├── man.krk │ │ │ ├── markdown.krk │ │ │ ├── protobuf.krk │ │ │ ├── py.krk │ │ │ ├── rust.krk │ │ │ └── xml.krk │ │ └── themes/ │ │ ├── __init__.krk │ │ ├── ansi.krk │ │ ├── citylights.krk │ │ ├── light.krk │ │ ├── solarized.krk │ │ ├── strawberry.krk │ │ ├── sunsmoke.krk │ │ ├── tiff.krk │ │ └── wombat.krk │ └── help/ │ ├── 0_index.trt │ ├── calculator.trt │ ├── file-browser.trt │ ├── help-browser.trt │ ├── package-manager.trt │ └── terminal.trt ├── boot/ │ ├── README.md │ ├── boot.S │ ├── config.c │ ├── editor.c │ ├── editor.h │ ├── elf.h │ ├── iso9660.h │ ├── kbd.c │ ├── kbd.h │ ├── link.ld │ ├── mbr.S │ ├── menu.c │ ├── menu.h │ ├── multiboot.c │ ├── multiboot.h │ ├── options.h │ ├── platform.c │ ├── qemu.c │ ├── qemu.h │ ├── text.c │ ├── text.h │ ├── util.c │ ├── util.h │ └── video.c ├── build/ │ ├── aarch64.mk │ └── x86_64.mk ├── kernel/ │ ├── arch/ │ │ ├── aarch64/ │ │ │ ├── arch.c │ │ │ ├── bootstub/ │ │ │ │ ├── bootstrap.S │ │ │ │ ├── link.ld │ │ │ │ └── main.c │ │ │ ├── context.S │ │ │ ├── dtb.c │ │ │ ├── entry.S │ │ │ ├── fwcfg.c │ │ │ ├── gic.c │ │ │ ├── irq.S │ │ │ ├── link.ld │ │ │ ├── main.c │ │ │ ├── mmu.c │ │ │ ├── pl011.c │ │ │ ├── rpi.c │ │ │ ├── rpi400/ │ │ │ │ ├── fbterm.c │ │ │ │ ├── link.ld │ │ │ │ ├── main.c │ │ │ │ └── start.S │ │ │ ├── rpi_miniuart.c │ │ │ ├── smp.c │ │ │ ├── traceback.c │ │ │ └── virtio.c │ │ └── x86_64/ │ │ ├── bootstrap.S │ │ ├── cmos.c │ │ ├── gdt.c │ │ ├── idt.c │ │ ├── irq.S │ │ ├── link.ld │ │ ├── main.c │ │ ├── mmu.c │ │ ├── pic.c │ │ ├── pit.c │ │ ├── ports.c │ │ ├── ps2hid.c │ │ ├── serial.c │ │ ├── smp.c │ │ └── user.c │ ├── audio/ │ │ └── snd.c │ ├── binfmt.c │ ├── generic.c │ ├── misc/ │ │ ├── args.c │ │ ├── assert.c │ │ ├── elf64.c │ │ ├── fbterm.c │ │ ├── gzip.c │ │ ├── hashmap.c │ │ ├── kprintf.c │ │ ├── ksym.c │ │ ├── list.c │ │ ├── malloc.c │ │ ├── pci.c │ │ ├── ringbuffer.c │ │ ├── string.c │ │ ├── tokenize.c │ │ └── tree.c │ ├── net/ │ │ ├── arp.c │ │ ├── eth.c │ │ ├── ipv4.c │ │ ├── loop.c │ │ ├── netif.c │ │ └── socket.c │ ├── sys/ │ │ ├── mutex.c │ │ ├── process.c │ │ ├── ptrace.c │ │ ├── shm.c │ │ ├── signal.c │ │ ├── syscall.c │ │ └── version.c │ ├── vfs/ │ │ ├── console.c │ │ ├── packetfs.c │ │ ├── pipe.c │ │ ├── portio.c │ │ ├── procfs.c │ │ ├── ramdisk.c │ │ ├── random.c │ │ ├── tarfs.c │ │ ├── tmpfs.c │ │ ├── tty.c │ │ ├── unixpipe.c │ │ ├── vfs.c │ │ └── zero.c │ └── video/ │ └── lfbvideo.c ├── lib/ │ ├── README.md │ ├── auth.c │ ├── button.c │ ├── confreader.c │ ├── decor-fancy.c │ ├── decorations.c │ ├── graphics.c │ ├── hashmap.c │ ├── icon_cache.c │ ├── inflate.c │ ├── jpeg.c │ ├── json.c │ ├── kbd.c │ ├── kuroko/ │ │ ├── _waitpid.c │ │ ├── _yutani2.c │ │ └── yutani_mainloop.krk │ ├── list.c │ ├── markup.c │ ├── markup_text.c │ ├── menu.c │ ├── panel_appmenu.c │ ├── panel_clock.c │ ├── panel_date.c │ ├── panel_logout.c │ ├── panel_network.c │ ├── panel_volume.c │ ├── panel_weather.c │ ├── panel_windowlist.c │ ├── pex.c │ ├── png.c │ ├── rline.c │ ├── rline_exp.c │ ├── termemu.c │ ├── text.c │ ├── tree.c │ └── yutani.c ├── libc/ │ ├── arch/ │ │ ├── aarch64/ │ │ │ ├── bad.c │ │ │ ├── crt0.S │ │ │ ├── crti.S │ │ │ ├── crtn.S │ │ │ ├── memcpy.c │ │ │ ├── memset.c │ │ │ └── setjmp.c │ │ └── x86_64/ │ │ ├── crt0.S │ │ ├── crti.S │ │ ├── crtn.S │ │ ├── math.c │ │ ├── memcpy.c │ │ ├── memset.c │ │ └── setjmp.c │ ├── assert/ │ │ └── assert.c │ ├── ctype/ │ │ ├── _ctype.c │ │ ├── isalnum.c │ │ ├── isalpha.c │ │ ├── isascii.c │ │ ├── iscntrl.c │ │ ├── isdigit.c │ │ ├── isgraph.c │ │ ├── islower.c │ │ ├── isprint.c │ │ ├── ispunct.c │ │ ├── isspace.c │ │ ├── isupper.c │ │ ├── isxdigit.c │ │ ├── tolower.c │ │ └── toupper.c │ ├── dirent/ │ │ ├── dir.c │ │ └── mkdir.c │ ├── dlfcn/ │ │ └── dlfcn.c │ ├── errno/ │ │ └── errorno.c │ ├── iconv/ │ │ └── iconv.c │ ├── ioctl/ │ │ └── ioctl.c │ ├── libgen/ │ │ ├── basename.c │ │ └── dirname.c │ ├── libintl/ │ │ └── libintl.c │ ├── locale/ │ │ ├── localeconv.c │ │ └── setlocale.c │ ├── main.c │ ├── math/ │ │ ├── bad.c │ │ └── math.c │ ├── poll/ │ │ └── poll.c │ ├── pthread/ │ │ ├── pthread.c │ │ └── pthread_rwlock.c │ ├── pty/ │ │ └── pty.c │ ├── pwd/ │ │ └── pwd.c │ ├── sched/ │ │ └── sched_yield.c │ ├── signal/ │ │ ├── kill.c │ │ ├── raise.c │ │ ├── sigaction.c │ │ ├── signal.c │ │ ├── sigpending.c │ │ ├── sigprocmask.c │ │ ├── sigset.c │ │ └── sigsuspend.c │ ├── stdio/ │ │ ├── perror.c │ │ ├── printf.c │ │ ├── puts.c │ │ ├── remove.c │ │ ├── rename.c │ │ ├── scanf.c │ │ ├── stdio.c │ │ ├── tmpfile.c │ │ └── tmpnam.c │ ├── stdlib/ │ │ ├── abort.c │ │ ├── atexit.c │ │ ├── atof.c │ │ ├── bsearch.c │ │ ├── div.c │ │ ├── getenv.c │ │ ├── labs.c │ │ ├── malloc.c │ │ ├── mbstowcs.c │ │ ├── mktemp.c │ │ ├── putenv.c │ │ ├── qsort.c │ │ ├── rand.c │ │ ├── realpath.c │ │ ├── setenv.c │ │ ├── strtod.c │ │ ├── strtoul.c │ │ └── system.c │ ├── string/ │ │ ├── memmove.c │ │ ├── str.c │ │ ├── strerror.c │ │ ├── strncmp.c │ │ ├── strncpy.c │ │ ├── strsignal.c │ │ └── strxfrm.c │ ├── strings/ │ │ └── strcasecmp.c │ ├── sys/ │ │ ├── fswait.c │ │ ├── mount.c │ │ ├── network.c │ │ ├── ptrace.c │ │ ├── reboot.c │ │ ├── shm.c │ │ ├── sysfunc.c │ │ ├── uname.c │ │ └── wait.c │ ├── time/ │ │ ├── clock.c │ │ ├── clock_gettime.c │ │ ├── ctime.c │ │ ├── gettimeofday.c │ │ ├── localtime.c │ │ ├── settimeofday.c │ │ ├── strftime.c │ │ ├── time.c │ │ └── times.c │ ├── unistd/ │ │ ├── access.c │ │ ├── alarm.c │ │ ├── chdir.c │ │ ├── chmod.c │ │ ├── chown.c │ │ ├── close.c │ │ ├── creat.c │ │ ├── dup2.c │ │ ├── execvp.c │ │ ├── exit.c │ │ ├── fcntl.c │ │ ├── fork.c │ │ ├── fstat.c │ │ ├── getcwd.c │ │ ├── getegid.c │ │ ├── geteuid.c │ │ ├── getgid.c │ │ ├── getgroups.c │ │ ├── getlogin.c │ │ ├── getopt.c │ │ ├── getopt_long.c │ │ ├── getpgrp.c │ │ ├── getpid.c │ │ ├── getuid.c │ │ ├── getwd.c │ │ ├── hostname.c │ │ ├── isatty.c │ │ ├── link.c │ │ ├── lseek.c │ │ ├── open.c │ │ ├── pathconf.c │ │ ├── pipe.c │ │ ├── pread.c │ │ ├── pwrite.c │ │ ├── read.c │ │ ├── readlink.c │ │ ├── rmdir.c │ │ ├── sbrk.c │ │ ├── setgid.c │ │ ├── setgroups.c │ │ ├── setpgid.c │ │ ├── setsid.c │ │ ├── setuid.c │ │ ├── sleep.c │ │ ├── stat.c │ │ ├── statcompat.c │ │ ├── symlink.c │ │ ├── sync.c │ │ ├── truncate.c │ │ ├── ttyname.c │ │ ├── umask.c │ │ ├── unlink.c │ │ ├── usleep.c │ │ └── write.c │ ├── utime/ │ │ └── utime.c │ └── wchar/ │ ├── wcscat.c │ ├── wcscmp.c │ ├── wcscpy.c │ ├── wcslen.c │ ├── wcsncpy.c │ ├── wcstok.c │ ├── wcstol.c │ └── wcwidth.c ├── linker/ │ ├── README.md │ ├── link.ld │ └── linker.c ├── modules/ │ ├── ac97.c │ ├── ahci.c │ ├── ata.c │ ├── dospart.c │ ├── e1000.c │ ├── es1371.c │ ├── ext2.c │ ├── hda.c │ ├── i965.c │ ├── iso9660.c │ ├── pcspkr.c │ ├── piix4.c │ ├── test.c │ ├── vbox.c │ ├── vmware.c │ └── xhci.c └── util/ ├── __init__.krk ├── activate.sh ├── arch.sh ├── auto-dep.krk ├── bochsrc.txt ├── build-in-docker-aarch64.sh ├── build-in-docker.sh ├── build-toolchain.sh ├── createramdisk.py ├── docker/ │ ├── Dockerfile │ └── README.md ├── gen_wcwidth.krk ├── generate-etc-issue.sh ├── generate-etc-os-release.sh ├── generate-release-notes.sh ├── gensym.krk ├── init.krk ├── libm.c ├── make-version ├── make_mbr.krk ├── mkdisk.sh ├── update-extents.krk └── valid-modules.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ github: [klange] ================================================ FILE: .github/SECURITY.md ================================================ # Reporting Vulnerabilities and Exploits As 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. Reports 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. ## For Users Beyond 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. ## For CTF Operators ToaruOS 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. Additionally, 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. ================================================ FILE: .github/workflows/aarch64.yml ================================================ on: [push, workflow_dispatch] name: QEMU AArch64 virt jobs: build-image: runs-on: ubuntu-latest steps: - name: Clone Repository uses: actions/checkout@v2 with: fetch-depth: 0 - name: Clone Kuroko uses: actions/checkout@v2 with: repository: kuroko-lang/kuroko path: kuroko - name: Checkout Kuroko run: git submodule update --init kuroko - name: Pull Builder Image run: docker pull toaruos/build-tools:aarch64 - name: Run Builder 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 - name: Upload virt Artifacts uses: actions/upload-artifact@v4 with: name: aarch64-virt path: | misaka-kernel ramdisk.igz bootstub - name: Upload rpi400 Artifacts uses: actions/upload-artifact@v4 with: name: aarch64-rpi400 path: | kernel8.img ================================================ FILE: .github/workflows/x86_64.yml ================================================ on: [push, workflow_dispatch] name: x86-64 CD Image jobs: build-image: runs-on: ubuntu-latest steps: - name: Clone Repository uses: actions/checkout@v2 with: fetch-depth: 0 - name: Clone Kuroko uses: actions/checkout@v2 with: repository: kuroko-lang/kuroko path: kuroko - name: Checkout Kuroko run: git submodule update --init kuroko - name: Pull Builder Image run: docker pull toaruos/build-tools:1.99.x - name: Run Builder 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 - name: Upload Branch Image uses: actions/upload-artifact@v4 with: name: build path: | image.iso - name: Draft Release notes if: startsWith(github.ref, 'refs/tags/v') run: bash util/generate-release-notes.sh > notes.md - name: Create Release if: startsWith(github.ref, 'refs/tags/v') uses: actions/create-release@v1 id: create_release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: ToaruOS ${{ github.ref }} body_path: ./notes.md draft: true - name: Upload Release Image if: startsWith(github.ref, 'refs/tags/v') uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.create_release.outputs.upload_url }} asset_path: ./image.iso asset_name: image.iso asset_content_type: application/x-iso9660-image ================================================ FILE: .gitignore ================================================ *.aux *.idx *.ilg *.ind *.log *.o *.a *.out *.pdf *.so *.swp *.swn *.toc *.ko *.pcap .gdb_history /ramdisk.tar /ramdisk.igz /misaka-kernel.64 /misaka-kernel /kernel/symbols.S /util/build /util/local /util/cross /base/bin/* /base/etc/issue /base/etc/os-release /base/usr/bin/* /base/usr/lib/* /base/lib/kuroko/* /base/usr/share/games/doom /.make/ /cdrom /fatbase /image.iso /boot/mbr.sys /bootstub /.arch /kernel8.img /kernel8.img.elf ================================================ FILE: .gitmodules ================================================ [submodule "util/binutils-gdb"] path = util/binutils-gdb url = ../../toaruos/binutils-gdb [submodule "util/gcc"] path = util/gcc url = ../../toaruos/gcc [submodule "kuroko"] path = kuroko url = ../../kuroko-lang/kuroko ================================================ FILE: .mailmap ================================================ K. Lange K. Lange K. Lange K. Lange K. Lange K. Lange K. Lange Markus Schober Mike Gerow Tianyi Wang Jozef Nagy <38380751+jozefnagyoff@users.noreply.github.com> Mai M. Ofek Lavie ================================================ FILE: AUTHORS ================================================ Maintainer and Author: K. Lange Major Contributors: Mike Gerow Dale Weiler Matt White Markus Schober Other Contributors: Adam DiCarlo David Hayman Fabien Siron Gil Mendes Ivailo Monev Josh Kilmer Lioncash Noah Rosamilia Ofek Patrick Lucas Peter Harliman Liem Shawn Anastasio Steve Jenson Tianyi Wang Tyler Bindon ================================================ FILE: LICENSE ================================================ University of Illinois/NCSA Open Source License Copyright (c) 2011-2022 K Lange, et al. (hereafter [fullname]). All rights reserved. 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: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. * 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. ================================================ FILE: Makefile ================================================ # ToaruOS 2.0 root Makefile TOOLCHAIN=util BASE=base export PATH := $(shell $(TOOLCHAIN)/activate.sh) ARCH ?= $(shell $(TOOLCHAIN)/arch.sh) include build/${ARCH}.mk # Cross compiler binaries CC = ${TARGET}-gcc NM = ${TARGET}-nm CXX= ${TARGET}-g++ AR = ${TARGET}-ar AS = ${TARGET}-as OC = ${TARGET}-objcopy STRIP= ${TARGET}-strip # CFLAGS for kernel objects and modules KERNEL_CFLAGS = -ffreestanding -O2 -std=gnu11 -g -static KERNEL_CFLAGS += -Wall -Wextra -Wno-unused-function -Wno-unused-parameter -Wstrict-prototypes KERNEL_CFLAGS += -pedantic -Wwrite-strings ${ARCH_KERNEL_CFLAGS} # Defined constants for the kernel KERNEL_CFLAGS += -D_KERNEL_ -DKERNEL_ARCH=${ARCH} KERNEL_CFLAGS += -DKERNEL_GIT_TAG=$(shell util/make-version) # Automatically find kernel sources from relevant paths KERNEL_OBJS = $(patsubst %.c,%.o,$(wildcard kernel/*.c)) KERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/*/*.c)) KERNEL_OBJS += $(patsubst %.c,%.o,$(wildcard kernel/arch/${ARCH}/*.c)) # Assembly sources only come from the arch-dependent directory KERNEL_ASMOBJS = $(filter-out kernel/symbols.o,$(patsubst %.S,%.o,$(wildcard kernel/arch/${ARCH}/*.S))) # These sources are used to determine if we should update symbols.o KERNEL_SOURCES = $(wildcard kernel/*.c) $(wildcard kernel/*/*.c) $(wildcard kernel/${ARCH}/*/*.c) KERNEL_SOURCES += $(wildcard kernel/arch/${ARCH}/*.S) # Kernel modules are one file = one module; if you want to build more complicated # modules, you could potentially use `ld -r` to turn multiple source objects into # a single relocatable object file. ARCH_ENABLED_MODS = $(shell util/valid-modules.sh $(ARCH)) MODULES = $(patsubst modules/%.c,$(BASE)/mod/%.ko,$(foreach mod,$(ARCH_ENABLED_MODS),modules/$(mod).c)) EMU = qemu-system-${ARCH} APPS=$(patsubst apps/%.c,%,$(wildcard apps/*.c)) APPS_X=$(foreach app,$(APPS),$(BASE)/bin/$(app)) APPS_Y=$(foreach app,$(APPS),.make/$(app).mak) APPS_SH=$(patsubst apps/%.sh,%.sh,$(wildcard apps/*.sh)) APPS_SH_X=$(foreach app,$(APPS_SH),$(BASE)/bin/$(app)) APPS_KRK=$(patsubst apps/%.krk,%.krk,$(wildcard apps/*.krk)) APPS_KRK_X=$(foreach app,$(APPS_KRK),$(BASE)/bin/$(app)) 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) KRK_MODS = $(patsubst kuroko/src/modules/module_%.c,$(BASE)/lib/kuroko/%.so,$(wildcard kuroko/src/modules/module_*.c)) KRK_MODS += $(patsubst kuroko/modules/%,$(BASE)/lib/kuroko/%,$(wildcard kuroko/modules/*.krk kuroko/modules/*/*.krk kuroko/modules/*/*/.krk kuroko/modules/*/*/*.krk)) KRK_MODS += $(patsubst lib/kuroko/%,$(BASE)/lib/kuroko/%,$(wildcard lib/kuroko/*.krk)) KRK_MODS_X = $(patsubst lib/kuroko/%.c,$(BASE)/lib/kuroko/%.so,$(wildcard lib/kuroko/*.c)) KRK_MODS_Y = $(patsubst lib/kuroko/%.c,.make/%.kmak,$(wildcard lib/kuroko/*.c)) CFLAGS= -O2 -std=gnu11 -I. -Iapps -fplan9-extensions -Wall -Wextra -Wno-unused-parameter ${ARCH_USER_CFLAGS} LIBC_CFLAGS = -O2 -std=gnu11 -ffreestanding -Wall -Wextra -Wno-unused-parameter ${ARCH_USER_CFLAGS} LIBC_OBJS = $(patsubst %.c,%.o,$(wildcard libc/*.c)) LIBC_OBJS += $(patsubst %.c,%.o,$(wildcard libc/*/*.c)) LIBC_OBJS += $(patsubst %.c,%.o,$(wildcard libc/arch/${ARCH}/*.c)) GCC_SHARED = $(BASE)/usr/lib/libgcc_s.so.1 $(BASE)/usr/lib/libgcc_s.so CRTS = $(BASE)/lib/crt0.o $(BASE)/lib/crti.o $(BASE)/lib/crtn.o LC = $(BASE)/lib/libc.so $(GCC_SHARED) .PHONY: all system clean run shell $(BASE)/mod/%.ko: modules/%.c | dirs ${CC} -c ${KERNEL_CFLAGS} -fno-pie -mcmodel=large -o $@ $< ramdisk.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 python3 util/createramdisk.py $(BASE)/etc/issue: kernel/sys/version.c util/generate-etc-issue.sh sh util/generate-etc-issue.sh > $@ $(BASE)/etc/os-release: kernel/sys/version.c util/generate-etc-os-release.sh sh util/generate-etc-os-release.sh > $@ KRK_SRC = $(sort $(wildcard kuroko/src/*.c)) $(BASE)/bin/kuroko: $(KRK_SRC) $(CRTS) lib/rline.c | $(LC) $(CC) $(CFLAGS) -o $@ -Wl,--export-dynamic -Ikuroko/src $(KRK_SRC) lib/rline.c $(BASE)/lib/kuroko/%.so: kuroko/src/modules/module_%.c| dirs $(LC) $(CC) $(CFLAGS) -shared -fPIC -Ikuroko/src -o $@ $< $(BASE)/lib/kuroko/%.krk: kuroko/modules/%.krk | dirs mkdir -p $(dir $@) cp $< $@ $(BASE)/lib/kuroko/%.krk: lib/kuroko/%.krk | dirs mkdir -p $(dir $@) cp $< $@ $(BASE)/lib/libkuroko.so: $(KRK_SRC) | $(LC) $(CC) -O2 -shared -fPIC -Ikuroko/src -o $@ $(filter-out kuroko/src/kuroko.c,$(KRK_SRC)) $(BASE)/lib/ld.so: linker/linker.c $(BASE)/lib/libc.a | dirs $(LC) $(CC) -g -static -Wl,-static $(CFLAGS) -z max-page-size=0x1000 -o $@ -Os -T linker/link.ld $< kernel/sys/version.o: ${KERNEL_SOURCES} kernel/symbols.o: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} util/gensym.krk -rm -f kernel/symbols.o ${NM} -g -f p ${KERNEL_ASMOBJS} ${KERNEL_OBJS} | kuroko util/gensym.krk > kernel/symbols.S ${CC} -c kernel/symbols.S -o $@ kernel/%.o: kernel/%.S ${CC} -c $< -o $@ HEADERS = $(wildcard base/usr/include/kernel/*.h) $(wildcard base/usr/include/kernel/*/*.h) kernel/%.o: kernel/%.c ${HEADERS} ${CC} ${KERNEL_CFLAGS} -nostdlib -g -Iinclude -c -o $@ $< clean: -rm -f ${KERNEL_ASMOBJS} -rm -f ${KERNEL_OBJS} $(MODULES) -rm -f kernel/symbols.o kernel/symbols.S misaka-kernel misaka-kernel.64 -rm -f ramdisk.tar ramdisk.igz -rm -f $(APPS_Y) $(LIBS_Y) $(KRK_MODS_Y) $(KRK_MODS) -rm -f $(APPS_X) $(LIBS_X) $(KRK_MODS_X) $(APPS_KRK_X) $(APPS_SH_X) -rm -f $(BASE)/lib/crt0.o $(BASE)/lib/crti.o $(BASE)/lib/crtn.o -rm -f $(BASE)/lib/libc.so $(BASE)/lib/libc.a -rm -f $(LIBC_OBJS) $(BASE)/lib/ld.so $(BASE)/lib/libkuroko.so $(BASE)/lib/libm.so -rm -f $(BASE)/bin/kuroko -rm -f $(GCC_SHARED) -rm -f boot/efi/*.o boot/bios/*.o libc/%.o: libc/%.c base/usr/include/syscall.h $(CC) ${LIBC_CFLAGS} -fPIC -c -o $@ $< .PHONY: libc libc: $(BASE)/lib/libc.a $(BASE)/lib/libc.so $(BASE)/lib/libc.a: ${LIBC_OBJS} $(CRTS) $(AR) cr $@ $(LIBC_OBJS) $(BASE)/lib/libc.so: ${LIBC_OBJS} | $(CRTS) ${CC} -nodefaultlibs -shared -fPIC -o $@ $^ -lgcc $(BASE)/lib/crt%.o: libc/arch/${ARCH}/crt%.S ${AS} -o $@ $< $(BASE)/usr/lib/%: $(TOOLCHAIN)/local/${TARGET}/lib/% | dirs cp -a $< $@ -$(STRIP) $@ $(BASE)/lib/libm.so: util/libm.c $(CC) -shared -nostdlib -fPIC -o $@ $< $(BASE)/dev: mkdir -p $@ $(BASE)/tmp: mkdir -p $@ $(BASE)/proc: mkdir -p $@ $(BASE)/bin: mkdir -p $@ $(BASE)/lib: mkdir -p $@ $(BASE)/cdrom: mkdir -p $@ $(BASE)/var: mkdir -p $@ $(BASE)/mod: mkdir -p $@ $(BASE)/lib/kuroko: mkdir -p $@ $(BASE)/usr/lib: mkdir -p $@ $(BASE)/usr/bin: mkdir -p $@ boot/efi: mkdir -p $@ boot/bios: mkdir -p $@ fatbase/efi/boot: mkdir -p $@ cdrom: mkdir -p $@ .make: mkdir -p .make dirs: $(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 ifeq (,$(findstring clean,$(MAKECMDGOALS))$(findstring $(BUILD_KRK),$(MAKECMDGOALS))) -include ${APPS_Y} -include ${LIBS_Y} -include ${KRK_MODS_Y} endif .make/%.lmak: lib/%.c util/auto-dep.krk | dirs $(CRTS) kuroko util/auto-dep.krk --makelib $< > $@ .make/%.mak: apps/%.c util/auto-dep.krk | dirs $(CRTS) kuroko util/auto-dep.krk --make $< > $@ .make/%.kmak: lib/kuroko/%.c util/auto-dep.krk | dirs kuroko util/auto-dep.krk --makekurokomod $< > $@ $(BASE)/bin/%.sh: apps/%.sh cp $< $@ chmod +x $@ $(BASE)/bin/%.krk: apps/%.krk cp $< $@ chmod +x $@ .PHONY: libs libs: $(LIBS_X) .PHONY: apps apps: $(APPS_X) SOURCE_FILES = $(wildcard kernel/*.c kernel/*/*.c kernel/*/*/*.c kernel/*/*/*/*.c) SOURCE_FILES += $(wildcard apps/*.c linker/*.c libc/*.c libc/*/*.c lib/*.c lib/kuroko/*.c) SOURCE_FILES += $(wildcard kuroko/src/*.c kuroko/src/*.h kuroko/src/*/*.c kuroko/src/*/*.h) SOURCE_FILES += $(wildcard $(BASE)/usr/include/*.h $(BASE)/usr/include/*/*.h $(BASE)/usr/include/*/*/*.h) tags: $(SOURCE_FILES) ctags -f tags $(SOURCE_FILES) ================================================ FILE: README.md ================================================ # ToaruOS ToaruOS is a "complete" operating system for x86-64 PCs and experimental support for ARMv8. While 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. The 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. There 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. ![Screenshot](https://klange.dev/s/Screenshot%20from%202021-12-06%2011-38-12.png) *Demonstration of ToaruOS's UI and some applications.* ## History > I have been working on ToaruOS for over a decade now, and my goals have changed over the years. > > 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. > 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. > > For its original 1.0 release in 2015, ToaruOS was not the "completely from scratch" OS it has since become. > Newlib provided the libc, and the GUI was built on Cairo, libpng, and Freetype. > 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. > > Through out the project, ToaruOS has also attracted quite a few beginner OS developers who have tried to use it as a reference. > 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. > 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. ## Features - **Dynamically linked userspace** with shared libraries and `dlopen`. - **Composited graphical UI** with software acceleration and a late-2000s design inspiration. - **VM integration** for absolute mouse and automatic display sizing in VirtualBox and VMware Workstation. - **Unix-like terminal interface** including a feature-rich terminal emulator and several familiar utilities. - **Optional third-party ports** including GCC 10.3, Binutils, SDL1.2, Quake, and more. ### Notable Components - **Misaka** (kernel), [kernel/](kernel/), a hybrid modular kernel, and the core of the operating system. - **Yutani** (window compositor), [apps/compositor.c](apps/compositor.c), manages window buffers, layout, and input routing. - **Bim** (text editor), [apps/bim.c](apps/bim.c), is a Vim-inspired editor with syntax highlighting. - **Terminal**, [apps/terminal.c](apps/terminal.c), xterm-esque terminal emulator with 24-bit color support. - **ld.so** (dynamic linker/loader), [linker/linker.c](linker/linker.c), loads dynamically-linked ELF binaries. - **Esh** (shell), [apps/esh.c](apps/esh.c), supports pipes, redirections, variables, etc. - **Kuroko** (interpreter), [kuroko/](https://kuroko-lang.github.io/), a dynamic bytecode-compiled programming language. ## Current Goals The following projects are currently in progress: - **Rewrite the network stack** for greater throughput, stability, and server support. - **Improve SMP performance** with better scheduling and smarter userspace synchronization functions. - **Support more hardware** with new device drivers for AHCI, USB, virtio devices, etc. - **Bring back ports** from ToaruOS "Legacy", like muPDF and Mesa. - **Improve POSIX coverage** especially in regards to signals, synchronization primitives, as well as by providing more common utilities. - **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. - **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. - **Implement a C compiler toolchain** in [toarucc](https://github.com/klange/toarucc). ## Building / Installation ### Building With Docker General users hoping to build ToaruOS from source are recommended to fork the repository on Github and make use of the Github CI pipeline. For 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: git clone https://github.com/klange/toaruos cd toaruos git submodule update --init kuroko docker pull toaruos/build-tools:1.99.x 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 After 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. ### Build Process Internals The `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. In 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. ### Project Layout - **apps** - Userspace applications, all first-party. - **base** - Ramdisk root filesystem staging directory. Includes C headers in `base/usr/include`, as well as graphical resources for the compositor and window decorator. - **boot** - BIOS and EFI loader with interactive menus. - **build** - Auxiliary build scripts for platform ports. - **kernel** - The Misaka kernel. - **kuroko** - Submodule checkout of the Kuroko interpreter. - **lib** - Userspace libraries. - **libc** - C standard library implementation. - **linker** - Userspace dynamic linker/loader, implements shared library support. - **modules** - Loadable driver modules for the kernel. - **util** - Utility scripts, staging directory for the toolchain (binutils/gcc). - **.make** - Generated Makefiles. ### Filesystem Layout The root filesystem is set up as follows: - `bin`: First-party applications. - `cdrom`: Mount point for the CD, if available. - `dev`: Virtual device directory, generated by the kernel. - `net`: Network interface devices. - `pex`: Packet Exchange hub, lists accessible IPC services. - `pts`: PTY secondaries, endpoints for TTYs. - `etc`: Configuration files, startup scripts. - `home`: User directories. - `lib`: First-party libraries - `kuroko`: Kuroko modules. - `mod`: Loadable kernel modules. - `proc`: Virtual files that present kernel state. - `1`, etc.: Virtual files with status information for individual processes. - `src`: Source files, see "Project Layout" section above. - `tmp`: Mounted as a read/write tmpfs normally. - `usr`: Userspace resources - `bin`: Third-party applications, normally empty until packages are installed. - `include`: Header files, including potentially ones from third-party packages. - `lib`: Third-party libraries. Should have `libgcc_s.so` by default. - `share`: Various resources. - `bim`: Syntax highlighting and themes for the text editor. - `cursor`: Mouse cursor sprites. - `fonts`: TrueType font files. Live CDs ship with Deja Vu Sans. - `games`: Dumping ground for game-related resource files, like Doom wads. - `help`: Documentation files for the Help Browser application. - `icons`: PNG icons, divided into further directories by size. - `ttk`: Spritesheet resources for the window decorator and widget library. - `wallpapers`: JPEG wallpapers. - `var`: Runtime files, including package manager manifest cache, PID files, some lock files, etc. ## Running ToaruOS ### VirtualBox and VMware Workstation The 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. Set 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. ![VirtualBox screenshot](https://klange.dev/s/Screenshot%20from%202021-12-06%2011-39-27.png) *ToaruOS running in VirtualBox.* ![VMware screenshot](https://klange.dev/s/Screenshot%20from%202021-12-06%2011-41-17.png) *ToaruOS running in VMware Workstation Player.* By 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. ### QEMU Most 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: ``` qemu-system-x86_64 -enable-kvm -m 1G -device AC97 -cdrom image.iso -smp 2 ``` Replace `-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. Note that QEMU command line options are not stable and these flags may produce warnings in newer versions. The 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. ### Other ToaruOS 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: ``` multiboot2 /path/to/misaka-kernel root=/dev/ram0 migrate vid=auto start=live-session module2 /path/to/ramdisk.igz set gfxpayload=keep ``` ![Native photo](https://klange.dev/s/IMG_8387.jpg) *ToaruOS running natively from a USB stick on a ThinkPad T410.* ## License All 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. Unless 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. ToaruOS 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. You 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. While 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). Some 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. ## Community ### Mirrors ToaruOS is regularly mirrored to multiple Git hosting sites. - Gitlab: [toaruos/toaruos](https://gitlab.com/toaruos/toaruos) - GitHub: [klange/toaruos](https://github.com/klange/toaruos) - Bitbucket: [klange/toaruos](https://bitbucket.org/klange/toaruos) - ToaruOS.org: [klange/toaruos](https://git.toaruos.org/klange/toaruos) ### IRC `#toaruos` on Libera (`irc.libera.chat`) ## FAQs ### Is ToaruOS self-hosting? Individual applications and libraries can be built by installing the `build-essential` metapackage from the repository, which will pull in `gcc` and `binutils`. Sources 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. For building ramdisks, finalized kernels, or CD images, some components are currently unavailable. In particular, the [build script for ramdisks](util/createramdisk.py) is still written in Python and depends on its `tarfile` module and `zlib` support. Previously, 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. ToaruOS 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. ### Is ToaruOS a Linux distribution? No, not at all. There is no code from Linux anywhere in ToaruOS, nor were Linux sources used as a reference material. ToaruOS 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. The complete source history, going back to when ToaruOS was nothing more than a baremetal "hello world" can be tracked through this git repository. ### When you say "complete"... ToaruOS 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". ToaruOS is _not_ complete in the sense of being "done". ### Is ToaruOS POSIX-compliant? While 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. ### Are contributions accepted? ToaruOS 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. You 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. ================================================ FILE: apps/about.c ================================================ /** * @brief about - Show an "About " 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. * * @copyright * 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-2019 K. Lange */ #include #include #include #include #include #include #include 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 mode, uint32_t color) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); struct MarkupState * renderer = markup_setup_renderer(NULL, 0, 0, 0, 1); markup_set_base_font_size(renderer, 13); markup_set_base_state(renderer, mode); markup_push_string(renderer, string); int calcwidth = markup_finish_renderer(renderer); renderer = markup_setup_renderer(ctx, bounds.left_width + center_x(calcwidth), bounds.top_height + 10 + logo.height + 10 + y + 13, color, 0); markup_set_base_font_size(renderer, 13); markup_set_base_state(renderer, mode); markup_push_string(renderer, string); markup_finish_renderer(renderer); } 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, MARKUP_TEXT_STATE_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, 0, rgb(0,0,255)); offset += 20; } else { draw_string(offset, *copy_str, 0, 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"; icon_path = "/usr/share/logo_login.png"; { version_str = malloc(100); struct utsname u; uname(&u); char * tmp = strstr(u.release, "-"); if (tmp) { *tmp = '\0'; } sprintf(version_str, "ToaruOS %s", u.release); } copyright_str[0] = "© 2011-2026 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://github.com/klange/toaruos"; } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, window, w, h); reinit_graphics_yutani(ctx, window); struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); width = w - bounds.width; height = h - bounds.height; redraw(); yutani_window_resize_done(yctx, window); } 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(); markup_text_init(); struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); window = yutani_window_create_flags(yctx, width + bounds.width, height + bounds.height, YUTANI_WINDOW_FLAG_DIALOG_ANIMATION); 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); 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*)(uintptr_t)wf->wid); if (win) { win->focused = wf->focused; redraw(); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; 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; } ================================================ FILE: apps/basename.c ================================================ /** * @brief basename - print file name * * @copyright * 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 */ #include #include #include int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "%s: expected argument\n", argv[0]); return 1; } char * c = basename(argv[1]); if (argc > 2) { char * suffix = argv[2]; char * found = strstr(c + strlen(c) - strlen(suffix), suffix); if (found && (found - c == (int)(strlen(c)-strlen(suffix)))) { *found = '\0'; } } fprintf(stdout, "%s\n", c); return 0; } ================================================ FILE: apps/beep.c ================================================ /** * @brief Implementation of the 'beep' utility. * * I've tried to get the functionality here as close to the common * utility available on Linux systems so that "beep music" can be * played back. * * @copyright * 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-2018 K. Lange */ #include #include #include #include int spkr = 0; struct spkr { int length; int frequency; }; static void note(int length, int frequency) { struct spkr s = { .length = length, .frequency = frequency, }; write(spkr, &s, sizeof(s)); } /* Stolen from the Linux tool */ #define DEFAULT_FREQ 440.0 #define DEFAULT_LEN 200 #define DEFAULT_DELAY 100 static int repetitions = 1; static float frequency = DEFAULT_FREQ; static int length = DEFAULT_LEN; static int delay = DEFAULT_DELAY; static int beep_after = 0; void beep(void) { for (int i = 0; i < repetitions; ++i) { note(length, frequency * 10); if (delay && ((i != repetitions - 1) || beep_after)) { usleep(delay * 1000); } } } 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]); } int opt; while ((opt = getopt(argc, argv, "?hr:f:l:d:D:n")) != -1) { switch (opt) { case 'h': case '?': fprintf(stderr, "usage: %s BEEP...\n" "Where BEEP consists of:\n" " -r REPS \033[3mNumber of repetitions.\033[0m\n" " -f FREQ \033[3mFrequency in Hz. 440 is A4. Supports fractional values.\033[0m\n" " -l TIME \033[3mDuration in milliseconds.\033[0m\n" " -d TIME \033[3mDelay between repetitions in milliseconds.\033[0m\n" " -D TIME \033[3mDelay between, and after, repetitions.\033[0m\n" " -n \033[3mStart a new beep.\033[0m\n" "\n" "The default values are:\n" " -r 1 -l %d -f %.2f -d %d\n" "\n" "A length of -1 will start a sustained beep without blocking.\n" "A length of 0 will stop a currently playing sustained beep.\n", argv[0], DEFAULT_LEN, DEFAULT_FREQ, DEFAULT_DELAY); return 1; case 'r': repetitions = atoi(optarg); break; case 'l': length = atoi(optarg); break; case 'f': frequency = atof(optarg); break; case 'd': delay = atoi(optarg); beep_after = 0; break; case 'D': delay = atoi(optarg); beep_after = 1; break; case 'n': beep(); repetitions = 1; frequency = DEFAULT_FREQ; length = DEFAULT_LEN; delay = DEFAULT_DELAY; beep_after = 0; break; } } beep(); return 0; } ================================================ FILE: apps/bim.c ================================================ /* Bim - A Text Editor * * Copyright (C) 2012-2026 K. Lange * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "bim.h" #define BIM_VERSION "3.2.0" TAG #define BIM_COPYRIGHT "Copyright 2012-2026 K. Lange <\033[3mklange@toaruos.org\033[23m>" #include #include #include #include #include global_config_t global_config = { /* State */ .term_width = 0, .term_height = 0, .bottom_size = 2, .yanks = NULL, .yank_count = 0, .yank_is_full_lines = 0, .tty_in = STDIN_FILENO, .bimrc_path = "~/.bim3rc", .syntax_fallback = NULL, /* Syntax to fall back to if no other match applies */ .search = NULL, .overlay_mode = OVERLAY_MODE_NONE, .command_buffer = NULL, .command_offset = 0, .command_col_no = 0, .history_point = -1, .search_point = -1, /* Bitset starts here */ .highlight_on_open = 1, .initial_file_is_read_only = 0, .go_to_line = 1, .break_from_selection = 1, /* Terminal capabilities */ .can_scroll = 1, .can_hideshow = 1, .can_altscreen = 1, .can_mouse = 1, .can_unicode = 1, .can_bright = 1, .can_title = 1, .can_bce = 1, .can_24bit = 1, /* can use 24-bit color */ .can_256color = 1, /* can use 265 colors */ .can_italic = 1, /* can use italics (without inverting) */ .can_insert = 0, /* ^[[L */ .can_bracketedpaste = 0, /* puts escapes before and after pasted stuff */ .can_sgrmouse = 0, /* Whether SGR mouse mode is availabe (large coordinates) */ /* Configuration options */ .history_enabled = 1, .highlight_parens = 1, /* highlight parens/braces when cursor moves */ .smart_case = 1, /* smart case-sensitivity while searching */ .highlight_current_line = 1, .shift_scrolling = 1, /* shift rather than moving cursor*/ .check_git = 0, .color_gutter = 1, /* shows modified lines */ .relative_lines = 0, .numbers = 1, .horizontal_shift_scrolling = 0, /* Whether to shift the whole screen when scrolling horizontally */ .hide_statusbar = 0, .tabs_visible = 1, .autohide_tabs = 0, .smart_complete = 0, .has_terminal = 0, .search_wraps = 1, .had_error = 0, .use_biminfo = 1, /* Integer config values */ .cursor_padding = 4, .split_percent = 50, .scroll_amount = 5, .tab_offset = 0, .background_task = NULL, .tail_task = NULL, .paren_pairs = NULL, }; struct key_name_map KeyNames[] = { {KEY_TIMEOUT, "[timeout]"}, {KEY_BACKSPACE, ""}, {KEY_ENTER, ""}, {KEY_ESCAPE, ""}, {KEY_TAB, ""}, {' ', ""}, /* These are mostly here for markdown output. */ {'`', ""}, {'|', ""}, {KEY_DELETE, ""}, {KEY_MOUSE, ""}, {KEY_MOUSE_SGR, ""}, {KEY_F1, ""},{KEY_F2, ""},{KEY_F3, ""},{KEY_F4, ""}, {KEY_F5, ""},{KEY_F6, ""},{KEY_F7, ""},{KEY_F8, ""}, {KEY_F9, ""},{KEY_F10, ""},{KEY_F11, ""},{KEY_F12, ""}, {KEY_HOME,""},{KEY_END,""},{KEY_PAGE_UP,""},{KEY_PAGE_DOWN,""}, {KEY_UP, ""},{KEY_DOWN, ""},{KEY_RIGHT, ""},{KEY_LEFT, ""}, {KEY_SHIFT_UP, ""},{KEY_SHIFT_DOWN, ""},{KEY_SHIFT_RIGHT, ""},{KEY_SHIFT_LEFT, ""}, {KEY_CTRL_UP, ""},{KEY_CTRL_DOWN, ""},{KEY_CTRL_RIGHT, ""},{KEY_CTRL_LEFT, ""}, {KEY_ALT_UP, ""},{KEY_ALT_DOWN, ""},{KEY_ALT_RIGHT, ""},{KEY_ALT_LEFT, ""}, {KEY_ALT_SHIFT_UP, ""},{KEY_ALT_SHIFT_DOWN, ""},{KEY_ALT_SHIFT_RIGHT, ""},{KEY_ALT_SHIFT_LEFT, ""}, {KEY_SHIFT_TAB,""}, {KEY_PASTE_BEGIN,""},{KEY_PASTE_END,""}, }; char * name_from_key(enum Key keycode) { for (unsigned int i = 0; i < sizeof(KeyNames)/sizeof(KeyNames[0]); ++i) { if (KeyNames[i].keycode == keycode) return KeyNames[i].name; } static char keyNameTmp[8] = {0}; if (keycode <= KEY_CTRL_UNDERSCORE) { keyNameTmp[0] = '^'; keyNameTmp[1] = '@' + keycode; keyNameTmp[2] = 0; return keyNameTmp; } to_eight(keycode, keyNameTmp); return keyNameTmp; } #define S(c) (krk_copyString(c,sizeof(c)-1)) #define CURRENT_NAME self static KrkClass * syntaxStateClass = NULL; struct SyntaxState { KrkInstance inst; struct syntax_state state; }; static void schedule_complete_recalc(void); /** * Theming data * * This default set is pretty simple "default foreground on default background" * except for search and selections which are black-on-white specifically. * * The theme colors get set by separate configurable theme scripts. */ const char * COLOR_FG = "@9"; const char * COLOR_BG = "@9"; const char * COLOR_ALT_FG = "@9"; const char * COLOR_ALT_BG = "@9"; const char * COLOR_NUMBER_FG = "@9"; const char * COLOR_NUMBER_BG = "@9"; const char * COLOR_STATUS_FG = "@9"; const char * COLOR_STATUS_BG = "@9"; const char * COLOR_STATUS_ALT= "@9"; const char * COLOR_TABBAR_BG = "@9"; const char * COLOR_TAB_BG = "@9"; const char * COLOR_ERROR_FG = "@9"; const char * COLOR_ERROR_BG = "@9"; const char * COLOR_SEARCH_FG = "@0"; const char * COLOR_SEARCH_BG = "@17"; const char * COLOR_KEYWORD = "@9"; const char * COLOR_STRING = "@9"; const char * COLOR_COMMENT = "@9"; const char * COLOR_TYPE = "@9"; const char * COLOR_PRAGMA = "@9"; const char * COLOR_NUMERAL = "@9"; const char * COLOR_SELECTFG = "@0"; const char * COLOR_SELECTBG = "@17"; const char * COLOR_RED = "@1"; const char * COLOR_GREEN = "@2"; const char * COLOR_BOLD = "@9"; const char * COLOR_LINK = "@9"; const char * COLOR_ESCAPE = "@9"; const char * current_theme = "none"; struct ColorName color_names[] = { {"text-fg", &COLOR_FG}, {"text-bg", &COLOR_BG}, {"alternate-fg", &COLOR_ALT_FG}, {"alternate-bg", &COLOR_ALT_BG}, {"number-fg", &COLOR_NUMBER_FG}, {"number-bg", &COLOR_NUMBER_BG}, {"status-fg", &COLOR_STATUS_FG}, {"status-bg", &COLOR_STATUS_BG}, {"status-alt", &COLOR_STATUS_ALT}, {"tabbar-bg", &COLOR_TABBAR_BG}, {"tab-bg", &COLOR_TAB_BG}, {"error-fg", &COLOR_ERROR_FG}, {"error-bg", &COLOR_ERROR_BG}, {"search-fg", &COLOR_SEARCH_FG}, {"search-bg", &COLOR_SEARCH_BG}, {"keyword", &COLOR_KEYWORD}, {"string", &COLOR_STRING}, {"comment", &COLOR_COMMENT}, {"type", &COLOR_TYPE}, {"pragma", &COLOR_PRAGMA}, {"numeral", &COLOR_NUMERAL}, {"select-fg", &COLOR_SELECTFG}, {"select-bg", &COLOR_SELECTBG}, {"red", &COLOR_RED}, {"green", &COLOR_GREEN}, {"bold", &COLOR_BOLD}, {"link", &COLOR_LINK}, {"escape", &COLOR_ESCAPE}, {NULL,NULL}, }; #define FLEXIBLE_ARRAY(name, add_name, type, zero) \ int flex_ ## name ## _count = 0; \ int flex_ ## name ## _space = 0; \ type * name = NULL; \ void add_name (type input) { \ if (flex_ ## name ## _space == 0) { \ flex_ ## name ## _space = 4; \ name = calloc(sizeof(type), flex_ ## name ## _space); \ } else if (flex_ ## name ## _count + 1 == flex_ ## name ## _space) { \ flex_ ## name ## _space *= 2; \ name = realloc(name, sizeof(type) * flex_ ## name ## _space); \ for (int i = flex_ ## name ## _count; i < flex_ ## name ## _space; ++i) name[i] = zero; \ } \ name[flex_ ## name ## _count] = input; \ flex_ ## name ## _count ++; \ } FLEXIBLE_ARRAY(mappable_actions, add_action, struct action_def, ((struct action_def){NULL,0,0,NULL})) FLEXIBLE_ARRAY(regular_commands, add_command, struct command_def, ((struct command_def){NULL,NULL,NULL})) FLEXIBLE_ARRAY(prefix_commands, add_prefix_command, struct command_def, ((struct command_def){NULL,NULL,NULL})) FLEXIBLE_ARRAY(themes, add_colorscheme, struct theme_def, ((struct theme_def){NULL,NULL})) /** * Special implementation of getch with a timeout */ int _bim_unget = -1; void bim_unget(int c) { _bim_unget = c; } void redraw_statusbar(void); int bim_getch_timeout(int timeout) { fflush(stdout); if (_bim_unget != -1) { int out = _bim_unget; _bim_unget = -1; return out; } struct pollfd fds[1]; fds[0].fd = global_config.tty_in; fds[0].events = POLLIN; int ret = poll(fds,1,timeout); if (ret > 0 && fds[0].revents & POLLIN) { unsigned char buf[1]; read(global_config.tty_in, buf, 1); return buf[0]; } else { background_task_t * task = global_config.background_task; if (task) { global_config.background_task = task->next; task->func(task); free(task); if (!global_config.background_task) { global_config.tail_task = NULL; redraw_statusbar(); } } return -1; } } /** * UTF-8 parser state * * TODO Why is this state global? Should be per-buffer or otherwise managed by add_buffer, and probably * reset a lot more often than we are currently resetting it. */ static uint32_t codepoint_r; static uint32_t state = 0; #define UTF8_ACCEPT 0 #define UTF8_REJECT 1 static inline uint32_t decode(uint32_t* state, uint32_t* codep, unsigned char byte) { static int state_table[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */ 1,1,1,1,1,1,1,1, /* 10xxxxxx */ 2,2,2,2, /* 110xxxxx */ 3,3, /* 1110xxxx */ 4, /* 11110xxx */ 1 /* 11111xxx */ }; static int mask_bytes[32] = { 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x1F,0x1F,0x1F,0x1F, 0x0F,0x0F, 0x07, 0x00 }; static int next[5] = { 0, 1, 0, 2, 3 }; if (*state == UTF8_ACCEPT) { *codep = byte & mask_bytes[byte >> 3]; *state = state_table[byte >> 3]; } else if (*state > 0) { *codep = (byte & 0x3F) | (*codep << 6); *state = next[*state]; } return *state; } #define shift_key(i) _shift_key((i), this_buf, &timeout); int _shift_key(int i, int this_buf[20], int *timeout) { int thing = this_buf[*timeout-1]; (*timeout) = 0; switch (thing) { /* There are other combinations we can handle... */ case '2': return i + 4; case '5': return i + 8; case '3': return i + 12; case '4': return i + 16; default: return i; } } int bim_getkey(int read_timeout) { int timeout = 0; int this_buf[20]; int cin; uint32_t c; uint32_t istate = 0; while ((cin = bim_getch_timeout((timeout == 1) ? 50 : read_timeout))) { if (cin == -1) { if (timeout && this_buf[timeout-1] == '\033') return KEY_ESCAPE; return KEY_TIMEOUT; } if (!decode(&istate, &c, cin)) { if (timeout == 0) { switch (c) { case '\033': if (timeout == 0) { this_buf[timeout] = c; timeout++; } continue; case KEY_LINEFEED: return KEY_ENTER; case KEY_DELETE: return KEY_BACKSPACE; } return c; } else { if (timeout >= 1 && this_buf[timeout-1] == '\033' && c == '\033') { bim_unget(c); timeout = 0; return KEY_ESCAPE; } if (timeout >= 1 && this_buf[0] == '\033' && c == 'O') { this_buf[timeout] = c; timeout++; continue; } if (timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == 'O') { switch (c) { case 'P': return KEY_F1; case 'Q': return KEY_F2; case 'R': return KEY_F3; case 'S': return KEY_F4; } timeout = 0; continue; } if (timeout >= 1 && this_buf[timeout-1] == '\033' && c != '[') { timeout = 0; bim_unget(c); return KEY_ESCAPE; } if (timeout >= 1 && this_buf[timeout-1] == '\033' && c == '[') { timeout = 1; this_buf[timeout] = c; timeout++; continue; } if (timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[' && (isdigit(c) || (c == ';'))) { this_buf[timeout] = c; timeout++; continue; } if (timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[') { switch (c) { case 'M': return KEY_MOUSE; case '<': return KEY_MOUSE_SGR; case 'A': return shift_key(KEY_UP); case 'B': return shift_key(KEY_DOWN); case 'C': return shift_key(KEY_RIGHT); case 'D': return shift_key(KEY_LEFT); case 'H': return KEY_HOME; case 'F': return KEY_END; case 'I': return KEY_PAGE_UP; case 'G': return KEY_PAGE_DOWN; case 'Z': return KEY_SHIFT_TAB; case '~': if (timeout == 3) { switch (this_buf[2]) { case '1': return KEY_HOME; case '3': return KEY_DELETE; case '4': return KEY_END; case '5': return KEY_PAGE_UP; case '6': return KEY_PAGE_DOWN; } } else if (timeout == 5) { if (this_buf[2] == '2' && this_buf[3] == '0' && this_buf[4] == '0') { return KEY_PASTE_BEGIN; } else if (this_buf[2] == '2' && this_buf[3] == '0' && this_buf[4] == '1') { return KEY_PASTE_END; } } else if (this_buf[2] == '1') { switch (this_buf[3]) { case '5': return KEY_F5; case '7': return KEY_F6; case '8': return KEY_F7; case '9': return KEY_F8; } } else if (this_buf[2] == '2') { switch (this_buf[3]) { case '0': return KEY_F9; case '1': return KEY_F10; case '3': return KEY_F11; case '4': return KEY_F12; } } break; } } timeout = 0; continue; } } else if (istate == UTF8_REJECT) { istate = 0; } } return KEY_TIMEOUT; } enum Key key_from_name(const char * name) { for (unsigned int i = 0; i < sizeof(KeyNames)/sizeof(KeyNames[0]); ++i) { if (!strcmp(KeyNames[i].name, name)) return KeyNames[i].keycode; } if (name[0] == '^' && name[1] && !name[2]) { return name[1] - '@'; } if (!name[1]) return name[0]; /* Try decoding */ uint32_t c, state = 0; int candidate = -1; while (*name) { if (!decode(&state, &c, (unsigned char)*name)) { if (candidate == -1) candidate = c; else return -1; /* Reject `name` if it is multiple codepoints */ } else if (state == UTF8_REJECT) { return -1; } } return candidate; } /** * Pointer to current active buffer */ buffer_t * env = NULL; buffer_t * left_buffer = NULL; buffer_t * right_buffer = NULL; /** * A buffer for holding a number (line, repetition count) */ #define NAV_BUFFER_MAX 10 char nav_buf[NAV_BUFFER_MAX+1]; int nav_buffer = 0; /** * Available buffers */ int buffers_len = 0; int buffers_avail = 0; buffer_t ** buffers = NULL; /** * Create a new buffer */ buffer_t * buffer_new(void) { if (buffers_len == buffers_avail) { /* If we are out of buffer space, expand the buffers vector */ buffers_avail *= 2; buffers = realloc(buffers, sizeof(buffer_t *) * buffers_avail); } /* TODO: Clean up split support and support multiple splits... */ if (left_buffer) { left_buffer->left = 0; left_buffer->width = global_config.term_width; right_buffer->left = 0; right_buffer->width = global_config.term_width; left_buffer = NULL; right_buffer = NULL; } /* Allocate a new buffer */ buffers[buffers_len] = malloc(sizeof(buffer_t)); memset(buffers[buffers_len], 0x00, sizeof(buffer_t)); buffers[buffers_len]->left = 0; buffers[buffers_len]->width = global_config.term_width; buffers[buffers_len]->highlighting_paren = -1; buffers[buffers_len]->numbers = global_config.numbers; buffers[buffers_len]->gutter = 1; buffers_len++; global_config.tabs_visible = (!global_config.autohide_tabs) || (buffers_len > 1); return buffers[buffers_len-1]; } /** * Open the biminfo file. */ FILE * open_biminfo(void) { if (!global_config.use_biminfo) return NULL; char * home = getenv("HOME"); if (!home) { /* ... but since it's not, we need $HOME, so fail if it isn't set. */ return NULL; } /* biminfo lives at ~/.biminfo */ char biminfo_path[PATH_MAX+1] = {0}; sprintf(biminfo_path,"%s/.biminfo",home); /* Try to open normally first... */ FILE * biminfo = fopen(biminfo_path,"r+"); if (!biminfo) { /* Otherwise, try to create it. */ biminfo = fopen(biminfo_path,"w+"); } return biminfo; } /** * Check if a file is open by examining the biminfo file */ int file_is_open(char * file) { /* Get the absolute path of the file to normalize for lookup */ char * _file = file; if (file[0] == '~') { char * home = getenv("HOME"); if (home) { _file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */ sprintf(_file, "%s%s", home, file+1); } } char tmp_path[PATH_MAX+2]; if (!realpath(_file, tmp_path)) { if (_file != file) free(_file); return 0; /* Assume not */ } if (_file != file) free(_file); strcat(tmp_path," "); FILE * biminfo = open_biminfo(); if (!biminfo) return 0; /* Assume not */ /* Scan */ char line[PATH_MAX+64]; while (!feof(biminfo)) { fpos_t start_of_line; fgetpos(biminfo, &start_of_line); fgets(line, PATH_MAX+63, biminfo); if (line[0] != '%') { continue; } if (!strncmp(&line[1],tmp_path, strlen(tmp_path))) { /* File is currently open */ int pid = -1; sscanf(line+1+strlen(tmp_path)+1,"%d",&pid); if (pid != -1 && pid != getpid()) { if (!kill(pid, 0)) { int key = 0; render_error("biminfo indicates another instance may already be editing this file"); render_commandline_message("\n"); render_commandline_message("file path = %s\n", tmp_path); render_commandline_message("pid = %d (still running)\n", pid); render_commandline_message("Open file anyway? (y/N)"); while ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); if (key != 'y') { fclose(biminfo); return 1; } } } fclose(biminfo); return 0; } } fclose(biminfo); return 0; } /** * Fetch the cursor position from a biminfo file */ int fetch_from_biminfo(buffer_t * buf) { /* Can't fetch if we don't have a filename */ if (!buf->file_name) return 1; char * file = buf->file_name; char * _file = file; if (file[0] == '~') { char * home = getenv("HOME"); if (home) { _file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */ sprintf(_file, "%s%s", home, file+1); } } /* Get the absolute path of the file to normalize for lookup */ char tmp_path[PATH_MAX+2]; if (!realpath(_file, tmp_path)) { if (_file != file) free(_file); return 1; } if (_file != file) free(_file); strcat(tmp_path," "); FILE * biminfo = open_biminfo(); if (!biminfo) return 1; /* Scan */ char line[PATH_MAX+64]; while (!feof(biminfo)) { fpos_t start_of_line; fgetpos(biminfo, &start_of_line); fgets(line, PATH_MAX+63, biminfo); if (line[0] != '>') { continue; } if (!strncmp(&line[1],tmp_path, strlen(tmp_path))) { /* Read */ sscanf(line+1+strlen(tmp_path)+1,"%d",&buf->line_no); sscanf(line+1+strlen(tmp_path)+21,"%d",&buf->col_no); if (buf->line_no > buf->line_count) buf->line_no = buf->line_count; if (buf->col_no > buf->lines[buf->line_no-1]->actual) buf->col_no = buf->lines[buf->line_no-1]->actual; try_to_center(); fclose(biminfo); return 0; } } fclose(biminfo); return 0; } /** * Write a file containing the last cursor position of a buffer. */ int update_biminfo(buffer_t * buf, int is_open) { if (!buf->file_name) return 1; char * file = buf->file_name; char * _file = file; if (file[0] == '~') { char * home = getenv("HOME"); if (home) { _file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */ sprintf(_file, "%s%s", home, file+1); } } /* Get the absolute path of the file to normalize for lookup */ char tmp_path[PATH_MAX+1]; if (!realpath(_file, tmp_path)) { if (_file != file) free(_file); return 1; } if (_file != file) free(_file); strcat(tmp_path," "); FILE * biminfo = open_biminfo(); if (!biminfo) return 1; /* Scan */ char line[PATH_MAX+64]; while (!feof(biminfo)) { fpos_t start_of_line; fgetpos(biminfo, &start_of_line); fgets(line, PATH_MAX+63, biminfo); if (line[0] != '>' && line[0] != '%') { continue; } if (!strncmp(&line[1],tmp_path, strlen(tmp_path))) { /* Update */ fsetpos(biminfo, &start_of_line); fprintf(biminfo,"%c%s %20d %20d\n", is_open ? '%' : '>', tmp_path, is_open ? getpid() : buf->line_no, buf->col_no); goto _done; } } if (ftell(biminfo) == 0) { /* New biminfo */ fprintf(biminfo, "# This is a biminfo file.\n"); fprintf(biminfo, "# It was generated by bim. Do not edit it by hand!\n"); fprintf(biminfo, "# Cursor positions and other state are stored here.\n"); } /* If we reach this point, we didn't find a record for this file * and the write cursor should be at the end, so just add a new line */ fprintf(biminfo,"%c%s %20d %20d\n", is_open ? '%' : '>', tmp_path, is_open ? getpid() : buf->line_no, buf->col_no); _done: fclose(biminfo); return 0; } void cancel_background_tasks(buffer_t * buf) { background_task_t * t = global_config.background_task; background_task_t * last = NULL; while (t) { if (t->env == buf) { if (last) { last->next = t->next; } else { global_config.background_task = t->next; } if (!t->next) { global_config.tail_task = last; } background_task_t * tmp = t->next; free(t); t = tmp; } else { last = t; t = t->next; } } } /** * Close a buffer */ buffer_t * buffer_close(buffer_t * buf) { int i; /* Locate the buffer in the buffer list */ for (i = 0; i < buffers_len; i++) { if (buf == buffers[i]) break; } /* This buffer doesn't exist? */ if (i == buffers_len) { return NULL; } /* Cancel any background tasks for this env */ cancel_background_tasks(buf); update_biminfo(buf, 0); /* Clean up lines used by old buffer */ for (int i = 0; i < buf->line_count; ++i) { free(buf->lines[i]); } free(buf->lines); if (buf->file_name) { free(buf->file_name); } history_t * h = buf->history; while (h->next) { h = h->next; } while (h) { history_t * x = h->previous; free(h); h = x; } /* Clean up the old buffer */ free(buf); /* Remove the buffer from the vector, moving others up */ if (i != buffers_len - 1) { memmove(&buffers[i], &buffers[i+1], sizeof(*buffers) * (buffers_len - i - 1)); } /* There is one less buffer */ buffers_len--; if (buffers_len && global_config.tab_offset >= buffers_len) global_config.tab_offset--; global_config.tabs_visible = (!global_config.autohide_tabs) || (buffers_len > 1); if (!buffers_len) { /* There are no more buffers. */ return NULL; } /* If this was the last buffer in the list, return the previous last buffer */ if (i == buffers_len) { return buffers[buffers_len-1]; } /* Otherwise return the buffer in the same location */ return buffers[i]; } /** * Convert syntax highlighting flag to color code */ const char * flag_to_color(int _flag) { int flag = _flag & FLAG_MASK_COLORS; switch (flag) { case FLAG_KEYWORD: return COLOR_KEYWORD; case FLAG_STRING: return COLOR_STRING; case FLAG_COMMENT: return COLOR_COMMENT; case FLAG_TYPE: return COLOR_TYPE; case FLAG_NUMERAL: return COLOR_NUMERAL; case FLAG_PRAGMA: return COLOR_PRAGMA; case FLAG_DIFFPLUS: return COLOR_GREEN; case FLAG_DIFFMINUS: return COLOR_RED; case FLAG_SELECT: return COLOR_FG; case FLAG_BOLD: return COLOR_BOLD; case FLAG_LINK_COLOR: return COLOR_LINK; case FLAG_ESCAPE: return COLOR_ESCAPE; default: return COLOR_FG; } } /** * Match and paint a single keyword. Returns 1 if the keyword was matched and 0 otherwise, * so it can be used for prefix checking for things that need further special handling. */ static int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)) { if (keyword_qualifier(lastchar())) return 0; if (!keyword_qualifier(charat())) return 0; int i = state->i; int slen = 0; while (i < state->line->actual || *keyword == '\0') { if (*keyword == '\0' && (i >= state->line->actual || !keyword_qualifier(state->line->text[i].codepoint))) { for (int j = 0; j < slen; ++j) { paint(1, flag); } return 1; } if (*keyword != state->line->text[i].codepoint) return 0; i++; keyword++; slen++; } return 0; } /** * This is a basic character matcher for "keyword" characters. */ static int simple_keyword_qualifier(int c) { return isalnum(c) || (c == '_'); } /** * These words can appear in comments and should be highlighted. * Since there are a lot of comment highlighters, this is provided * as a common function that can be used by multiple highlighters. */ static int common_comment_buzzwords(struct syntax_state * state) { if (match_and_paint(state, "TODO", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } else if (match_and_paint(state, "XXX", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } else if (match_and_paint(state, "FIXME", FLAG_ERROR, simple_keyword_qualifier)) { return 1; } return 0; } /** * Paint a comment until end of line; assumes this comment can not continue. * (Some languages have comments that can continue with a \ - don't use this!) * Assumes you've already painted your comment start characters. */ static int paint_comment(struct syntax_state * state) { while (charat() != -1) { if (common_comment_buzzwords(state)) continue; else { paint(1, FLAG_COMMENT); } } return -1; } /** * Find and return a highlighter by name, or return NULL if none was found. */ static struct syntax_definition * find_syntax_calculator(const char * name) { for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { if (!strcmp(s->name, name)) { return s; } } return NULL; } int syntax_count = 0; int syntax_space = 0; struct syntax_definition * syntaxes = NULL; void add_syntax(struct syntax_definition def) { /* See if a name match already exists for this def. */ for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { if (!strcmp(def.name,s->name)) { *s = def; return; } } if (syntax_space == 0) { syntax_space = 4; syntaxes = calloc(sizeof(struct syntax_definition), syntax_space); } else if (syntax_count +1 == syntax_space) { syntax_space *= 2; syntaxes = realloc(syntaxes, sizeof(struct syntax_definition) * syntax_space); for (int i = syntax_count; i < syntax_space; ++i) syntaxes[i].name = NULL; } syntaxes[syntax_count] = def; syntax_count++; } void redraw_all(void); /** * Calculate syntax highlighting for the given line, and lines after * if their initial syntax state has changed by this recalculation. * * If `line_no` is -1, this line is taken to be a special line and not * part of a buffer; search highlighting will not be processed and syntax * highlighting will halt after the line is finished. * * If `env->slowop` is currently enabled, recalculation is skipped. */ void recalculate_syntax(line_t * line, int line_no) { if (env->slowop) return; /* Clear syntax for this line first */ int is_original = 1; while (1) { for (int i = 0; i < line->actual; ++i) { line->text[i].flags = line->text[i].flags & (3 << 5); } if (!env->syntax) { if (line_no != -1) rehighlight_search(line); return; } /* Start from the line's stored in initial state */ struct SyntaxState * s = (void*)krk_newInstance(env->syntax->krkClass); s->state.env = env; s->state.line = line; s->state.line_no = line_no; s->state.state = line->istate; s->state.i = 0; while (1) { struct termios old, new; tcgetattr(global_config.tty_in, &old); new = old; new.c_lflag |= ISIG; tcsetattr(global_config.tty_in, TCSANOW, &new); ptrdiff_t before = krk_currentThread.stackTop - krk_currentThread.stack; krk_push(OBJECT_VAL(env->syntax->krkFunc)); krk_push(OBJECT_VAL(s)); KrkValue result = krk_callStack(1); tcsetattr(global_config.tty_in, TCSANOW, &old); krk_currentThread.stackTop = krk_currentThread.stack + before; if (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { render_error("Exception occurred in plugin: %s", AS_INSTANCE(krk_currentThread.currentException)->_class->name->chars); render_commandline_message("\n"); krk_dumpTraceback(); goto _syntaxError; } else if (!IS_NONE(result) && !IS_INTEGER(result)) { render_error("Instead of an integer, got %s", krk_typeName(result)); render_commandline_message("\n"); goto _syntaxError; } s->state.state = IS_NONE(result) ? -1 : AS_INTEGER(result); if (s->state.state != 0) { if (line_no == -1) return; rehighlight_search(line); if (!is_original) { redraw_line(line_no); } if (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != s->state.state) { line_no++; line = env->lines[line_no]; line->istate = s->state.state; if (env->loading) return; is_original = 0; goto _next; } return; } } _next: (void)0; } _syntaxError: krk_resetStack(); fprintf(stderr,"This syntax highlighter will be disabled in this environment."); env->syntax = NULL; cancel_background_tasks(env); pause_for_key(); redraw_all(); } /** * Recalculate tab widths. */ void recalculate_tabs(line_t * line) { if (env->loading) return; int j = 0; for (int i = 0; i < line->actual; ++i) { if (line->text[i].codepoint == '\t') { line->text[i].display_width = env->tabstop - (j % env->tabstop); } j += line->text[i].display_width; } } /** * The next section contains the basic editing primitives. All other * actions are built out of these primitives, and they are the basic * instructions that get stored in history to be undone (or redone). * * Primitives may recalculate syntax or redraw lines, if needed, but only * when conditions for redrawing are met (such as not being in `slowop`, * or loading the file; also replaying history, or when loading files). * * At the moment, primitives and most other functions do not take the current * buffer (environment) as an argument and instead rely on a global variable; * this should definitely be fixed at some point... */ /** * When a new action that produces history happens and there is forward * history that can be redone, we need to erase it as our tree has branched. * If we wanted, we could actually story things in a tree structure so that * the new branch and the old branch can both stick around, and that should * probably be explored in the future... */ void history_free(history_t * root) { if (!root->next) return; /* Find the last entry so we can free backwards */ history_t * n = root->next; while (n->next) { n = n->next; } /* Free everything after the root, including stored content. */ while (n != root) { history_t * p = n->previous; switch (n->type) { case HISTORY_REPLACE_LINE: free(n->contents.remove_replace_line.contents); /* fall-through */ case HISTORY_REMOVE_LINE: free(n->contents.remove_replace_line.old_contents); break; default: /* Nothing extra to free */ break; } free(n); n = p; } root->next = NULL; } /** * This macro is called by primitives to insert history elements for each * primitive action when performing in edit modes. */ #define HIST_APPEND(e) do { \ e->col = env->col_no; \ e->line = env->line_no; \ if (env->history) { \ e->previous = env->history; \ history_free(env->history); \ env->history->next = e; \ e->next = NULL; \ } \ env->history = e; \ } while (0) /** * Mark a point where a complete set of actions has ended. * Individual history entries include things like "insert one character" * but a user action that should be undone is "insert several characters"; * breaks should be inserted after a series of primitives when there is * a clear "end", such as when switching out of insert mode after typing. */ void set_history_break(void) { if (!global_config.history_enabled) return; /* Do not produce duplicate breaks, or add breaks if we are at a sentinel. */ if (env->history->type != HISTORY_BREAK && env->history->type != HISTORY_SENTINEL) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_BREAK; HIST_APPEND(e); } } /** * (Primitive) Insert a character into an existing line. * * This is the most basic primitive action: Take a line, and insert a codepoint * into it at a given offset. If `lineno` is not -1, the line is assumed to * be part of the active buffer. If inserting a character means the line needs * to grow, then it will be reallocated, so the return value of the new line * must ALWAYS be used. This primitive will NOT automatically update the * buffer with the new pointer, so if you are calling insert on a buffer you * MUST update env->lines[lineno-1] yourself. */ __attribute__((warn_unused_result)) line_t * line_insert(line_t * line, char_t c, int offset, int lineno) { if (!env->loading && global_config.history_enabled && lineno != -1) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_INSERT; e->contents.insert_delete_replace.lineno = lineno; e->contents.insert_delete_replace.offset = offset; e->contents.insert_delete_replace.codepoint = c.codepoint; HIST_APPEND(e); } /* If there is not enough space... */ if (line->actual == line->available) { /* Expand the line buffer */ if (line->available == 0) { line->available = 8; } else { line->available *= 2; } line = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available); } /* If this was not the last character, then shift remaining characters forward. */ if (offset < line->actual) { memmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); } /* Insert the new character */ line->text[offset] = c; /* There is one new character in the line */ line->actual += 1; if (!env->loading) { line->rev_status = 2; /* Modified */ recalculate_tabs(line); recalculate_syntax(line, lineno); } return line; } /** * (Primitive) Delete a character from a line. * * Remove the character at the given offset. We never shrink lines, so this * does not have a return value, and delete should never be called during * a loading operation (though it may be called during a history replay). */ void line_delete(line_t * line, int offset, int lineno) { /* Can't delete character before start of line. */ if (offset == 0) return; /* Can't delete past end of line either */ if (offset > line->actual) return; if (!env->loading && global_config.history_enabled && lineno != -1) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_DELETE; e->contents.insert_delete_replace.lineno = lineno; e->contents.insert_delete_replace.offset = offset; e->contents.insert_delete_replace.old_codepoint = line->text[offset-1].codepoint; HIST_APPEND(e); } /* If this isn't the last character, we need to move all subsequent characters backwards */ if (offset < line->actual) { memmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); } /* The line is one character shorter */ line->actual -= 1; line->rev_status = 2; recalculate_tabs(line); recalculate_syntax(line, lineno); } /** * (Primitive) Replace a character in a line. * * Replaces the codepoint at the given offset with a new character. Since this * does not involve any size changes, it does not have a return value. * Since a replacement character may be a tab, we do still need to recalculate * character widths for tabs as they may change. */ void line_replace(line_t * line, char_t _c, int offset, int lineno) { if (!env->loading && global_config.history_enabled && lineno != -1) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_REPLACE; e->contents.insert_delete_replace.lineno = lineno; e->contents.insert_delete_replace.offset = offset; e->contents.insert_delete_replace.codepoint = _c.codepoint; e->contents.insert_delete_replace.old_codepoint = line->text[offset].codepoint; HIST_APPEND(e); } line->text[offset] = _c; if (!env->loading) { line->rev_status = 2; /* Modified */ recalculate_tabs(line); recalculate_syntax(line, lineno); } } /** * (Primitive) Remove a line from the active buffer * * This primitive is only valid for a buffer. Delete a line, or if this is the * only line in the buffer, clear it but keep the line around with no * characters. We use the `line_delete` primitive to clear that line, * otherwise we are our own primitive and produce history entries. * * While we do not shrink the `lines` array, it is returned here anyway. */ line_t ** remove_line(line_t ** lines, int offset) { /* If there is only one line, clear it instead of removing it. */ if (env->line_count == 1) { while (lines[offset]->actual > 0) { line_delete(lines[offset], lines[offset]->actual, offset); } return lines; } /* When a line is removed, we need to keep its contents so we * can un-remove it on redo... */ if (!env->loading && global_config.history_enabled) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_REMOVE_LINE; e->contents.remove_replace_line.lineno = offset; e->contents.remove_replace_line.old_contents = malloc(sizeof(line_t) + sizeof(char_t) * lines[offset]->available); memcpy(e->contents.remove_replace_line.old_contents, lines[offset], sizeof(line_t) + sizeof(char_t) * lines[offset]->available); HIST_APPEND(e); } /* Otherwise, free the data used by the line */ free(lines[offset]); /* Move other lines up */ if (offset < env->line_count-1) { memmove(&lines[offset], &lines[offset+1], sizeof(line_t *) * (env->line_count - (offset - 1))); lines[env->line_count-1] = NULL; } /* There is one less line */ env->line_count -= 1; return lines; } /** * (Primitive) Add a new line to the active buffer. * * Inserts a new line into a buffer at the given line offset. * Since this grows the buffer, it will return the new line array * after reallocation if needed. */ line_t ** add_line(line_t ** lines, int offset) { /* Invalid offset? */ if (offset > env->line_count) return lines; if (!env->loading && global_config.history_enabled) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_ADD_LINE; e->contents.add_merge_split_lines.lineno = offset; HIST_APPEND(e); } /* Not enough space */ if (env->line_count == env->line_avail) { /* Allocate more space */ env->line_avail *= 2; lines = realloc(lines, sizeof(line_t *) * env->line_avail); } /* If this isn't the last line, move other lines down */ if (offset < env->line_count) { memmove(&lines[offset+1], &lines[offset], sizeof(line_t *) * (env->line_count - offset)); } /* Allocate the new line */ lines[offset] = calloc(sizeof(line_t) + sizeof(char_t) * 32, 1); lines[offset]->available = 32; /* There is one new line */ env->line_count += 1; env->lines = lines; if (!env->loading) { lines[offset]->rev_status = 2; /* Modified */ } if (offset > 0 && !env->loading) { recalculate_syntax(lines[offset-1],offset-1); } return lines; } /** * (Primitive) Replace a line with data from another line. * * This is only called when pasting yanks after calling `add_line`, * but it allows us to have simpler history for that action. */ void replace_line(line_t ** lines, int offset, line_t * replacement) { if (!env->loading && global_config.history_enabled) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_REPLACE_LINE; e->contents.remove_replace_line.lineno = offset; e->contents.remove_replace_line.old_contents = malloc(sizeof(line_t) + sizeof(char_t) * lines[offset]->available); memcpy(e->contents.remove_replace_line.old_contents, lines[offset], sizeof(line_t) + sizeof(char_t) * lines[offset]->available); e->contents.remove_replace_line.contents = malloc(sizeof(line_t) + sizeof(char_t) * replacement->available); memcpy(e->contents.remove_replace_line.contents, replacement, sizeof(line_t) + sizeof(char_t) * replacement->available); HIST_APPEND(e); } if (lines[offset]->available < replacement->actual) { lines[offset] = realloc(lines[offset], sizeof(line_t) + sizeof(char_t) * replacement->available); lines[offset]->available = replacement->available; } lines[offset]->actual = replacement->actual; memcpy(&lines[offset]->text, &replacement->text, sizeof(char_t) * replacement->actual); if (!env->loading) { lines[offset]->rev_status = 2; recalculate_syntax(lines[offset],offset); } } /** * (Primitive) Merge two consecutive lines. * * Take two lines in a buffer and turn them into one line. * `lineb` is the offset of the second line... or the * line number of the first line, depending on which indexing * system you prefer to think about. This won't grow `lines`, * but it will likely modify it and can reallocate individual * lines as well. */ line_t ** merge_lines(line_t ** lines, int lineb) { /* linea is the line immediately before lineb */ int linea = lineb - 1; if (!env->loading && global_config.history_enabled) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_MERGE_LINES; e->contents.add_merge_split_lines.lineno = lineb; e->contents.add_merge_split_lines.split = env->lines[linea]->actual; HIST_APPEND(e); } /* If there isn't enough space in linea hold both... */ if (lines[linea]->available < lines[linea]->actual + lines[lineb]->actual) { while (lines[linea]->available < lines[linea]->actual + lines[lineb]->actual) { /* ... allocate more space until it fits */ if (lines[linea]->available == 0) { lines[linea]->available = 8; } else { lines[linea]->available *= 2; } } lines[linea] = realloc(lines[linea], sizeof(line_t) + sizeof(char_t) * lines[linea]->available); } /* Copy the second line into the first line */ memcpy(&lines[linea]->text[lines[linea]->actual], &lines[lineb]->text, sizeof(char_t) * lines[lineb]->actual); /* The first line is now longer */ lines[linea]->actual = lines[linea]->actual + lines[lineb]->actual; if (!env->loading) { lines[linea]->rev_status = 2; recalculate_tabs(lines[linea]); recalculate_syntax(lines[linea], linea); } /* Remove the second line */ free(lines[lineb]); /* Move other lines up */ if (lineb < env->line_count) { memmove(&lines[lineb], &lines[lineb+1], sizeof(line_t *) * (env->line_count - (lineb - 1))); lines[env->line_count-1] = NULL; } /* There is one less line */ env->line_count -= 1; return lines; } /** * (Primitive) Split a line into two lines at the given column. * * Takes one line and makes it two lines. There are some optimizations * if you are trying to "split" at the first or last column, which * are both just treated as add_line. */ line_t ** split_line(line_t ** lines, int line, int split) { /* If we're trying to split from the start, just add a new blank line before */ if (split == 0) { return add_line(lines, line); } if (!env->loading && global_config.history_enabled) { history_t * e = malloc(sizeof(history_t)); e->type = HISTORY_SPLIT_LINE; e->contents.add_merge_split_lines.lineno = line; e->contents.add_merge_split_lines.split = split; HIST_APPEND(e); } if (!env->loading) { unhighlight_matching_paren(); } /* Allocate more space as needed */ if (env->line_count == env->line_avail) { env->line_avail *= 2; lines = realloc(lines, sizeof(line_t *) * env->line_avail); } /* Shift later lines down */ if (line < env->line_count) { memmove(&lines[line+2], &lines[line+1], sizeof(line_t *) * (env->line_count - line)); } int remaining = lines[line]->actual - split; /* This is some wacky math to get a good power-of-two */ int v = remaining; v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; /* Allocate space for the new line */ lines[line+1] = calloc(sizeof(line_t) + sizeof(char_t) * v, 1); lines[line+1]->available = v; lines[line+1]->actual = remaining; /* Move the data from the old line into the new line */ memmove(lines[line+1]->text, &lines[line]->text[split], sizeof(char_t) * remaining); lines[line]->actual = split; if (!env->loading) { lines[line]->rev_status = 2; lines[line+1]->rev_status = 2; recalculate_tabs(lines[line]); recalculate_tabs(lines[line+1]); recalculate_syntax(lines[line], line); recalculate_syntax(lines[line+1], line+1); } /* There is one new line */ env->line_count += 1; /* We may have reallocated lines */ return lines; } /** * Primitives end here. Everything after this point that wants to * perform modifications must be built on these primitives and * should never directly modify env->lines or the contents thereof, * outside of syntax highlighting flag changes. */ /** * The following section is where we implement "smart" automatic * indentation. A lot of this is hacked-together nonsense and "smart" * is a bit of an overstatement. */ /** * Understand spaces and comments and check if the previous line * ended with a brace or a colon. */ int line_ends_with_brace(line_t * line) { int i = line->actual-1; while (i >= 0) { if ((line->text[i].flags & 0x1F) == FLAG_COMMENT || line->text[i].codepoint == ' ') { i--; } else { break; } } if (i < 0) return 0; return (line->text[i].codepoint == '{' || line->text[i].codepoint == ':') ? i+1 : 0; } /** * Determine if a given line is a comment by checking its initial * syntax highlighting state value. This is used by automatic indentation * to continue block comments in languages like C. * * TODO: Why isn't this a syntax-> method? */ int line_is_comment(line_t * line) { if (!env->syntax) return 0; if (!strcmp(env->syntax->name,"c")) { if (line->istate == 1) return 1; } else if (!strcmp(env->syntax->name,"java")) { if (line->istate == 1) return 1; } else if (!strcmp(env->syntax->name,"kotlin")) { if (line->istate == 1) return 1; } else if (!strcmp(env->syntax->name,"rust")) { if (line->istate > 0) return 1; } return 0; } /** * Figure out where the indentation for a brace belongs by finding * where the start of the line is after whitespace. This is called * by default when we insert } and tries to align it with the indentation * of the matching opening {. */ int find_brace_line_start(int line, int col) { int ncol = col - 1; while (ncol > 0) { if (env->lines[line-1]->text[ncol-1].codepoint == ')') { int t_line_no = env->line_no; int t_col_no = env->col_no; env->line_no = line; env->col_no = ncol; int paren_match_line = -1, paren_match_col = -1; find_matching_paren(&paren_match_line, &paren_match_col, 1); if (paren_match_line != -1) { line = paren_match_line; } env->line_no = t_line_no; env->col_no = t_col_no; break; } else if (env->lines[line-1]->text[ncol-1].codepoint == ' ') { ncol--; } else { break; } } return line; } /** * Add indentation from the previous (temporally) line. * * By "temporally", we mean not necessarily the line above, but * potentially the line below if we are inserting a line above * the cursor. */ void add_indent(int new_line, int old_line, int ignore_brace) { if (env->indent) { int changed = 0; if (old_line < new_line && line_is_comment(env->lines[new_line])) { for (int i = 0; i < env->lines[old_line]->actual; ++i) { if (env->lines[old_line]->text[i].codepoint == '/') { if (env->lines[old_line]->text[i+1].codepoint == '*') { /* Insert ' * ' */ char_t space = {1,FLAG_COMMENT,' '}; char_t asterisk = {1,FLAG_COMMENT,'*'}; env->lines[new_line] = line_insert(env->lines[new_line],space,i,new_line); env->lines[new_line] = line_insert(env->lines[new_line],asterisk,i+1,new_line); env->lines[new_line] = line_insert(env->lines[new_line],space,i+2,new_line); env->col_no += 3; } break; } else if (env->lines[old_line]->text[i].codepoint == ' ' && env->lines[old_line]->text[i+1].codepoint == '*') { /* Insert ' * ' */ char_t space = {1,FLAG_COMMENT,' '}; char_t asterisk = {1,FLAG_COMMENT,'*'}; env->lines[new_line] = line_insert(env->lines[new_line],space,i,new_line); env->lines[new_line] = line_insert(env->lines[new_line],asterisk,i+1,new_line); env->lines[new_line] = line_insert(env->lines[new_line],space,i+2,new_line); env->col_no += 3; break; } else if (env->lines[old_line]->text[i].codepoint == ' ' || env->lines[old_line]->text[i].codepoint == '\t' || env->lines[old_line]->text[i].codepoint == '*') { env->lines[new_line] = line_insert(env->lines[new_line],env->lines[old_line]->text[i],i,new_line); env->col_no++; changed = 1; } else { break; } } } else { int line_to_copy_from = old_line; int col; if (old_line < new_line && !ignore_brace && (col = line_ends_with_brace(env->lines[old_line])) && env->lines[old_line]->text[col-1].codepoint == '{') { line_to_copy_from = find_brace_line_start(old_line+1, col)-1; } for (int i = 0; i < env->lines[line_to_copy_from]->actual; ++i) { if (line_to_copy_from < new_line && i == env->lines[line_to_copy_from]->actual - 3 && env->lines[line_to_copy_from]->text[i].codepoint == ' ' && env->lines[line_to_copy_from]->text[i+1].codepoint == '*' && env->lines[line_to_copy_from]->text[i+2].codepoint == '/') { break; } else if (env->lines[line_to_copy_from]->text[i].codepoint == ' ' || env->lines[line_to_copy_from]->text[i].codepoint == '\t') { env->lines[new_line] = line_insert(env->lines[new_line],env->lines[line_to_copy_from]->text[i],i,new_line); env->col_no++; changed = 1; } else { break; } } } if (old_line < new_line && !ignore_brace && line_ends_with_brace(env->lines[old_line])) { if (env->tabs) { char_t c = {0}; c.codepoint = '\t'; c.display_width = env->tabstop; env->lines[new_line] = line_insert(env->lines[new_line], c, env->col_no-1, new_line); env->col_no++; changed = 1; } else { for (int j = 0; j < env->tabstop; ++j) { char_t c = {0}; c.codepoint = ' '; c.display_width = 1; env->lines[new_line] = line_insert(env->lines[new_line], c, env->col_no-1, new_line); env->col_no++; } changed = 1; } } int was_whitespace = 1; for (int i = 0; i < env->lines[old_line]->actual; ++i) { if (env->lines[old_line]->text[i].codepoint != ' ' && env->lines[old_line]->text[i].codepoint != '\t') { was_whitespace = 0; break; } } if (was_whitespace) { while (env->lines[old_line]->actual) { line_delete(env->lines[old_line], env->lines[old_line]->actual, old_line); } } if (changed) { recalculate_syntax(env->lines[new_line],new_line); } } } /** * Initialize a buffer with default values. * * Should be called after creating a buffer. */ void setup_buffer(buffer_t * env) { /* If this buffer was already initialized, clear out its line data */ if (env->lines) { for (int i = 0; i < env->line_count; ++i) { free(env->lines[i]); } free(env->lines); } /* Default state parameters */ env->line_no = 1; /* Default cursor position */ env->col_no = 1; env->line_count = 1; /* Buffers always have at least one line */ env->modified = 0; env->readonly = 0; env->offset = 0; env->line_avail = 8; /* Default line buffer capacity */ env->tabs = 1; /* Tabs by default */ env->tabstop = 4; /* Tab stop width */ env->indent = 1; /* Auto-indent by default */ env->history = malloc(sizeof(struct history)); memset(env->history, 0, sizeof(struct history)); env->last_save_history = env->history; /* Allocate line buffer */ env->lines = malloc(sizeof(line_t *) * env->line_avail); /* Initialize the first line */ env->lines[0] = calloc(sizeof(line_t) + sizeof(char_t) * 32, 1); env->lines[0]->available = 32; } /** * Toggle buffered / unbuffered modes */ struct termios old; void get_initial_termios(void) { tcgetattr(STDOUT_FILENO, &old); } void set_unbuffered(void) { struct termios new = old; new.c_iflag &= (~ICRNL) & (~IXON); new.c_lflag &= (~ICANON) & (~ECHO) & (~ISIG); #ifdef VLNEXT new.c_cc[VLNEXT] = 0; #endif #ifdef VDISCARD new.c_cc[VDISCARD] = 0; #endif tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new); } void set_buffered(void) { tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old); } /** * Convert codepoint to utf-8 string */ int to_eight(uint32_t codepoint, char * out) { memset(out, 0x00, 7); if (codepoint < 0x0080) { out[0] = (char)codepoint; } else if (codepoint < 0x0800) { out[0] = 0xC0 | (codepoint >> 6); out[1] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x10000) { out[0] = 0xE0 | (codepoint >> 12); out[1] = 0x80 | ((codepoint >> 6) & 0x3F); out[2] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x200000) { out[0] = 0xF0 | (codepoint >> 18); out[1] = 0x80 | ((codepoint >> 12) & 0x3F); out[2] = 0x80 | ((codepoint >> 6) & 0x3F); out[3] = 0x80 | ((codepoint) & 0x3F); } else if (codepoint < 0x4000000) { out[0] = 0xF8 | (codepoint >> 24); out[1] = 0x80 | (codepoint >> 18); out[2] = 0x80 | ((codepoint >> 12) & 0x3F); out[3] = 0x80 | ((codepoint >> 6) & 0x3F); out[4] = 0x80 | ((codepoint) & 0x3F); } else { out[0] = 0xF8 | (codepoint >> 30); out[1] = 0x80 | ((codepoint >> 24) & 0x3F); out[2] = 0x80 | ((codepoint >> 18) & 0x3F); out[3] = 0x80 | ((codepoint >> 12) & 0x3F); out[4] = 0x80 | ((codepoint >> 6) & 0x3F); out[5] = 0x80 | ((codepoint) & 0x3F); } return strlen(out); } /** * Get the presentation width of a codepoint */ int codepoint_width(wchar_t codepoint) { if (codepoint == '\t') { return 1; /* Recalculate later */ } if (codepoint < 32) { /* We render these as ^@ */ return 2; } if (codepoint == 0x7F) { /* Renders as ^? */ return 2; } if (codepoint > 0x7f && codepoint < 0xa0) { /* Upper control bytes */ return 4; } if (codepoint == 0xa0) { /* Non-breaking space _ */ return 1; } /* Skip wcwidth for anything under 256 */ if (codepoint > 127) { if (global_config.can_unicode) { /* Higher codepoints may be wider (eg. Japanese) */ int out = wcwidth(codepoint); if (out >= 1) return out; } /* Invalid character, render as [U+ABCD] or [U+ABCDEF] */ return (codepoint < 0x10000) ? 8 : 10; } return 1; } /** * The following section contains methods for crafting terminal escapes * for rendering the display. We do not use curses or any similar * library, so we have to generate these sequences ourself based on * some assumptions about the target terminal. */ /** * Move the terminal cursor */ void place_cursor(int x, int y) { printf("\033[%d;%dH", y, x); } /** * Given two color codes from a theme, convert them to an escape * sequence to set the foreground and background color. This allows * us to support basic 16, xterm 256, and true color in themes. */ char * color_string(const char * fg, const char * bg) { static char output[100]; char * t = output; t += sprintf(t,"\033[22;23;"); if (*bg == '@') { int _bg = atoi(bg+1); if (_bg < 10) { t += sprintf(t, "4%d;", _bg); } else { t += sprintf(t, "10%d;", _bg-10); } } else { t += sprintf(t, "48;%s;", bg); } if (*fg == '@') { int _fg = atoi(fg+1); if (_fg < 10) { t += sprintf(t, "3%dm", _fg); } else { t += sprintf(t, "9%dm", _fg-10); } } else { t += sprintf(t, "38;%sm", fg); } return output; } /** * Set text colors * * Normally, text colors are just strings, but if they * start with @ they will be parsed as integers * representing one of the 16 standard colors, suitable * for terminals without support for the 256- or 24-bit * color modes. */ void set_colors(const char * fg, const char * bg) { printf("%s", color_string(fg, bg)); } /** * Set just the foreground color * * (See set_colors above) */ void set_fg_color(const char * fg) { printf("\033[22;23;"); if (*fg == '@') { int _fg = atoi(fg+1); if (_fg < 10) { printf("3%dm", _fg); } else { printf("9%dm", _fg-10); } } else { printf("38;%sm", fg); } } /** * Clear the rest of this line */ void clear_to_end(void) { if (global_config.can_bce) { printf("\033[K"); } } /** * For terminals without bce, prepaint the whole line, so we don't have to track * where the cursor is for everything. Inefficient, but effective. */ void paint_line(const char * bg) { if (!global_config.can_bce) { set_colors(COLOR_FG, bg); for (int i = 0; i < global_config.term_width; ++i) { printf(" "); } printf("\r"); } } /** * Enable bold text display */ void set_bold(void) { printf("\033[1m"); } /** * Disable bold */ void unset_bold(void) { printf("\033[22m"); } /** * Enable underlined text display */ void set_underline(void) { printf("\033[4m"); } /** * Disable underlined text display */ void unset_underline(void) { printf("\033[24m"); } /** * Reset text display attributes */ void reset(void) { printf("\033[0m"); } /** * Clear the entire screen */ void clear_screen(void) { printf("\033[H\033[2J"); } /** * Hide the cursor */ void hide_cursor(void) { if (global_config.can_hideshow) { printf("\033[?25l"); } } /** * Show the cursor */ void show_cursor(void) { if (global_config.can_hideshow) { printf("\033[?25h"); } } /** * Store the cursor position */ void store_cursor(void) { printf("\0337"); } /** * Restore the cursor position. */ void restore_cursor(void) { printf("\0338"); } /** * Request mouse events */ void mouse_enable(void) { if (global_config.can_mouse) { printf("\033[?1000h"); if (global_config.can_sgrmouse) { printf("\033[?1006h"); } } } /** * Stop mouse events */ void mouse_disable(void) { if (global_config.can_mouse) { if (global_config.can_sgrmouse) { printf("\033[?1006l"); } printf("\033[?1000l"); } } /** * Shift the screen up one line */ void shift_up(int amount) { printf("\033[%dS", amount); } /** * Shift the screen down one line. */ void shift_down(int amount) { printf("\033[%dT", amount); } void insert_lines_at(int line, int count) { place_cursor(1, line); printf("\033[%dL", count); } void delete_lines_at(int line, int count) { place_cursor(1, line); printf("\033[%dM", count); } /** * Switch to the alternate terminal screen. */ void set_alternate_screen(void) { if (global_config.can_altscreen) { printf("\033[?1049h"); } } /** * Restore the standard terminal screen. */ void unset_alternate_screen(void) { if (global_config.can_altscreen) { printf("\033[?1049l"); } } /** * Enable bracketed paste mode. */ void set_bracketed_paste(void) { if (global_config.can_bracketedpaste) { printf("\033[?2004h"); } } /** * Disable bracketed paste mode. */ void unset_bracketed_paste(void) { if (global_config.can_bracketedpaste) { printf("\033[?2004l"); } } /** * Get the name of just a file from a full path. * Returns a pointer within the original string. * * Called in a few different places where the name of a file * is needed from its full path, such as drawing tab names or * building HTML files. */ char * file_basename(char * file) { char * c = strrchr(file, '/'); if (!c) return file; return (c+1); } /** * Print a tab name with fixed width and modifiers * into an output buffer and return the written width. */ int draw_tab_name(buffer_t * _env, char * out, int max_width, int * width) { uint32_t c, state = 0; char * t = _env->file_name ? file_basename(_env->file_name) : "[No Name]"; #define ADD(c) do { \ *o = c; \ o++; \ *o = '\0'; \ bytes++; \ } while (0) char * o = out; *o = '\0'; int bytes = 0; if (max_width < 2) return 1; ADD(' '); (*width)++; if (_env->modified) { if (max_width < 4) return 1; ADD('+'); (*width)++; ADD(' '); (*width)++; } while (*t) { /* File names can definitely by UTF-8, and we need to * understand their display width... */ if (!decode(&state, &c, (unsigned char)*t)) { /* But our displayed tab name is also just stored * as UTF-8 again, so we essentially rebuild it... */ char tmp[7]; int size = to_eight(c, tmp); if (bytes + size > 62) break; if (*width + size >= max_width) return 1; for (int i = 0; i < size; ++i) { ADD(tmp[i]); } (*width) += codepoint_width(c); } else if (state == UTF8_REJECT) { state = 0; } t++; } if (max_width == *width + 1) return 1; ADD(' '); (*width)++; #undef ADD return 0; } /** * Redraw the tabbar, with a tab for each buffer. * * The active buffer is highlighted. */ void redraw_tabbar(void) { if (!global_config.tabs_visible) return; /* Hide cursor while rendering UI */ hide_cursor(); /* Move to upper left */ place_cursor(1,1); paint_line(COLOR_TABBAR_BG); /* For each buffer... */ int offset = 0; if (global_config.tab_offset) { set_colors(COLOR_NUMBER_FG, COLOR_NUMBER_BG); printf("<"); offset++; } for (int i = global_config.tab_offset; i < buffers_len; i++) { buffer_t * _env = buffers[i]; if (_env == env) { /* If this is the active buffer, highlight it */ reset(); set_colors(COLOR_FG, COLOR_BG); set_bold(); } else { /* Otherwise use default tab color */ reset(); set_colors(COLOR_FG, COLOR_TAB_BG); set_underline(); } if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { if (global_config.command_buffer->actual) { char * f = _env->file_name ? file_basename(_env->file_name) : ""; /* TODO: Support unicode input here; needs conversion */ int i = 0; for (; i < global_config.command_buffer->actual && f[i] == global_config.command_buffer->text[i].codepoint; ++i); if (global_config.command_buffer->actual == i) { set_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG); } } } char title[64]; int size = 0; int filled = draw_tab_name(_env, title, global_config.term_width - offset, &size); if (filled) { offset += size; printf("%s", title); set_colors(COLOR_NUMBER_FG, COLOR_NUMBER_BG); while (offset != global_config.term_width - 1) { printf(" "); offset++; } printf(">"); break; } printf("%s", title); offset += size; } /* Reset bold/underline */ reset(); /* Fill the rest of the tab bar */ set_colors(COLOR_FG, COLOR_TABBAR_BG); clear_to_end(); } /** * Braindead log10 implementation for figuring out the width of the * line number column. */ int log_base_10(unsigned int v) { int r = (v >= 1000000000) ? 9 : (v >= 100000000) ? 8 : (v >= 10000000) ? 7 : (v >= 1000000) ? 6 : (v >= 100000) ? 5 : (v >= 10000) ? 4 : (v >= 1000) ? 3 : (v >= 100) ? 2 : (v >= 10) ? 1 : 0; return r; } /** * Render a line of text. * * This handles rendering the actual text content. A full line of text * also includes a line number and some padding, but those elements * are rendered elsewhere; this method can be used for lines that are * not attached to a buffer such as command input lines. * * width: width of the text display region (term width - line number width) * offset: how many cells into the line to start rendering at */ void render_line(line_t * line, int width, int offset, int line_no) { int i = 0; /* Offset in char_t line data entries */ int j = 0; /* Offset in terminal cells */ const char * last_color = NULL; int was_selecting = 0, was_searching = 0, was_underlining = 0; /* Set default text colors */ set_colors(COLOR_FG, line->is_current ? COLOR_ALT_BG : COLOR_BG); /* * When we are rendering in the middle of a wide character, * we render -'s to fill the remaining amount of the character's width. */ int remainder = 0; int is_spaces = 1; /* For each character in the line ... */ while (i < line->actual) { /* If there is remaining text... */ if (remainder) { /* If we should be drawing by now... */ if (j >= offset) { if (was_underlining) printf("\033[24m"); /* Fill remainder with -'s */ set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("-"); set_colors(COLOR_FG, line->is_current ? COLOR_ALT_BG : COLOR_BG); } /* One less remaining width cell to fill */ remainder--; /* Terminal offset moves forward */ j++; /* * If this was the last remaining character, move to * the next codepoint in the line */ if (remainder == 0) { i++; } continue; } /* Get the next character to draw */ char_t c = line->text[i]; if (c.codepoint != ' ') is_spaces = 0; /* If we should be drawing by now... */ if (j >= offset) { /* If this character is going to fall off the edge of the screen... */ if (j - offset + c.display_width >= width) { /* We draw this with special colors so it isn't ambiguous */ set_colors(COLOR_ALT_FG, COLOR_ALT_BG); if (was_underlining) printf("\033[24m"); /* If it's wide, draw ---> as needed */ while (j - offset < width - 1) { printf("-"); j++; } /* End the line with a > to show it overflows */ printf(">"); set_colors(COLOR_FG, COLOR_BG); return; } /* Syntax highlighting */ const char * color = flag_to_color(c.flags); if (c.flags & FLAG_SELECT) { if ((c.flags & FLAG_MASK_COLORS) == FLAG_NONE) color = COLOR_SELECTFG; set_colors(color, COLOR_SELECTBG); was_selecting = 1; } else if ((c.flags & FLAG_SEARCH) || (c.flags == FLAG_NOTICE)) { set_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG); was_searching = 1; } else if (c.flags == FLAG_ERROR) { set_colors(COLOR_ERROR_FG, COLOR_ERROR_BG); was_searching = 1; /* co-opting this should work... */ } else { if (was_selecting || was_searching) { was_selecting = 0; was_searching = 0; set_colors(color, line->is_current ? COLOR_ALT_BG : COLOR_BG); last_color = color; } else if (!last_color || strcmp(color, last_color)) { set_fg_color(color); last_color = color; } } if ((c.flags & FLAG_UNDERLINE) && !was_underlining) { printf("\033[4m"); was_underlining = 1; } else if (!(c.flags & FLAG_UNDERLINE) && was_underlining) { printf("\033[24m"); was_underlining = 0; } if ((env->mode == MODE_COL_SELECTION || env->mode == MODE_COL_INSERT) && line_no >= ((env->start_line < env->line_no) ? env->start_line : env->line_no) && line_no <= ((env->start_line < env->line_no) ? env->line_no : env->start_line) && ((j == env->sel_col) || (j < env->sel_col && j + c.display_width > env->sel_col))) { set_colors(COLOR_SELECTFG, COLOR_SELECTBG); was_selecting = 1; } #define _set_colors(fg,bg) \ if (!(c.flags & FLAG_SELECT) && !(c.flags & FLAG_SEARCH) && !(was_selecting)) { \ set_colors(fg,(line->is_current && bg == COLOR_BG) ? COLOR_ALT_BG : bg); \ } /* Render special characters */ if (c.codepoint == '\t') { _set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("%s", global_config.tab_indicator); for (int i = 1; i < c.display_width; ++i) { printf("%s" ,global_config.space_indicator); } _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint < 32) { /* Codepoints under 32 to get converted to ^@ escapes */ _set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("^%c", '@' + c.codepoint); _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint == 0x7f) { _set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("^?"); _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint > 0x7f && c.codepoint < 0xa0) { _set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("<%2x>", c.codepoint); _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint == 0xa0) { _set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("_"); _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.display_width == 8) { _set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("[U+%04x]", c.codepoint); _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.display_width == 10) { _set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("[U+%06x]", c.codepoint); _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (i > 0 && is_spaces && c.codepoint == ' ' && !(i % env->tabstop)) { _set_colors(COLOR_ALT_FG, COLOR_BG); /* Normal background so this is more subtle */ if (global_config.can_unicode) { printf("▏"); } else { printf("|"); } _set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint == ' ' && i == line->actual - 1) { /* Special case: space at end of line */ _set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("%s",global_config.space_indicator); _set_colors(COLOR_FG, COLOR_BG); } else { /* Normal characters get output */ char tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */ to_eight(c.codepoint, tmp); printf("%s", tmp); } /* Advance the terminal cell offset by the render width of this character */ j += c.display_width; /* Advance to the next character */ i++; } else if (c.display_width > 1) { /* * If this is a wide character but we aren't ready to render yet, * we may need to draw some filler text for the remainder of its * width to ensure we don't jump around when horizontally scrolling * past wide characters. */ remainder = c.display_width - 1; j++; } else { /* Regular character, not ready to draw, advance without doing anything */ j++; i++; } } if (was_underlining) printf("\033[24m"); /** * Determine what color the rest of the line should be. */ if (env->mode != MODE_LINE_SELECTION) { /* If we are not selecting, then use the normal background or highlight * the current line if that feature is enabled. */ if (line->is_current) { set_colors(COLOR_FG, COLOR_ALT_BG); } else { set_colors(COLOR_FG, COLOR_BG); } } else { /* If this line was empty but was part of the selection, we didn't * set the selection color already, so we need to do that here. */ if (!line->actual) { if (env->line_no == line_no || (env->start_line > env->line_no && (line_no >= env->line_no && line_no <= env->start_line)) || (env->start_line < env->line_no && (line_no >= env->start_line && line_no <= env->line_no))) { set_colors(COLOR_SELECTFG, COLOR_SELECTBG); } } } /** * In column modes, we may need to draw a column select beyond the end * of a given line, so we need to draw up to that point first. */ if ((env->mode == MODE_COL_SELECTION || env->mode == MODE_COL_INSERT) && line_no >= ((env->start_line < env->line_no) ? env->start_line : env->line_no) && line_no <= ((env->start_line < env->line_no) ? env->line_no : env->start_line) && j <= env->sel_col && env->sel_col < width) { set_colors(COLOR_FG, COLOR_BG); while (j < env->sel_col) { printf(" "); j++; } set_colors(COLOR_SELECTFG, COLOR_SELECTBG); printf(" "); j++; set_colors(COLOR_FG, COLOR_BG); } /* * `maxcolumn` renders the background outside of the requested line length * in a different color, with a line at the border between the two. */ if (env->maxcolumn && line_no > -1 /* ensures we don't do this for command line */) { /* Fill out the normal background */ if (j < offset) j = offset; for (; j < width + offset && j < env->maxcolumn; ++j) { printf(" "); } /* Draw the line */ if (j < width + offset && j == env->maxcolumn) { j++; set_colors(COLOR_ALT_FG, COLOR_ALT_BG); if (global_config.can_unicode) { printf("▏"); /* Should this be configurable? */ } else { printf("|"); } } /* Fill the rest with the alternate background color */ set_colors(COLOR_ALT_FG, COLOR_ALT_BG); } /** * Clear out the rest of the line. If we are the only buffer or the right split, * and our terminal supports `bce`, we can just bce; otherwise write spaces * until we reach the right side of the screen. */ if (global_config.can_bce && (line_no == -1 || env->left + env->width == global_config.term_width)) { clear_to_end(); } else { /* Paint the rest of the line */ if (j < offset) j = offset; for (; j < width + offset; ++j) { printf(" "); } } } /** * Get the width of the line number region */ int num_width(void) { if (!env->numbers) return 0; int w = log_base_10(env->line_count) + 3; if (w < 4) return 4; return w; } /** * Display width of the revision status gutter. */ int gutter_width(void) { return env->gutter; } /** * Draw the gutter and line numbers. */ void draw_line_number(int x) { if (!env->numbers) return; /* Draw the line number */ if (env->lines[x]->is_current) { set_colors(COLOR_NUMBER_BG, COLOR_NUMBER_FG); } else { set_colors(COLOR_NUMBER_FG, COLOR_NUMBER_BG); } if (global_config.relative_lines && x+1 != env->line_no) { x = x+1 - env->line_no; x = ((x < 0) ? -x : x)-1; } int num_size = num_width() - 2; /* Padding */ for (int y = 0; y < num_size - log_base_10(x + 1); ++y) { printf(" "); } printf("%d%c", x + 1, ((x+1 == env->line_no || global_config.horizontal_shift_scrolling) && env->coffset > 0) ? '<' : ' '); } /** * Used to highlight the current line after moving the cursor. */ void recalculate_current_line(void) { int something_changed = 0; if (global_config.highlight_current_line) { for (int i = 0; i < env->line_count; ++i) { if (env->lines[i]->is_current && i != env->line_no-1) { env->lines[i]->is_current = 0; something_changed = 1; redraw_line(i); } else if (i == env->line_no-1 && !env->lines[i]->is_current) { env->lines[i]->is_current = 1; something_changed = 1; redraw_line(i); } } } else { something_changed = 1; } if (something_changed && global_config.relative_lines) { for (int i = env->offset; i < env->offset + global_config.term_height - global_config.bottom_size - 1 && i < env->line_count; ++i) { /* Place cursor for line number */ place_cursor(1 + gutter_width() + env->left, (i)-env->offset + 1 + global_config.tabs_visible); draw_line_number(i); } } } /** * Redraw line. * * This draws the line number as well as the actual text. */ void redraw_line(int x) { if (env->loading) return; /* Determine if this line is visible. */ if (x - env->offset < 0 || x - env->offset > global_config.term_height - global_config.bottom_size - 1 - global_config.tabs_visible) { return; } /* Calculate offset in screen */ int j = x - env->offset; /* Hide cursor when drawing */ hide_cursor(); /* Move cursor to upper left most cell of this line */ place_cursor(1 + env->left,1 + global_config.tabs_visible + j); /* Draw a gutter on the left. */ if (env->gutter) { switch (env->lines[x]->rev_status) { case 1: set_colors(COLOR_NUMBER_FG, COLOR_GREEN); printf(" "); break; case 2: set_colors(COLOR_NUMBER_FG, global_config.color_gutter ? COLOR_SEARCH_BG : COLOR_ALT_FG); printf(" "); break; case 3: set_colors(COLOR_NUMBER_FG, COLOR_KEYWORD); printf(" "); break; case 4: set_colors(COLOR_ALT_FG, COLOR_RED); printf("▆"); break; case 5: set_colors(COLOR_KEYWORD, COLOR_RED); printf("▆"); break; default: set_colors(COLOR_NUMBER_FG, COLOR_ALT_FG); printf(" "); break; } } draw_line_number(x); int should_shift = x + 1 == env->line_no || global_config.horizontal_shift_scrolling || ((env->mode == MODE_COL_SELECTION || env->mode == MODE_COL_INSERT) && x + 1 >= ((env->start_line < env->line_no) ? env->start_line : env->line_no) && x + 1 <= ((env->start_line < env->line_no) ? env->line_no : env->start_line)); /* * Draw the line text * If this is the active line, the current character cell offset should be used. * (Non-active lines are not shifted and always render from the start of the line) */ render_line(env->lines[x], env->width - gutter_width() - num_width(), should_shift ? env->coffset : 0, x+1); } /** * Draw a ~ line where there is no buffer text. */ void draw_excess_line(int j) { place_cursor(1+env->left,1 + global_config.tabs_visible + j); paint_line(COLOR_ALT_BG); set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("~"); if (env->left + env->width == global_config.term_width && global_config.can_bce) { clear_to_end(); } else { /* Paint the rest of the line */ for (int x = 1; x < env->width; ++x) { printf(" "); } } } /** * Redraw the entire text area */ void redraw_text(void) { if (!env) return; if (!global_config.has_terminal) return; /* Hide cursor while rendering */ hide_cursor(); /* Figure out the available size of the text region */ int l = global_config.term_height - global_config.bottom_size - global_config.tabs_visible; int j = 0; /* Draw each line */ for (int x = env->offset; j < l && x < env->line_count; x++) { redraw_line(x); j++; } /* Draw the rest of the text region as ~ lines */ for (; j < l; ++j) { draw_excess_line(j); } } static int view_left_offset = 0; static int view_right_offset = 0; /** * When in split view, draw the other buffer. * Has special handling for when the split is * on a single buffer. */ void redraw_alt_buffer(buffer_t * buf) { if (left_buffer == right_buffer) { /* draw the opposite view */ int left, width, offset; left = env->left; width = env->width; offset = env->offset; if (left == 0) { /* Draw the right side */ env->left = width; env->width = global_config.term_width - width; env->offset = view_right_offset; view_left_offset = offset; } else { env->left = 0; env->width = global_config.term_width * global_config.split_percent / 100; env->offset = view_left_offset; view_right_offset = offset; } redraw_text(); env->left = left; env->width = width; env->offset = offset; } /* Swap out active buffer */ buffer_t * tmp = env; env = buf; /* Redraw text */ redraw_text(); /* Return original active buffer */ env = tmp; } /** * Basically wcswidth() but implemented internally using our * own utf-8 decoder to ensure it works properly. */ int display_width_of_string(const char * str) { uint8_t * s = (uint8_t *)str; int out = 0; uint32_t c, state = 0; while (*s) { if (!decode(&state, &c, *s)) { out += codepoint_width(c); } else if (state == UTF8_REJECT) { state = 0; } s++; } return out; } void statusbar_append_status(int *remaining_width, size_t *filled, char * output, char * base, ...) { va_list args; va_start(args, base); char tmp[100] = {0}; /* should be big enough */ vsnprintf(tmp, 100, base, args); va_end(args); int width = display_width_of_string(tmp) + 2; size_t totalWidth = strlen(tmp); totalWidth += strlen(color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG)); totalWidth += strlen(color_string(COLOR_STATUS_FG, COLOR_STATUS_BG)); totalWidth += strlen(color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG)); totalWidth += 3; if (totalWidth + *filled >= 2047) { return; } if (width < *remaining_width) { strcat(output,color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG)); strcat(output,"["); strcat(output,color_string(COLOR_STATUS_FG, COLOR_STATUS_BG)); strcat(output, tmp); strcat(output,color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG)); strcat(output,"]"); (*remaining_width) -= width; (*filled) += totalWidth; } } int statusbar_build_right(char * right_hand) { char tmp[1024] = {0}; sprintf(tmp, " Line %d/%d Col: %d ", env->line_no, env->line_count, env->col_no); int out = display_width_of_string(tmp); char * s = right_hand; s += sprintf(s, "%s", color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG)); s += sprintf(s, " Line "); s += sprintf(s, "%s", color_string(COLOR_STATUS_FG, COLOR_STATUS_BG)); s += sprintf(s, "%d/%d ", env->line_no, env->line_count); s += sprintf(s, "%s", color_string(COLOR_STATUS_ALT, COLOR_STATUS_BG)); s += sprintf(s, " Col: "); s += sprintf(s, "%s", color_string(COLOR_STATUS_FG, COLOR_STATUS_BG)); s += sprintf(s, "%d ", env->col_no); return out; } /** * Draw the status bar * * The status bar shows the name of the file, whether it has modifications, * and (in the future) what syntax highlighting mode is enabled. * * The right side of the status bar shows the line number and column. */ void redraw_statusbar(void) { if (global_config.hide_statusbar) return; if (!global_config.has_terminal) return; if (!env) return; /* Hide cursor while rendering */ hide_cursor(); /* Move cursor to the status bar line (second from bottom */ place_cursor(1, global_config.term_height - 1); /* Set background colors for status line */ paint_line(COLOR_STATUS_BG); set_colors(COLOR_STATUS_FG, COLOR_STATUS_BG); /* Pre-render the right hand side of the status bar */ char right_hand[1024] = {0}; int right_width = statusbar_build_right(right_hand); char status_bits[2048] = {0}; /* Sane maximum */ size_t filled = 0; int remaining_width = global_config.term_width - right_width; #define ADD(...) do { statusbar_append_status(&remaining_width, &filled, status_bits, __VA_ARGS__); } while (0) if (env->syntax) { ADD("%s",env->syntax->name); } /* Print file status indicators */ if (env->modified) { ADD("+"); } if (env->readonly) { ADD("ro"); } if (env->crnl) { ADD("crnl"); } if (env->tabs) { ADD("tabs"); } else { ADD("spaces=%d", env->tabstop); } if (global_config.yanks) { ADD("y:%ld", global_config.yank_count); } if (env->indent) { ADD("indent"); } if (global_config.smart_complete) { ADD("complete"); } if (global_config.background_task) { ADD("working"); } #undef ADD uint8_t * file_name = (uint8_t *)(env->file_name ? env->file_name : "[No Name]"); int file_name_width = display_width_of_string((char*)file_name); if (remaining_width > 3) { int is_chopped = 0; while (remaining_width < file_name_width + 3) { is_chopped = 1; if ((*file_name & 0xc0) == 0xc0) { /* First byte of a multibyte character */ file_name++; while ((*file_name & 0xc0) == 0x80) file_name++; } else { file_name++; } file_name_width = display_width_of_string((char*)file_name); } if (is_chopped) { set_colors(COLOR_ALT_FG, COLOR_STATUS_BG); printf("<"); } set_colors(COLOR_STATUS_FG, COLOR_STATUS_BG); printf("%s ", file_name); } printf("%s", status_bits); /* Clear the rest of the status bar */ clear_to_end(); /* Move the cursor appropriately to draw it */ place_cursor(global_config.term_width - right_width, global_config.term_height - 1); set_colors(COLOR_STATUS_FG, COLOR_STATUS_BG); printf("%s",right_hand); } /** * Redraw the navigation numbers on the right side of the command line */ void redraw_nav_buffer(void) { if (!global_config.has_terminal) return; if (nav_buffer) { store_cursor(); place_cursor(global_config.term_width - nav_buffer - 2, global_config.term_height); printf("%s", nav_buf); clear_to_end(); restore_cursor(); } } /** * Draw the command line * * The command line either has input from the user (:quit, :!make, etc.) * or shows the INSERT (or VISUAL in the future) mode name. */ void redraw_commandline(void) { if (!global_config.has_terminal) return; if (!env) return; /* Hide cursor while rendering */ hide_cursor(); /* Move cursor to the last line */ place_cursor(1, global_config.term_height); /* Set background color */ paint_line(COLOR_BG); set_colors(COLOR_FG, COLOR_BG); /* If we are in an edit mode, note that. */ if (env->mode == MODE_INSERT) { set_bold(); printf("-- INSERT --"); clear_to_end(); unset_bold(); } else if (env->mode == MODE_LINE_SELECTION) { set_bold(); printf("-- LINE SELECTION -- (%d:%d)", (env->start_line < env->line_no) ? env->start_line : env->line_no, (env->start_line < env->line_no) ? env->line_no : env->start_line ); clear_to_end(); unset_bold(); } else if (env->mode == MODE_COL_SELECTION) { set_bold(); printf("-- COL SELECTION -- (%d:%d %d)", (env->start_line < env->line_no) ? env->start_line : env->line_no, (env->start_line < env->line_no) ? env->line_no : env->start_line, (env->sel_col) ); clear_to_end(); unset_bold(); } else if (env->mode == MODE_COL_INSERT) { set_bold(); printf("-- COL INSERT -- (%d:%d %d)", (env->start_line < env->line_no) ? env->start_line : env->line_no, (env->start_line < env->line_no) ? env->line_no : env->start_line, (env->sel_col) ); clear_to_end(); unset_bold(); } else if (env->mode == MODE_REPLACE) { set_bold(); printf("-- REPLACE --"); clear_to_end(); unset_bold(); } else if (env->mode == MODE_CHAR_SELECTION) { set_bold(); printf("-- CHAR SELECTION -- "); clear_to_end(); unset_bold(); } else if (env->mode == MODE_DIRECTORY_BROWSE) { set_bold(); printf("-- DIRECTORY BROWSE --"); clear_to_end(); unset_bold(); } else { clear_to_end(); } redraw_nav_buffer(); } /** * Draw a message on the command line. */ void render_commandline_message(char * message, ...) { /* varargs setup */ va_list args; va_start(args, message); /* Hide cursor while rendering */ hide_cursor(); /* Move cursor to the last line */ place_cursor(1, global_config.term_height); /* Set background color */ paint_line(COLOR_BG); set_colors(COLOR_FG, COLOR_BG); vprintf(message, args); va_end(args); /* Clear the rest of the status bar */ clear_to_end(); redraw_nav_buffer(); } BIM_ACTION(redraw_all, 0, "Repaint the screen." ,void) { if (!env) return; redraw_tabbar(); redraw_text(); if (left_buffer) { redraw_alt_buffer(left_buffer == env ? right_buffer : left_buffer); } redraw_statusbar(); redraw_commandline(); if (global_config.overlay_mode == OVERLAY_MODE_COMMAND || global_config.overlay_mode == OVERLAY_MODE_SEARCH || global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { render_command_input_buffer(); } } void pause_for_key(void) { int c; while ((c = bim_getch())== -1); bim_unget(c); redraw_all(); } /** * Redraw all screen elements except the other split view. */ void redraw_most(void) { redraw_tabbar(); redraw_text(); redraw_statusbar(); redraw_commandline(); } /** * Disable screen splitting. */ void unsplit(void) { if (left_buffer) { left_buffer->left = 0; left_buffer->width = global_config.term_width; } if (right_buffer) { right_buffer->left = 0; right_buffer->width = global_config.term_width; } left_buffer = NULL; right_buffer = NULL; redraw_all(); } /** * Update the terminal title bar */ void update_title(void) { if (!global_config.can_title) return; char cwd[1024] = {'/',0}; getcwd(cwd, 1024); for (int i = 1; i < 3; ++i) { printf("\033]%d;%s%s (%s) - Bim\007", i, env->file_name ? env->file_name : "[No Name]", env->modified ? " +" : "", cwd); } } /** * Mark this buffer as modified and * redraw the status and tabbar if needed. */ void set_modified(void) { /* If it was already marked modified, no need to do anything */ if (env->modified) return; /* Mark as modified */ env->modified = 1; /* Redraw some things */ update_title(); redraw_tabbar(); redraw_statusbar(); } /** * Draw a message on the status line */ void render_status_message(char * message, ...) { if (!env) return; /* Don't print when there's no active environment; this usually means a bimrc command tried to print something */ /* varargs setup */ va_list args; va_start(args, message); /* Hide cursor while rendering */ hide_cursor(); /* Move cursor to the status bar line (second from bottom */ place_cursor(1, global_config.term_height - 1); /* Set background colors for status line */ paint_line(COLOR_STATUS_BG); set_colors(COLOR_STATUS_FG, COLOR_STATUS_BG); /* Process format string */ vprintf(message, args); va_end(args); /* Clear the rest of the status bar */ clear_to_end(); } /** * Draw an error message to the command line. */ void render_error(char * message, ...) { /* varargs setup */ va_list args; va_start(args, message); if (env) { /* Hide cursor while rendering */ hide_cursor(); /* Move cursor to the command line */ place_cursor(1, global_config.term_height); /* Set appropriate error message colors */ set_colors(COLOR_ERROR_FG, COLOR_ERROR_BG); /* Draw the message */ vprintf(message, args); va_end(args); global_config.had_error = 1; } else { printf("bim: error during startup: "); vprintf(message, args); va_end(args); printf("\n"); } } int is_paren(int c) { uint32_t * p = global_config.paren_pairs; while (*p) { if ((uint32_t)c == *p) return 1; p++; } return 0; } #define _rehighlight_parens() do { \ if (i < 0 || i >= env->line_count) break; \ for (int j = 0; j < env->lines[i]->actual; ++j) { \ if (i == line-1 && j == col-1) { \ env->lines[line-1]->text[col-1].flags |= FLAG_SELECT; \ continue; \ } else { \ env->lines[i]->text[j].flags &= (~FLAG_SELECT); \ } \ } \ redraw_line(i); \ } while (0) /** * If the config option is enabled, find the matching * paren character and highlight it with the SELECT * colors, clearing out other SELECT values. As we * co-opt the SELECT flag, don't do this in selection * modes - only in normal and insert modes. */ void highlight_matching_paren(void) { if (env->mode == MODE_LINE_SELECTION || env->mode == MODE_CHAR_SELECTION) return; if (!global_config.highlight_parens) return; int line = -1, col = -1; if (env->line_no <= env->line_count && env->col_no <= env->lines[env->line_no-1]->actual && is_paren(env->lines[env->line_no-1]->text[env->col_no-1].codepoint)) { find_matching_paren(&line, &col, 1); } 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)) { find_matching_paren(&line, &col, 2); } if (env->highlighting_paren == -1 && line == -1) return; if (env->highlighting_paren > 0) { int i = env->highlighting_paren - 1; _rehighlight_parens(); } if (env->highlighting_paren != line && line != -1) { int i = line - 1; _rehighlight_parens(); } env->highlighting_paren = line; } /** * Recalculate syntax for the matched paren. * Useful when entering selection modes. */ void unhighlight_matching_paren(void) { if (env->highlighting_paren > 0 && env->highlighting_paren <= env->line_count) { for (int i = 0; i < env->line_count; i++) { for (int j = 0; j < env->lines[i]->actual; ++j) { env->lines[i]->text[j].flags &= ~(FLAG_SELECT); } } env->highlighting_paren = -1; } } /** * Move the cursor to the appropriate location based * on where it is in the text region. * * This does some additional math to set the text * region horizontal offset. */ void place_cursor_actual(void) { /* Invalid positions */ if (env->line_no < 1) env->line_no = 1; if (env->col_no < 1) env->col_no = 1; /* Account for the left hand gutter */ int num_size = num_width() + gutter_width(); int x = num_size + 1 - env->coffset; /* Determine where the cursor is physically */ for (int i = 0; i < env->col_no - 1; ++i) { char_t * c = &env->lines[env->line_no-1]->text[i]; x += c->display_width; } /* y is a bit easier to calculate */ int y = env->line_no - env->offset + 1; int needs_redraw = 0; while (y < 2 + global_config.cursor_padding && env->offset > 0) { y++; env->offset--; needs_redraw = 1; } while (y > 1 + global_config.term_height - global_config.bottom_size - global_config.cursor_padding - global_config.tabs_visible) { y--; env->offset++; needs_redraw = 1; } if (needs_redraw) { redraw_text(); redraw_tabbar(); redraw_statusbar(); redraw_commandline(); } /* If the cursor has gone off screen to the right... */ if (x > env->width - 1) { /* Adjust the offset appropriately to scroll horizontally */ int diff = x - (env->width - 1); env->coffset += diff; x -= diff; redraw_text(); } /* Same for scrolling horizontally to the left */ if (x < num_size + 1) { int diff = (num_size + 1) - x; env->coffset -= diff; x += diff; redraw_text(); } highlight_matching_paren(); recalculate_current_line(); /* Move the actual terminal cursor */ place_cursor(x+env->left,y - !global_config.tabs_visible); /* Show the cursor */ show_cursor(); } /** * If the screen is split, update the split sizes based * on the new terminal width and the user's split_percent setting. */ void update_split_size(void) { if (!left_buffer) return; if (left_buffer == right_buffer) { if (left_buffer->left == 0) { left_buffer->width = global_config.term_width * global_config.split_percent / 100; } else { right_buffer->left = global_config.term_width * global_config.split_percent / 100; right_buffer->width = global_config.term_width - right_buffer->left; } return; } left_buffer->left = 0; left_buffer->width = global_config.term_width * global_config.split_percent / 100; right_buffer->left = left_buffer->width; right_buffer->width = global_config.term_width - left_buffer->width; } /** * Update screen size */ void update_screen_size(void) { struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); global_config.term_width = w.ws_col; global_config.term_height = w.ws_row; if (env) { if (left_buffer) { update_split_size(); } else if (env != left_buffer && env != right_buffer) { env->width = w.ws_col; } } for (int i = 0; i < buffers_len; ++i) { if (buffers[i] != left_buffer && buffers[i] != right_buffer) { buffers[i]->width = w.ws_col; } } } /** * Handle terminal size changes */ void SIGWINCH_handler(int sig) { (void)sig; update_screen_size(); redraw_all(); signal(SIGWINCH, SIGWINCH_handler); } /** * Handle suspend */ void SIGTSTP_handler(int sig) { (void)sig; mouse_disable(); set_buffered(); reset(); clear_screen(); show_cursor(); unset_bracketed_paste(); unset_alternate_screen(); fflush(stdout); signal(SIGTSTP, SIG_DFL); raise(SIGTSTP); } void SIGCONT_handler(int sig) { (void)sig; set_alternate_screen(); set_bracketed_paste(); set_unbuffered(); update_screen_size(); mouse_enable(); redraw_all(); update_title(); signal(SIGCONT, SIGCONT_handler); signal(SIGTSTP, SIGTSTP_handler); } void SIGINT_handler(int sig) { krk_currentThread.flags |= KRK_THREAD_SIGNALLED; signal(SIGINT, SIGINT_handler); } void try_to_center(void) { int half_a_screen = (global_config.term_height - 3) / 2; if (half_a_screen < env->line_no) { env->offset = env->line_no - half_a_screen; } else { env->offset = 0; } } BIM_ACTION(suspend, 0, "Suspend bim and the rest of the job it was run in." ,void) { kill(0, SIGTSTP); } /** * Move the cursor to a specific line. */ BIM_ACTION(goto_line, ARG_IS_CUSTOM, "Jump to the requested line." ,int line) { if (line == -1) line = env->line_count; /* Respect file bounds */ if (line < 1) line = 1; if (line > env->line_count) line = env->line_count; /* Move the cursor / text region offsets */ env->coffset = 0; env->line_no = line; env->col_no = 1; if (!env->loading) { if (line > env->offset && line < env->offset + global_config.term_height - global_config.bottom_size) { place_cursor_actual(); } else { try_to_center(); } redraw_most(); } else { try_to_center(); } } /** * Process (part of) a file and add it to a buffer. */ void add_buffer(uint8_t * buf, int size) { for (int i = 0; i < size; ++i) { if (!decode(&state, &codepoint_r, buf[i])) { uint32_t c = codepoint_r; if (c == '\n') { if (!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') { env->lines[env->line_no-1]->actual--; env->crnl = 1; } env->lines = add_line(env->lines, env->line_no); env->col_no = 1; env->line_no += 1; } else if (env->crnl && c == '\r') { continue; } else { char_t _c; _c.codepoint = (uint32_t)c; _c.flags = 0; _c.display_width = codepoint_width((wchar_t)c); line_t * line = env->lines[env->line_no - 1]; line_t * nline = line_insert(line, _c, env->col_no - 1, env->line_no-1); if (line != nline) { env->lines[env->line_no - 1] = nline; } env->col_no += 1; } } else if (state == UTF8_REJECT) { state = 0; } } } /** * Add a raw string to a buffer. Convenience wrapper * for add_buffer for nil-terminated strings. */ void add_string(char * string) { add_buffer((uint8_t*)string,strlen(string)); } int str_ends_with(const char * haystack, const char * needle) { int i = strlen(haystack); int j = strlen(needle); do { if (haystack[i] != needle[j]) return 0; if (j == 0) return 1; if (i == 0) return 0; i--; j--; } while (1); } /** * Find a syntax highlighter for the given filename. */ struct syntax_definition * match_syntax(char * file) { for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { for (char ** ext = s->ext; *ext; ++ext) { if (str_ends_with(file, *ext)) return s; } } return NULL; } /** * Set the syntax configuration by the name of the syntax highlighter. */ void set_syntax_by_name(const char * name) { if (!strcmp(name,"none")) { for (int i = 0; i < env->line_count; ++i) { env->lines[i]->istate = -1; for (int j = 0; j < env->lines[i]->actual; ++j) { env->lines[i]->text[j].flags &= (3 << 5); } } env->syntax = NULL; redraw_all(); return; } for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { if (!strcmp(name,s->name)) { env->syntax = s; for (int i = 0; i < env->line_count; ++i) { env->lines[i]->istate = -1; } schedule_complete_recalc(); redraw_all(); return; } } render_error("unrecognized syntax type"); } /** * Check if a string is all numbers. */ int is_all_numbers(const char * c) { while (*c) { if (!isdigit(*c)) return 0; c++; } return 1; } struct file_listing { int type; char * filename; }; int sort_files(const void * a, const void * b) { struct file_listing * _a = (struct file_listing *)a; struct file_listing * _b = (struct file_listing *)b; if (_a->type == _b->type) { return strcmp(_a->filename, _b->filename); } else { return _a->type - _b->type; } } void read_directory_into_buffer(char * file) { DIR * dirp = opendir(file); if (!dirp) { env->loading = 0; return; } add_string("# Directory listing for `"); add_string(file); add_string("`\n"); /* Flexible array to hold directory contents */ int available = 32; int count = 0; struct file_listing * files = calloc(sizeof(struct file_listing), available); /* Read directory */ struct dirent * ent = readdir(dirp); while (ent) { struct stat statbuf; char * tmp = malloc(strlen(file) + 1 + strlen(ent->d_name) + 1); snprintf(tmp, strlen(file) + 1 + strlen(ent->d_name) + 1, "%s/%s", file, ent->d_name); stat(tmp, &statbuf); int type = (S_ISDIR(statbuf.st_mode)) ? 'd' : 'f'; if (count + 1 == available) { available *= 2; \ files = realloc(files, sizeof(struct file_listing) * available); \ } \ files[count].type = type; files[count].filename = strdup(ent->d_name); count++; ent = readdir(dirp); free(tmp); } closedir(dirp); /* Sort directory entries */ qsort(files, count, sizeof(struct file_listing), sort_files); for (int i = 0; i < count; ++i) { add_string(files[i].type == 'd' ? "d" : "f"); add_string(" "); add_string(files[i].filename); add_string("\n"); free(files[i].filename); } free(files); env->file_name = strdup(file); env->syntax = find_syntax_calculator("dirent"); schedule_complete_recalc(); env->readonly = 1; env->loading = 0; env->mode = MODE_DIRECTORY_BROWSE; env->line_no = 1; redraw_all(); } BIM_ACTION(open_file_from_line, 0, "When browsing a directory, open the file under the cursor." ,void) { if (env->lines[env->line_no-1]->actual < 1) return; if (env->lines[env->line_no-1]->text[0].codepoint != 'd' && env->lines[env->line_no-1]->text[0].codepoint != 'f') return; /* Collect file name */ char * tmp = malloc(strlen(env->file_name) + 1 + env->lines[env->line_no-1]->actual * 7); /* Should be enough */ memset(tmp, 0, strlen(env->file_name) + 1 + env->lines[env->line_no-1]->actual * 7); char * t = tmp; /* Start by copying the filename */ t += sprintf(t, "%s/", env->file_name); /* Start from character 2 to skip d/f and space */ for (int i = 2; i < env->lines[env->line_no-1]->actual; ++i) { t += to_eight(env->lines[env->line_no-1]->text[i].codepoint, t); } *t = '\0'; /* Normalize */ char tmp_path[PATH_MAX+1]; if (!realpath(tmp, tmp_path)) { free(tmp); return; } free(tmp); /* Open file */ buffer_t * old_buffer = env; open_file(tmp_path); buffer_close(old_buffer); update_title(); redraw_all(); } int line_matches(line_t * line, char * string) { uint32_t c = 0, state = 0; int i = 0; while (*string) { if (!decode(&state, &c, *string)) { if (i >= line->actual) return 0; if (line->text[i].codepoint != c) return 0; string++; i++; } else if (state == UTF8_REJECT) { state = 0; } } return 1; } void run_onload(buffer_t * env) { KrkValue onLoad; if (krk_tableGet_fast(&krk_currentThread.module->fields, S("onload"), &onLoad)) { krk_push(onLoad); int args = 0; if (env->file_name) { krk_push(OBJECT_VAL(S("filename"))); krk_push(OBJECT_VAL(krk_copyString(env->file_name,strlen(env->file_name)))); args++; } if (env->syntax) { krk_push(OBJECT_VAL(S("lang"))); krk_push(OBJECT_VAL(krk_copyString(env->syntax->name,strlen(env->syntax->name)))); args++; if (env->syntax->krkClass) { krk_push(OBJECT_VAL(S("highlighter"))); krk_push(OBJECT_VAL(env->syntax->krkClass)); args++; } } if (IS_CLOSURE(onLoad) && AS_CLOSURE(onLoad)->function->requiredArgs == 1) { /* Use old ABI if the function accepts one required argument. */ krk_push(krk_callNativeOnStack(args * 2, &krk_currentThread.stackTop[-args*2], 0, krk_dict_of)); if (args) { krk_swap(args * 2); while (args--) { krk_pop(); krk_pop(); } } krk_callStack(1); } else { /* Otherwise use the new API where the function can accepts keyword args. */ krk_push(KWARGS_VAL(args)); krk_callStack(args * 2 + 1); } krk_resetStack(); } } static void render_syntax_async(background_task_t * task) { buffer_t * old_env = env; env = task->env; int line_no = task->_private_i; if (env->line_count && line_no < env->line_count) { int tmp = env->loading; env->loading = 1; recalculate_syntax(env->lines[line_no], line_no); env->loading = tmp; if (env == old_env) { redraw_line(line_no); } } env = old_env; } static void schedule_complete_recalc(void) { if (env->line_count < 1000) { for (int i = 0; i < env->line_count; ++i) { recalculate_syntax(env->lines[i], i); } return; } /* TODO see if there's already a redraw scheduled */ for (int i = 0; i < env->line_count; ++i) { background_task_t * task = malloc(sizeof(background_task_t)); task->env = env; task->_private_i = i; task->func = render_syntax_async; task->next = NULL; if (global_config.tail_task) { global_config.tail_task->next = task; } global_config.tail_task = task; if (!global_config.background_task) { global_config.background_task = task; } } redraw_statusbar(); } /** * Create a new buffer from a file. */ void open_file(char * file) { env = buffer_new(); env->width = global_config.term_width; env->left = 0; env->loading = 1; setup_buffer(env); FILE * f; int init_line = -1; if (!strcmp(file,"-")) { /** * Read file from stdin. stderr provides terminal input. */ if (isatty(STDIN_FILENO)) { if (buffers_len == 1) { quit("stdin is a terminal and you tried to open -; not letting you do that"); } close_buffer(); render_error("stdin is a terminal and you tried to open -; not letting you do that"); return; } f = stdin; env->modified = 1; } else { char * l = strrchr(file, ':'); if (l && is_all_numbers(l+1)) { *l = '\0'; l++; init_line = atoi(l); } char * _file = file; if (file[0] == '~') { char * home = getenv("HOME"); if (home) { _file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */ sprintf(_file, "%s%s", home, file+1); } } if (file_is_open(_file)) { if (file != _file) free(_file); close_buffer(); return; } struct stat statbuf; if (!stat(_file, &statbuf) && S_ISDIR(statbuf.st_mode)) { read_directory_into_buffer(_file); if (file != _file) free(_file); return; } f = fopen(_file, "r"); if (file != _file) free(_file); if (!f && errno != ENOENT) { render_error("%s: %s", file, strerror(errno)); pause_for_key(); close_buffer(); return; } env->file_name = strdup(file); } if (!f) { if (global_config.highlight_on_open) { env->syntax = match_syntax(file); } env->loading = 0; if (global_config.go_to_line) { goto_line(1); } if (env->syntax && env->syntax->prefers_spaces) { env->tabs = 0; } update_biminfo(env, 1); run_onload(env); return; } uint8_t buf[BLOCK_SIZE]; state = 0; while (!feof(f) && !ferror(f)) { size_t r = fread(buf, 1, BLOCK_SIZE, f); add_buffer(buf, r); } if (ferror(f)) { env->loading = 0; return; } if (env->line_no && env->lines[env->line_no-1] && env->lines[env->line_no-1]->actual == 0) { /* Remove blank line from end */ env->lines = remove_line(env->lines, env->line_no-1); } if (global_config.highlight_on_open) { env->syntax = match_syntax(file); if (!env->syntax) { if (line_matches(env->lines[0], "lines[0], "lines[0], "#!/usr/bin/env bash")) set_syntax_by_name("bash"); else if (line_matches(env->lines[0], "#!/bin/bash")) set_syntax_by_name("bash"); else if (line_matches(env->lines[0], "#!/bin/sh")) set_syntax_by_name("bash"); else if (line_matches(env->lines[0], "#!/usr/bin/env python")) set_syntax_by_name("py"); else if (line_matches(env->lines[0], "#!/usr/bin/env groovy")) set_syntax_by_name("groovy"); } if (!env->syntax && global_config.syntax_fallback) { set_syntax_by_name(global_config.syntax_fallback); } schedule_complete_recalc(); } /* Try to automatically figure out tabs vs. spaces */ int tabs = 0, spaces = 0; for (int i = 0; i < env->line_count; ++i) { if (env->lines[i]->actual > 1) { /* Make sure line has at least some text on it */ if (env->lines[i]->text[0].codepoint == '\t') tabs++; if (env->lines[i]->text[0].codepoint == ' ' && env->lines[i]->text[1].codepoint == ' ') /* Ignore spaces at the start of asterisky C comments */ spaces++; } } if (spaces > tabs) { env->tabs = 0; } else if (spaces == tabs && env->syntax) { env->tabs = env->syntax->prefers_spaces; } if (spaces > tabs) { int one = 0, two = 0, three = 0, four = 0; /* If you use more than that, I don't like you. */ int lastCount = 0; for (int i = 0; i < env->line_count; ++i) { if (env->lines[i]->actual > 1 && !line_is_comment(env->lines[i])) { /* Count spaces at beginning */ int c = 0, diff = 0; while (c < env->lines[i]->actual && env->lines[i]->text[c].codepoint == ' ') c++; if (c > lastCount) { diff = c - lastCount; } else if (c < lastCount) { diff = lastCount - c; } if (diff == 1) one++; if (diff == 2) two++; if (diff == 3) three++; if (diff == 4) four++; lastCount = c; } } if (four > three && four > two && four > one) { env->tabstop = 4; } else if (three > two && three > one) { env->tabstop = 3; } else if (two > one) { env->tabstop = 2; } else { env->tabstop = 1; } } env->loading = 0; if (global_config.check_git) { env->checkgitstatusonwrite = 1; git_examine(file); } for (int i = 0; i < env->line_count; ++i) { recalculate_tabs(env->lines[i]); } if (global_config.go_to_line) { if (init_line != -1) { goto_line(init_line); } else { env->line_no = 1; env->col_no = 1; fetch_from_biminfo(env); place_cursor_actual(); redraw_all(); set_preferred_column(); } } update_biminfo(env, 1); fclose(f); run_onload(env); } /** * Clean up the terminal and exit the editor. */ void quit(const char * message) { mouse_disable(); set_buffered(); reset(); clear_screen(); show_cursor(); unset_bracketed_paste(); unset_alternate_screen(); krk_freeVM(); if (message) { fprintf(stdout, "%s\n", message); } exit(0); } /** * Try to quit, but don't continue if there are * modified buffers open. */ void try_quit(void) { for (int i = 0; i < buffers_len; i++ ) { buffer_t * _env = buffers[i]; if (_env->modified) { if (_env->file_name) { render_error("Modifications made to file `%s` in tab %d. Aborting.", _env->file_name, i+1); } else { render_error("Unsaved new file in tab %d. Aborting.", i+1); } return; } } /* Close all buffers */ while (buffers_len) { buffer_close(buffers[0]); } quit(NULL); } /** * Switch to the previous buffer */ BIM_ACTION(previous_tab, 0, "Switch the previous tab" ,void) { buffer_t * last = NULL; for (int i = 0; i < buffers_len; i++) { buffer_t * _env = buffers[i]; if (_env == env) { if (last) { /* Wrap around */ env = last; if (left_buffer && (left_buffer != env && right_buffer != env)) unsplit(); redraw_all(); update_title(); return; } else { env = buffers[buffers_len-1]; if (left_buffer && (left_buffer != env && right_buffer != env)) unsplit(); redraw_all(); update_title(); return; } } last = _env; } } /** * Switch to the next buffer */ BIM_ACTION(next_tab, 0, "Switch to the next tab" ,void) { for (int i = 0; i < buffers_len; i++) { buffer_t * _env = buffers[i]; if (_env == env) { if (i != buffers_len - 1) { env = buffers[i+1]; if (left_buffer && (left_buffer != env && right_buffer != env)) unsplit(); redraw_all(); update_title(); return; } else { /* Wrap around */ env = buffers[0]; if (left_buffer && (left_buffer != env && right_buffer != env)) unsplit(); redraw_all(); update_title(); return; } } } } /** * Check for modified lines in a file by examining `git diff` output. * This can be enabled globally in bimrc or per environment with the 'git' option. */ int git_examine(char * filename) { if (env->modified) return 1; int fds[2]; pipe(fds); int child = fork(); if (child == 0) { FILE * dev_null = fopen("/dev/null","w"); close(fds[0]); dup2(fds[1], STDOUT_FILENO); dup2(fileno(dev_null), STDERR_FILENO); char * args[] = {"git","--no-pager","diff","-U0","--no-color","--",filename,NULL}; exit(execvp("git",args)); } else if (child < 0) { return 1; } close(fds[1]); FILE * f = fdopen(fds[0],"r"); int line_offset = 0; while (!feof(f)) { int c = fgetc(f); if (c < 0) break; if (c == '@' && line_offset == 0) { /* Read line offset, count */ if (fgetc(f) == '@' && fgetc(f) == ' ' && fgetc(f) == '-') { /* This algorithm is borrowed from Kakoune and only requires us to parse the @@ line */ int from_line = 0; int from_count = 0; int to_line = 0; int to_count = 0; fscanf(f,"%d",&from_line); if (fgetc(f) == ',') { fscanf(f,"%d",&from_count); } else { from_count = 1; } fscanf(f,"%d",&to_line); if (fgetc(f) == ',') { fscanf(f,"%d",&to_count); } else { to_count = 1; } if (to_line > env->line_count) continue; if (from_count == 0 && to_count > 0) { /* No -, all + means all of to_count is green */ for (int i = 0; i < to_count; ++i) { env->lines[to_line+i-1]->rev_status = 1; /* Green */ } } else if (from_count > 0 && to_count == 0) { /* * No +, all - means we have a deletion. We mark the next line such that it has a red bar at the top * Note that to_line is one lower than the affected line, so we don't need to mess with indexes. */ if (to_line >= env->line_count) continue; env->lines[to_line]->rev_status = 4; /* Red */ } else if (from_count > 0 && from_count == to_count) { /* from = to, all modified */ for (int i = 0; i < to_count; ++i) { env->lines[to_line+i-1]->rev_status = 3; /* Blue */ } } else if (from_count > 0 && from_count < to_count) { /* from < to, some modified, some added */ for (int i = 0; i < from_count; ++i) { env->lines[to_line+i-1]->rev_status = 3; /* Blue */ } for (int i = from_count; i < to_count; ++i) { env->lines[to_line+i-1]->rev_status = 1; /* Green */ } } else if (to_count > 0 && from_count > to_count) { /* from > to, we deleted but also modified some lines */ env->lines[to_line-1]->rev_status = 5; /* Red + Blue */ for (int i = 1; i < to_count; ++i) { env->lines[to_line+i-1]->rev_status = 3; /* Blue */ } } } } if (c == '\n') { line_offset = 0; continue; } line_offset++; } fclose(f); waitpid(-1,NULL,WNOHANG); return 0; } /** * Write file contents to FILE */ void output_file(buffer_t * env, FILE * f) { int i, j; for (i = 0; i < env->line_count; ++i) { line_t * line = env->lines[i]; line->rev_status = 0; for (j = 0; j < line->actual; j++) { char_t c = line->text[j]; if (c.codepoint == 0) { char buf[1] = {0}; fwrite(buf, 1, 1, f); } else { char tmp[8] = {0}; int i = to_eight(c.codepoint, tmp); fwrite(tmp, i, 1, f); } } if (env->crnl) fputc('\r', f); fputc('\n', f); } } /** * Write active buffer to file */ void write_file(char * file) { if (!file) { render_error("Need a file to write to."); return; } char * _file = file; if (file[0] == '~') { char * home = getenv("HOME"); if (home) { _file = malloc(strlen(file) + strlen(home) + 4); /* Paranoia */ sprintf(_file, "%s%s", home, file+1); } } FILE * f = fopen(_file, "w+"); if (file != _file) free(_file); if (!f) { render_error("Failed to open file for writing."); return; } /* Go through each line and convert it back to UTF-8 */ output_file(env, f); fclose(f); /* Mark it no longer modified */ env->modified = 0; env->last_save_history = env->history; /* If there was no file name set, set one */ if (!env->file_name) { env->file_name = malloc(strlen(file) + 1); memcpy(env->file_name, file, strlen(file) + 1); } if (env->checkgitstatusonwrite) { git_examine(file); } update_title(); redraw_all(); } /** * Close the active buffer */ void close_buffer(void) { buffer_t * previous_env = env; buffer_t * new_env = buffer_close(env); if (new_env == previous_env) { /* ?? Failed to close buffer */ render_error("lolwat"); } if (left_buffer && env == left_buffer) { left_buffer = NULL; right_buffer->left = 0; right_buffer->width = global_config.term_width; right_buffer = NULL; } else if (left_buffer && env == right_buffer) { right_buffer = NULL; left_buffer->left = 0; left_buffer->width = global_config.term_width; left_buffer = NULL; } /* No more buffers, exit */ if (!new_env) { quit(NULL); } /* Set the new active buffer */ env = new_env; /* Redraw the screen */ redraw_all(); update_title(); } /** * Set the visual column the cursor should attempt to keep * when moving up and down based on where the cursor currently is. * This should happen whenever the user intentionally changes * the cursor's horizontal positioning, such as with left/right * arrow keys, word-move, search, mouse, etc. */ void set_preferred_column(void) { int c = 0; for (int i = 0; i < env->lines[env->line_no-1]->actual && i < env->col_no-1; ++i) { c += env->lines[env->line_no-1]->text[i].display_width; } env->preferred_column = c; } BIM_ACTION(cursor_down, 0, "Move the cursor one line down." ,void) { /* If this isn't already the last line... */ if (env->line_no < env->line_count) { /* Move the cursor down */ env->line_no += 1; /* Try to place the cursor horizontally at the preferred column */ int _x = 0; for (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) { char_t * c = &env->lines[env->line_no-1]->text[i]; _x += c->display_width; env->col_no = i+1; if (_x > env->preferred_column) { break; } } if (env->mode == MODE_INSERT && _x <= env->preferred_column) { env->col_no = env->lines[env->line_no-1]->actual + 1; } /* * If the horizontal cursor position exceeds the width the new line, * then move the cursor left to the extent of the new line. * * If we're in insert mode, we can go one cell beyond the end of the line */ if (env->col_no > env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT)) { env->col_no = env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT); if (env->col_no == 0) env->col_no = 1; } if (env->loading) return; /* * If the screen was scrolled horizontally, unscroll it; * if it will be scrolled on this line as well, that will * be handled by place_cursor_actual */ int redraw = 0; if (env->coffset != 0) { env->coffset = 0; redraw = 1; } /* If we've scrolled past the bottom of the screen, scroll the screen */ if (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - global_config.tabs_visible - global_config.cursor_padding) { env->offset += 1; /* Tell terminal to scroll */ if (global_config.can_scroll && !left_buffer) { if (!global_config.can_insert) { shift_up(1); redraw_tabbar(); } else { delete_lines_at(global_config.tabs_visible ? 2 : 1, 1); } /* A new line appears on screen at the bottom, draw it */ int l = global_config.term_height - global_config.bottom_size - global_config.tabs_visible; if (env->offset + l < env->line_count + 1) { redraw_line(env->offset + l-1); } else { draw_excess_line(l - 1); } } else { redraw_text(); } redraw_statusbar(); redraw_commandline(); place_cursor_actual(); return; } else if (redraw) { /* Otherwise, if we need to redraw because of coffset change, do that */ redraw_text(); } set_history_break(); /* Update the status bar */ redraw_statusbar(); /* Place the terminal cursor again */ place_cursor_actual(); } } BIM_ACTION(cursor_up, 0, "Move the cursor up one line." ,void) { /* If this isn't the first line already */ if (env->line_no > 1) { /* Move the cursor down */ env->line_no -= 1; /* Try to place the cursor horizontally at the preferred column */ int _x = 0; for (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) { char_t * c = &env->lines[env->line_no-1]->text[i]; _x += c->display_width; env->col_no = i+1; if (_x > env->preferred_column) { break; } } if (env->mode == MODE_INSERT && _x <= env->preferred_column) { env->col_no = env->lines[env->line_no-1]->actual + 1; } /* * If the horizontal cursor position exceeds the width the new line, * then move the cursor left to the extent of the new line. * * If we're in insert mode, we can go one cell beyond the end of the line */ if (env->col_no > env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT)) { env->col_no = env->lines[env->line_no-1]->actual + (env->mode == MODE_INSERT); if (env->col_no == 0) env->col_no = 1; } if (env->loading) return; /* * If the screen was scrolled horizontally, unscroll it; * if it will be scrolled on this line as well, that will * be handled by place_cursor_actual */ int redraw = 0; if (env->coffset != 0) { env->coffset = 0; redraw = 1; } int e = (env->offset == 0) ? env->offset : env->offset + global_config.cursor_padding; if (env->line_no <= e) { env->offset -= 1; /* Tell terminal to scroll */ if (global_config.can_scroll && !left_buffer) { if (!global_config.can_insert) { shift_down(1); redraw_tabbar(); } else { insert_lines_at(global_config.tabs_visible ? 2 : 1, 1); } /* * The line at the top of the screen should always be real * so we can just call redraw_line here */ redraw_line(env->offset); } else { redraw_tabbar(); redraw_text(); } redraw_statusbar(); redraw_commandline(); place_cursor_actual(); return; } else if (redraw) { /* Otherwise, if we need to redraw because of coffset change, do that */ redraw_text(); } set_history_break(); /* Update the status bar */ redraw_statusbar(); /* Place the terminal cursor again */ place_cursor_actual(); } } BIM_ACTION(cursor_left, 0, "Move the cursor one character to the left." ,void) { if (env->col_no > 1) { env->col_no -= 1; /* Update the status bar */ redraw_statusbar(); /* Place the terminal cursor again */ place_cursor_actual(); } set_history_break(); set_preferred_column(); } BIM_ACTION(cursor_right, 0, "Move the cursor one character to the right." ,void) { /* If this isn't already the rightmost column we can reach on this line in this mode... */ if (env->col_no < env->lines[env->line_no-1]->actual + !!(env->mode == MODE_INSERT)) { env->col_no += 1; /* Update the status bar */ redraw_statusbar(); /* Place the terminal cursor again */ place_cursor_actual(); } set_history_break(); set_preferred_column(); } BIM_ACTION(cursor_home, 0, "Move the cursor to the beginning of the line." ,void) { env->col_no = 1; set_history_break(); set_preferred_column(); /* Update the status bar */ redraw_statusbar(); /* Place the terminal cursor again */ place_cursor_actual(); } BIM_ACTION(cursor_end, 0, "Move the cursor to the end of the line, or past the end in insert mode." ,void) { env->col_no = env->lines[env->line_no-1]->actual+!!(env->mode == MODE_INSERT); set_history_break(); set_preferred_column(); /* Update the status bar */ redraw_statusbar(); /* Place the terminal cursor again */ place_cursor_actual(); } BIM_ACTION(leave_insert, 0, "Leave insert modes and return to normal mode." ,void) { if (env->col_no > env->lines[env->line_no-1]->actual) { env->col_no = env->lines[env->line_no-1]->actual; if (env->col_no == 0) env->col_no = 1; set_preferred_column(); } set_history_break(); env->mode = MODE_NORMAL; redraw_commandline(); } struct MatchQualifier { int (*matchFunc)(struct MatchQualifier*,uint32_t,int); union { uint32_t matchChar; struct { uint32_t * start; uint32_t * end; } matchSquares; }; }; /** * Helper for handling smart case sensitivity. */ int match_char(struct MatchQualifier * self, uint32_t b, int mode) { if (mode == 0) { return self->matchChar == b; } else if (mode == 1) { return tolower(self->matchChar) == tolower(b); } return 0; } int match_squares(struct MatchQualifier * self, uint32_t c, int mode) { uint32_t * start = self->matchSquares.start; uint32_t * end = self->matchSquares.end; uint32_t * t = start; int good = 1; if (*t == '^') { t++; good = 0; } while (t != end) { uint32_t test = *t++; if (test == '\\' && *t && strchr("\\]",*t)) { test = *t++; } else if (test == '\\' && *t == 't') { test = '\t'; t++; } if (*t == '-') { t++; if (t == end) return 0; uint32_t right = *t++; if (right == '\\' && *t && strchr("\\]",*t)) { right = *t++; } else if (right == '\\' && *t == 't') { right = '\t'; t++; } if (mode ? (tolower(c) >= tolower(test) && tolower(c) <= tolower(right)) : (c >= test && c <= right)) return good; } else { if (mode ? (tolower(c) == tolower(test)) : (c == test)) return good; } } return !good; } int match_dot(struct MatchQualifier * self, uint32_t c, int mode) { return 1; } struct BackRef { int start; int len; uint32_t * copy; }; #define MAX_REFS 10 int regex_matches(line_t * line, int j, uint32_t * needle, int ignorecase, int *len, uint32_t **needleout, int refindex, struct BackRef * refs) { int k = j; uint32_t * match = needle; if (*match == '^') { if (j != 0) return 0; match++; } while (k < line->actual + 1) { if (needleout && *match == ')') { *needleout = match + 1; if (len) *len = k - j; return 1; } if (*match == '\0') { if (needleout) return 0; if (len) *len = k - j; return 1; } if (*match == '$') { if (k != line->actual) return 0; match++; continue; } if (k == line->actual) break; struct MatchQualifier matcher = {match_char, .matchChar=*match}; if (*match == '.') { matcher.matchFunc = match_dot; match++; } else if (*match == '\\' && strchr("$^/\\.[?]*+()",match[1]) != NULL) { matcher.matchChar = match[1]; match += 2; } else if (*match == '\\' && match[1] == 't') { matcher.matchChar = '\t'; match += 2; } else if (*match == '[') { uint32_t * s = match+1; uint32_t * e = s; while (*e && *e != ']') { if (*e == '\\' && e[1] == ']') e++; e++; } if (!*e) break; /* fail match on unterminated [] sequence */ match = e + 1; matcher.matchFunc = match_squares; matcher.matchSquares.start = s; matcher.matchSquares.end = e; } else if (*match == '(') { match++; int _len; uint32_t * newmatch; if (!regex_matches(line, k, match, ignorecase, &_len, &newmatch, 0, NULL)) break; match = newmatch; if (refindex && refindex < MAX_REFS) { refs[refindex].start = k; refs[refindex].len = _len; refindex++; } k += _len; continue; } else { match++; } if (*match == '?') { /* Optional */ match++; if (matcher.matchFunc(&matcher, line->text[k].codepoint, ignorecase)) { int _len; if (regex_matches(line,k+1,match,ignorecase,&_len, needleout, refindex, refs)) { if (len) *len = _len + k + 1 - j; return 1; } } continue; } else if (*match == '+' || *match == '*') { /* Must match at least one */ if (*match == '+') { if (!matcher.matchFunc(&matcher, line->text[k].codepoint, ignorecase)) break; k++; } /* Match any */ match++; int greedy = 1; if (*match == '?') { /* non-greedy */ match++; greedy = 0; } int _j = k; while (_j < line->actual + 1) { int _len; if (!greedy && regex_matches(line, _j, match, ignorecase, &_len, needleout, refindex, refs)) { if (len) *len = _len + _j - j; return 1; } if (_j < line->actual && !matcher.matchFunc(&matcher, line->text[_j].codepoint, ignorecase)) break; _j++; } if (!greedy) return 0; while (_j >= k) { int _len; if (regex_matches(line, _j, match, ignorecase, &_len, needleout, refindex, refs)) { if (len) *len = _len + _j - j; return 1; } _j--; } return 0; } else { if (!matcher.matchFunc(&matcher, line->text[k].codepoint, ignorecase)) break; k++; } } return 0; } int subsearch_matches(line_t * line, int j, uint32_t * needle, int ignorecase, int *len) { return regex_matches(line, j, needle, ignorecase, len, NULL, 0, NULL); } /** * Replace text on a given line with other text. */ void perform_replacement(int line_no, uint32_t * needle, uint32_t * replacement, int col, int ignorecase, int *out_col, int *line_out) { line_t * line = env->lines[line_no-1]; int j = col; while (j < line->actual + 1) { int match_len; struct BackRef refs[MAX_REFS] = {0}; if (regex_matches(line,j,needle,ignorecase,&match_len,NULL,1,refs)) { refs[0].start = j; refs[0].len = match_len; for (int i = 0; i < MAX_REFS; ++i) { refs[i].copy = malloc(sizeof(uint32_t) * refs[i].len); for (int j = 0; j < refs[i].len; ++j) { refs[i].copy[j] = line->text[j+refs[i].start].codepoint; } } /* Perform replacement */ for (int i = 0; i < match_len; ++i) { line_delete(line, j+1, line_no-1); } int t = 0; for (uint32_t * r = replacement; *r; ++r) { uint32_t rep = *r; char_t _c; _c.flags = 0; line_t * nline = line; if (*r == '\\' && r[1] == 't') { rep = '\t'; ++r; } else if (*r == '\\' && (r[1] == '\\')) { rep = r[1]; ++r; } else if (*r == '\\' && (r[1] >= '0' && r[1] <= '9')) { int i = r[1] - '0'; ++r; nline = line; for (int k = 0; k < refs[i].len; ++k) { _c.codepoint = refs[i].copy[k]; _c.display_width = codepoint_width(refs[i].copy[k]); nline = line_insert(nline, _c, j + t + k, line_no -1); } t += refs[i].len; rep = 0; } else if (*r == '\\' && (r[1] == 'n')) { ++r; env->lines = split_line(env->lines, line_no - 1, j + t); line_no++; line = env->lines[line_no-1]; j = 0; t = 0; continue; } if (rep) { _c.codepoint = rep; _c.display_width = codepoint_width(rep); nline = line_insert(nline, _c, j + t, line_no -1); t++; } if (line != nline) { env->lines[line_no-1] = nline; line = nline; } } *out_col = j + t; *line_out = line_no; set_modified(); for (int i = 0; i < MAX_REFS; ++i) { free(refs[i].copy); } return; } j++; } *out_col = -1; } #define COMMAND_HISTORY_MAX 255 unsigned char * command_history[COMMAND_HISTORY_MAX] = {NULL}; unsigned char * search_history[COMMAND_HISTORY_MAX] = {NULL}; /** * Add a command to the history. If that command was * already in history, it is moved to the front of the list; * otherwise, the whole list is shifted backwards and * overflow is freed up. */ void insert_command_history(unsigned char ** which_history, char * cmd) { /* See if this is already in the history. */ size_t amount_to_shift = COMMAND_HISTORY_MAX - 1; for (int i = 0; i < COMMAND_HISTORY_MAX && which_history[i]; ++i) { if (!strcmp((char*)which_history[i], cmd)) { free(which_history[i]); amount_to_shift = i; break; } } /* Remove last entry that will roll off the stack */ if (amount_to_shift == COMMAND_HISTORY_MAX - 1) { if (which_history[COMMAND_HISTORY_MAX-1]) free(which_history[COMMAND_HISTORY_MAX-1]); } /* Roll the history */ memmove(&which_history[1], &which_history[0], sizeof(char *) * (amount_to_shift)); which_history[0] = (unsigned char*)strdup(cmd); } static uint32_t term_colors[] = { 0x000000, 0xcc0000, 0x3e9a06, 0xc4a000, 0x3465a4, 0x75507b, 0x06989a, 0xeeeeec, 0x555753, 0xef2929, 0x8ae234, 0xfce94f, 0x729fcf, 0xad7fa8, 0x34e2e2, 0xFFFFFF, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee, }; /** * Convert a color setting from terminal format * to a hexadecimal color code and add it to the current * buffer. This is used for HTML conversion, but could * possibly be used for other purposes. */ static void html_convert_color(const char * color_string) { char tmp[100]; if (!strncmp(color_string,"2;",2)) { /* 24-bit color */ int red, green, blue; sscanf(color_string+2,"%d;%d;%d",&red,&green,&blue); sprintf(tmp, "#%02x%02x%02x;", red,green,blue); } else if (!strncmp(color_string,"5;",2)) { /* 256 colors; needs lookup table */ int index; sscanf(color_string+2,"%d",&index); sprintf(tmp,"#%06x;", (unsigned int)term_colors[(index >= 0 && index <= 255) ? index : 0]); } else { /* 16 colors; needs lookup table */ int index; uint32_t color; sscanf(color_string+1,"%d",&index); if (index >= 10) { index -= 10; color = term_colors[index+8]; } else if (index == 9) { color = term_colors[0]; } else { color = term_colors[index]; } sprintf(tmp,"#%06x;", (unsigned int)color); } add_string(tmp); char * italic = strstr(color_string,";3"); if (italic && italic[2] == '\0') { add_string(" font-style: oblique;"); } char * bold = strstr(color_string,";1"); if (bold && bold[2] == '\0') { add_string(" font-weight: bold;"); } char * underline = strstr(color_string,";4"); if (underline && underline[2] == '\0') { add_string(" font-decoration: underline;"); } } int convert_to_html(void) { buffer_t * old = env; env = buffer_new(); setup_buffer(env); env->loading = 1; add_string("\n"); add_string("\n"); add_string(" \n"); add_string(" \n"); if (old->file_name) { add_string(" "); add_string(file_basename(old->file_name)); add_string("\n"); } add_string(" \n"); add_string(" \n"); add_string("
\n");

	for (int i = 0; i < old->line_count; ++i) {
		char tmp[100];
		sprintf(tmp, "", i+1, i+1);
		add_string(tmp);
		int last_flag = -1;
		int opened = 0;
		int all_spaces = 1;
		for (int j = 0; j < old->lines[i]->actual; ++j) {
			char_t c = old->lines[i]->text[j];

			if (c.codepoint != ' ') all_spaces = 0;

			if (last_flag == -1 || last_flag != (c.flags & 0x1F)) {
				if (opened) add_string("");
				opened = 1;
				char tmp[100];
				sprintf(tmp, "",
					c.flags & FLAG_MASK_COLORS,
					(c.flags & FLAG_UNDERLINE) ? " ul" : "");
				add_string(tmp);
				last_flag = (c.flags & 0x1F);
			}

			if (c.codepoint == '<') {
				add_string("<");
			} else if (c.codepoint == '>') {
				add_string(">");
			} else if (c.codepoint == '&') {
				add_string("&");
			} else if (c.codepoint == '\t') {
				char tmp[100];
				sprintf(tmp, "	",c.display_width);
				add_string(tmp);
			} else if (j > 0 && c.codepoint == ' ' && all_spaces && !(j % old->tabstop)) {
				add_string(" ");
			} else {
				char tmp[7] = {0}; /* Max six bytes, use 7 to ensure last is always nil */
				to_eight(c.codepoint, tmp);
				add_string(tmp);
			}
		}
		if (opened) {
			add_string("");
		} else {
			add_string("");
		}
		add_string("\n");
	}

	add_string("
\n"); add_string("\n"); env->loading = 0; env->modified = 1; if (old->file_name) { char * base = file_basename(old->file_name); char * tmp = malloc(strlen(base) + 5); *tmp = '\0'; strcat(tmp, base); strcat(tmp, ".htm"); env->file_name = tmp; } for (int i = 0; i < env->line_count; ++i) { recalculate_tabs(env->lines[i]); } env->syntax = match_syntax(".htm"); schedule_complete_recalc(); return 0; } /** * Based on vim's :TOhtml * Convert syntax-highlighted buffer contents to HTML. */ BIM_COMMAND(tohtml,"tohtml","Convert the document to an HTML representation with syntax highlighting.") { convert_to_html(); redraw_all(); return 0; } BIM_ALIAS("TOhtml",tohtml,tohtml) int _prefix_command_run_script(char * cmd) { if (env->mode == MODE_LINE_SELECTION) { int range_top, range_bot; range_top = env->start_line < env->line_no ? env->start_line : env->line_no; range_bot = env->start_line < env->line_no ? env->line_no : env->start_line; int in[2]; pipe(in); int out[2]; pipe(out); int child = fork(); /* Open child process and set up pipes */ if (child == 0) { FILE * dev_null = fopen("/dev/null","w"); /* for stderr */ close(out[0]); close(in[1]); dup2(out[1], STDOUT_FILENO); dup2(in[0], STDIN_FILENO); dup2(fileno(dev_null), STDERR_FILENO); system(&cmd[1]); /* Yes we can just do this */ exit(1); } else if (child < 0) { render_error("Failed to fork"); return 1; } close(out[1]); close(in[0]); /* Write lines to child process */ FILE * f = fdopen(in[1],"w"); for (int i = range_top; i <= range_bot; ++i) { line_t * line = env->lines[i-1]; for (int j = 0; j < line->actual; j++) { char_t c = line->text[j]; if (c.codepoint == 0) { char buf[1] = {0}; fwrite(buf, 1, 1, f); } else { char tmp[8] = {0}; int i = to_eight(c.codepoint, tmp); fwrite(tmp, i, 1, f); } } fputc('\n', f); } fclose(f); close(in[1]); /* Read results from child process into a new buffer */ FILE * result = fdopen(out[0],"r"); buffer_t * old = env; env = buffer_new(); setup_buffer(env); env->loading = 1; uint8_t buf[BLOCK_SIZE]; state = 0; while (!feof(result) && !ferror(result)) { size_t r = fread(buf, 1, BLOCK_SIZE, result); add_buffer(buf, r); } if (env->line_no && env->lines[env->line_no-1] && env->lines[env->line_no-1]->actual == 0) { env->lines = remove_line(env->lines, env->line_no-1); } fclose(result); waitpid(-1,NULL,WNOHANG); env->loading = 0; /* Return to the original buffer and replace the selected lines with the output */ buffer_t * new = env; env = old; for (int i = range_top; i <= range_bot; ++i) { /* Remove the existing lines */ env->lines = remove_line(env->lines, range_top-1); } for (int i = 0; i < new->line_count; ++i) { /* Add the new lines */ env->lines = add_line(env->lines, range_top + i - 1); replace_line(env->lines, range_top + i - 1, new->lines[i]); recalculate_tabs(env->lines[range_top+i-1]); } env->modified = 1; /* Close the temporary buffer */ buffer_close(new); } else { /* Reset and draw some line feeds */ reset(); printf("\n\n"); /* Set buffered for shell application */ set_buffered(); /* Call the shell and wait for completion */ system(&cmd[1]); /* Return to the editor, wait for user to press enter. */ set_unbuffered(); printf("\n\nPress ENTER to continue."); int c; while ((c = bim_getch(), c != ENTER_KEY && c != LINE_FEED)); /* Redraw the screen */ redraw_all(); } /* Done processing command */ return 0; } int replace_text(int range_top, int range_bot, char divider, char * needle) { char * c = needle; char * replacement = NULL; char * options = ""; while (*c) { if (*c == divider) { *c = '\0'; replacement = c + 1; break; } c++; } if (!replacement) { render_error("nothing to replace with"); return 1; } c = replacement; while (*c) { if (*c == divider) { *c = '\0'; options = c + 1; break; } c++; } int global = 0; int case_insensitive = 0; /* Parse options */ while (*options) { switch (*options) { case 'g': global = 1; break; case 'i': case_insensitive = 1; break; } options++; } uint32_t * needle_c = malloc(sizeof(uint32_t) * (strlen(needle) + 1)); uint32_t * replacement_c = malloc(sizeof(uint32_t) * (strlen(replacement) + 1)); { int i = 0; uint32_t c, state = 0; for (char * cin = needle; *cin; cin++) { if (!decode(&state, &c, *cin)) { needle_c[i] = c; i++; } else if (state == UTF8_REJECT) { state = 0; } } needle_c[i] = 0; i = 0; c = 0; state = 0; for (char * cin = replacement; *cin; cin++) { if (!decode(&state, &c, *cin)) { replacement_c[i] = c; i++; } else if (state == UTF8_REJECT) { state = 0; } } replacement_c[i] = 0; } int replacements = 0; for (int line = range_top; line <= range_bot; ++line) { int col = 0; while (col != -1) { int _line = line; perform_replacement(line, needle_c, replacement_c, col, case_insensitive, &col, &_line); if (col != -1) replacements++; if (_line > line) { range_bot += _line - line; line = _line; } if (!global) break; } } if (env->mode == MODE_LINE_SELECTION) { env->start_line = env->start_line < env->line_no ? range_top : range_bot; env->line_no = env->start_line < env->line_no ? range_bot : range_top; } free(needle_c); free(replacement_c); if (replacements) { render_status_message("replaced %d instance%s of %s", replacements, replacements == 1 ? "" : "s", needle); set_history_break(); redraw_text(); } else { render_error("Pattern not found: %s", needle); } return 0; } BIM_PREFIX_COMMAND(repsome,"s","Perform a replacement over selected lines") { int range_top, range_bot; if (env->mode == MODE_LINE_SELECTION) { range_top = env->start_line < env->line_no ? env->start_line : env->line_no; range_bot = env->start_line < env->line_no ? env->line_no : env->start_line; } else { range_top = env->line_no; range_bot = env->line_no; } return replace_text(range_top, range_bot, cmd[1], &cmd[2]); } BIM_PREFIX_COMMAND(repall,"%s","Perform a replacement over the entire file.") { return replace_text(1, env->line_count, cmd[2], &cmd[3]); } BIM_COMMAND(e,"e","Open a file") { if (argc > 1) { /* This actually opens a new tab */ open_file(argv[1]); update_title(); } else { if (env->modified) { render_error("File is modified, can not reload."); return 1; } if (!env->file_name) { render_error("No file name."); return 1; } buffer_t * old_env = env; open_file(env->file_name); buffer_t * new_env = env; env = old_env; #define SWAP(T,a,b) do { T x = a; a = b; b = x; } while (0) SWAP(line_t **, env->lines, new_env->lines); SWAP(int, env->line_count, new_env->line_count); SWAP(int, env->line_avail, new_env->line_avail); SWAP(history_t *, env->history, new_env->history); buffer_close(new_env); /* Should probably also free, this needs editing. */ schedule_complete_recalc(); redraw_all(); } return 0; } BIM_COMMAND(tabnew,"tabnew","Open a new tab") { if (argc > 1) { open_file(argv[1]); update_title(); } else { env = buffer_new(); setup_buffer(env); redraw_all(); update_title(); } return 0; } BIM_COMMAND(w,"w","Write a file") { /* w: write file */ if (argc > 1) { write_file(argv[1]); } else { write_file(env->file_name); } return 0; } BIM_COMMAND(wq,"wq","Write and close buffer") { write_file(env->file_name); close_buffer(); return 0; } BIM_COMMAND(history,"history","Display command history") { render_commandline_message(""); /* To clear command line */ for (int i = COMMAND_HISTORY_MAX; i > 1; --i) { if (command_history[i-1]) render_commandline_message("%d:%s\n", i-1, command_history[i-1]); } render_commandline_message("\n"); redraw_tabbar(); redraw_commandline(); pause_for_key(); return 0; } BIM_COMMAND(q,"q","Close buffer") { if (left_buffer && left_buffer == right_buffer) { unsplit(); return 0; } if (env->modified) { render_error("No write since last change. Use :q! to force exit."); } else { close_buffer(); } update_title(); return 0; } BIM_COMMAND(qbang,"q!","Force close buffer") { close_buffer(); update_title(); return 0; } BIM_COMMAND(qa,"qa","Try to close all buffers") { try_quit(); return 0; } BIM_ALIAS("qall",qall,qa) BIM_COMMAND(qabang,"qa!","Force exit") { /* Forcefully exit editor */ while (buffers_len) { buffer_close(buffers[0]); } quit(NULL); return 1; /* doesn't return */ } BIM_COMMAND(tabp,"tabp","Previous tab") { previous_tab(); update_title(); return 0; } BIM_COMMAND(tabn,"tabn","Next tab") { next_tab(); update_title(); return 0; } BIM_COMMAND(tabm,"tabm","Move the current tab to a new index") { /* Figure out the current index */ int i = 0; for (; i < buffers_len; i++) { if (buffers[i] == env) break; } if (i == buffers_len) { render_status_message("(invalid state?)"); return 1; } if (argc < 2) { render_status_message("tab = %d", i); return 1; } int newIndex = atoi(argv[1]); if (newIndex == i) { return 0; } /* Okay, this is stupid, but, remove the buffer */ memmove(&buffers[i], &buffers[i+1], sizeof(*buffers) * (buffers_len - i -1)); /* Then make space at the destination */ memmove(&buffers[newIndex+1], &buffers[newIndex], sizeof(*buffers) * (buffers_len - newIndex -1)); buffers[newIndex] = env; redraw_tabbar(); update_title(); return 0; } BIM_COMMAND(tab,"tab", "Open a specific tab") { if (argc < 2) return bim_command_tabm("tabm", argc, argv); int i = atoi(argv[1]); if (i < 0 || i > buffers_len) { render_error("Invalid tab index"); return 1; } env = buffers[i]; if (left_buffer && (left_buffer != env && right_buffer != env)) unsplit(); redraw_all(); update_title(); return 0; } BIM_COMMAND(tabindicator,"tabindicator","Set the tab indicator") { if (argc < 2) { render_status_message("tabindicator=%s", global_config.tab_indicator); return 0; } if (!global_config.can_unicode && strlen(argv[1]) != 1) return 0; if (display_width_of_string(argv[1]) != 1) { render_error("Can't set '%s' as indicator, must be one cell wide.", argv[1]); return 1; } if (global_config.tab_indicator) free(global_config.tab_indicator); global_config.tab_indicator = strdup(argv[1]); return 0; } BIM_COMMAND(spaceindicator,"spaceindicator","Set the space indicator") { if (argc < 2) { render_status_message("spaceindicator=%s", global_config.space_indicator); return 0; } if (!global_config.can_unicode && strlen(argv[1]) != 1) return 0; if (display_width_of_string(argv[1]) != 1) { render_error("Can't set '%s' as indicator, must be one cell wide.", argv[1]); return 1; } if (global_config.space_indicator) free(global_config.space_indicator); global_config.space_indicator = strdup(argv[1]); return 0; } BIM_COMMAND(global_git,"global.git","Show or change the default status of git integration") { if (argc < 2) { render_status_message("global.git=%d", global_config.check_git); } else { global_config.check_git = !!atoi(argv[1]); } return 0; } BIM_COMMAND(git,"git","Show or change status of git integration") { if (!env) { render_error("requires environment (did you mean global.git?)"); return 1; } if (argc < 2) { render_status_message("git=%d", env->checkgitstatusonwrite); } else { env->checkgitstatusonwrite = !!atoi(argv[1]); if (env->checkgitstatusonwrite && !env->modified && env->file_name) { git_examine(env->file_name); redraw_text(); } } return 0; } BIM_COMMAND(colorgutter,"colorgutter","Show or change status of gutter colorization for unsaved modifications") { if (argc < 2) { render_status_message("colorgutter=%d", global_config.color_gutter); } else { global_config.color_gutter = !!atoi(argv[1]); redraw_text(); } return 0; } BIM_COMMAND(indent,"indent","Enable smart indentation") { env->indent = 1; redraw_statusbar(); return 0; } BIM_COMMAND(noindent,"noindent","Disable smart indentation") { env->indent = 0; redraw_statusbar(); return 0; } /* TODO: global.maxcolumn */ BIM_COMMAND(maxcolumn,"maxcolumn","Highlight past the given column to indicate maximum desired line length") { if (argc < 2) { render_status_message("maxcolumn=%d",env->maxcolumn); return 0; } env->maxcolumn = atoi(argv[1]); redraw_text(); return 0; } BIM_COMMAND(cursorcolumn,"cursorcolumn","Show the visual column offset of the cursor.") { render_status_message("cursorcolumn=%d", env->preferred_column); return 0; } BIM_COMMAND(noh,"noh","Clear search term") { if (global_config.search) { free(global_config.search); global_config.search = NULL; for (int i = 0; i < env->line_count; ++i) { for (int j = 0; j < env->lines[i]->actual; ++j) { env->lines[i]->text[j].flags &= ~(FLAG_SEARCH); } } redraw_text(); } return 0; } BIM_COMMAND(help,"help","Show help text.") { if (argc < 2) { render_commandline_message(""); /* To clear command line */ render_commandline_message("\n"); render_commandline_message(" \033[1mbim - a text editor \033[22m\n"); render_commandline_message("\n"); render_commandline_message(" Available commands:\n"); render_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"); render_commandline_message(" Write out with \033[3m:w \033[4mfile\033[24;23m\n"); render_commandline_message(" Set syntax with \033[3m:syntax \033[4mlanguage\033[24;23m\n"); render_commandline_message(" Open a new tab with \033[3m:e \033[4mpath/to/file\033[24;23m\n"); render_commandline_message(" \033[3m:tabn\033[23m and \033[3m:tabp\033[23m can be used to switch tabs\n"); render_commandline_message(" Set the color scheme with \033[3m:theme \033[4mtheme\033[24;23m\n"); render_commandline_message(" Set the behavior of the tab key with \033[3m:tabs\033[23m or \033[3m:spaces\033[23m\n"); render_commandline_message(" Set tabstop with \033[3m:tabstop \033[4mwidth\033[24;23m\n"); render_commandline_message("\n"); render_commandline_message(" Bim %s%s\n", BIM_VERSION, BIM_BUILD_DATE); render_commandline_message(" %s\n", BIM_COPYRIGHT); render_commandline_message("\n"); } else { int found = 0; for (struct command_def * c = regular_commands; !found && regular_commands && c->name; ++c) { if (!strcmp(c->name, argv[1])) { render_commandline_message(""); /* To clear command line */ render_commandline_message("Help description for `%s`:\n", c->name); render_commandline_message(" %s\n", c->description); found = 1; break; } } for (struct command_def * c = prefix_commands; !found && prefix_commands && c->name; ++c) { if (!strcmp(c->name, argv[1])) { render_commandline_message(""); /* To clear command line */ render_commandline_message("Help description for `%s`:\n", c->name); render_commandline_message(" %s\n", c->description); found = 1; break; } } if (!found) { render_error("Unknown command: %s", argv[1]); return 1; } } /* Redrawing the tabbar makes it look like we just shifted the whole view up */ redraw_tabbar(); redraw_commandline(); /* Wait for a character so we can redraw the screen before continuing */ pause_for_key(); return 0; } BIM_COMMAND(version,"version","Show version information.") { render_status_message("Bim %s%s", BIM_VERSION, BIM_BUILD_DATE); return 0; } BIM_COMMAND(theme,"theme","Set color theme") { if (argc < 2) { render_status_message("theme=%s", current_theme); } else { for (struct theme_def * d = themes; themes && d->name; ++d) { if (!strcmp(argv[1], d->name)) { ptrdiff_t before = krk_currentThread.stackTop - krk_currentThread.stack; krk_push(OBJECT_VAL(d->callable)); KrkValue result = krk_callStack(0); krk_currentThread.stackTop = krk_currentThread.stack + before; if (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { render_error("Exception occurred in theme: %s", AS_INSTANCE(krk_currentThread.currentException)->_class->name->chars); krk_dumpTraceback(); int key = 0; while ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); } current_theme = d->name; redraw_all(); return 0; } } } return 0; } BIM_ALIAS("colorscheme",colorscheme,theme) BIM_COMMAND(splitpercent,"splitpercent","Display or change view split") { if (argc < 2) { render_status_message("splitpercent=%d", global_config.split_percent); return 0; } else { global_config.split_percent = atoi(argv[1]); if (left_buffer) { update_split_size(); redraw_all(); } } return 0; } BIM_COMMAND(split,"split","Split the current view.") { buffer_t * original = env; if (argc > 1) { int is_not_number = 0; for (char * c = argv[1]; *c; ++c) is_not_number |= !isdigit(*c); if (is_not_number) { /* Open a file for the new split */ open_file(argv[1]); right_buffer = buffers[buffers_len-1]; } else { /* Use an existing buffer for the new split */ int other = atoi(argv[1]); if (other >= buffers_len || other < 0) { render_error("Invalid buffer number: %d", other); return 1; } right_buffer = buffers[other]; } } else { /* Use the current buffer for the new split */ right_buffer = original; } left_buffer = original; update_split_size(); redraw_all(); return 0; } BIM_COMMAND(unsplit,"unsplit","Show only one buffer on screen") { unsplit(); return 0; } BIM_COMMAND(horizontalscrolling,"horizontalscrolling","Set the horizontal scrolling mode") { if (argc < 2) { render_status_message("horizontalscrolling=%d", global_config.horizontal_shift_scrolling); return 0; } else { global_config.horizontal_shift_scrolling = !!atoi(argv[1]); redraw_all(); } return 0; } BIM_COMMAND(syntax,"syntax","Show or set the active syntax highlighter") { if (argc < 2) { render_status_message("syntax=%s", env->syntax ? env->syntax->name : "none"); } else { set_syntax_by_name(argv[1]); } return 0; } BIM_COMMAND(recalc,"recalc","Recalculate syntax for the entire file.") { schedule_complete_recalc(); redraw_all(); return 0; } BIM_COMMAND(tabstop,"tabstop","Show or set the tabstop (width of an indentation unit)") { if (argc < 2) { render_status_message("tabstop=%d", env->tabstop); } else { int t = atoi(argv[1]); if (t > 0 && t < 12) { env->tabstop = t; for (int i = 0; i < env->line_count; ++i) { recalculate_tabs(env->lines[i]); } redraw_all(); } else { render_error("Invalid tabstop: %s", argv[1]); } } return 0; } BIM_COMMAND(spaces,"spaces","Use spaces for indentation") { env->tabs = 0; if (argc > 1) { bim_command_tabstop("tabstop", argc, argv); } redraw_statusbar(); return 0; } BIM_COMMAND(tabs,"tabs","Use tabs for indentation") { env->tabs = 1; if (argc > 1) { bim_command_tabstop("tabstop", argc, argv); } redraw_statusbar(); return 0; } BIM_COMMAND(clearyear,"clearyank","Clear the yank buffer") { if (global_config.yanks) { for (unsigned int i = 0; i < global_config.yank_count; ++i) { free(global_config.yanks[i]); } free(global_config.yanks); global_config.yanks = NULL; global_config.yank_count = 0; redraw_statusbar(); } return 0; } BIM_COMMAND(padding,"padding","Show or set cursor padding when scrolling vertically") { if (argc < 2) { render_status_message("padding=%d", global_config.cursor_padding); } else { global_config.cursor_padding = atoi(argv[1]); if (env) { place_cursor_actual(); } } return 0; } BIM_COMMAND(smartcase,"smartcase","Show or set the status of the smartcase search option") { if (argc < 2) { render_status_message("smartcase=%d", global_config.smart_case); } else { global_config.smart_case = atoi(argv[1]); if (env) place_cursor_actual(); } return 0; } BIM_COMMAND(hlparen,"hlparen","Show or set the configuration option to highlight matching braces") { if (argc < 2) { render_status_message("hlparen=%d", global_config.highlight_parens); } else { global_config.highlight_parens = atoi(argv[1]); if (env) { for (int i = 0; i < env->line_count; ++i) { for (int j = 0; j < env->lines[i]->actual; ++j) { env->lines[i]->text[j].flags &= (~FLAG_SELECT); } } redraw_text(); place_cursor_actual(); } } return 0; } BIM_COMMAND(hlcurrent,"hlcurrent","Show or set the configuration option to highlight the current line") { if (argc < 2) { render_status_message("hlcurrent=%d", global_config.highlight_current_line); } else { global_config.highlight_current_line = atoi(argv[1]); if (env) { if (!global_config.highlight_current_line) { for (int i = 0; i < env->line_count; ++i) { env->lines[i]->is_current = 0; } } redraw_text(); place_cursor_actual(); } } return 0; } BIM_COMMAND(crnl,"crnl","Show or set the line ending mode") { if (argc < 2) { render_status_message("crnl=%d", env->crnl); } else { env->crnl = !!atoi(argv[1]); redraw_statusbar(); } return 0; } BIM_COMMAND(global_numbers,"global.numbers","Set whether numbers are displayed by default") { if (argc < 2) { render_status_message("global.numbers=%d", global_config.numbers); } else { global_config.numbers = !!atoi(argv[1]); redraw_all(); } return 0; } BIM_COMMAND(global_statusbar,"global.statusbar","Show or set whether to display the statusbar") { if (argc < 2) { render_status_message("global.statusbar=%d",!global_config.hide_statusbar); } else { global_config.hide_statusbar = !atoi(argv[1]); global_config.bottom_size = global_config.hide_statusbar ? 1 : 2; redraw_all(); } return 0; } BIM_COMMAND(global_scrollamount,"global.scrollamount","Show or set scroll amount when using mouse wheel") { if (argc < 2) { render_status_message("global.scrollamount=%d",global_config.scroll_amount); } else { global_config.scroll_amount = atoi(argv[1]); } return 0; } BIM_COMMAND(global_search_wraps,"wrapsearch","Enable search wrapping around from top or bottom") { if (argc < 2) { render_status_message("wrapsearch=%d",global_config.search_wraps); } else { global_config.search_wraps = !!atoi(argv[1]); } return 0; } BIM_COMMAND(smartcomplete,"smartcomplete","Enable autocompletion while typing") { if (argc < 2) { render_status_message("smartcomplete=%d",global_config.smart_complete); } else { global_config.smart_complete = !!atoi(argv[1]); } return 0; } BIM_COMMAND(global_autohide_tabs,"global.autohidetabs","Whether to show the tab bar when there is only one tab") { if (argc < 2) { render_status_message("global.autohidetabs=%d", global_config.autohide_tabs); } else { global_config.autohide_tabs = !!atoi(argv[1]); global_config.tabs_visible = (!global_config.autohide_tabs) || (buffers_len > 1); redraw_all(); } return 0; } BIM_COMMAND(numbers,"numbers","Show or set the display of line numbers") { if (argc < 2) { render_status_message("numbers=%d", env->numbers); } else { env->numbers = !!atoi(argv[1]); redraw_all(); } return 0; } BIM_COMMAND(relativenumbers,"relativenumbers","Show or set the display of relative line numbers") { if (argc < 2) { render_status_message("relativenumber=%d", global_config.relative_lines); } else { global_config.relative_lines = atoi(argv[1]); if (env) { if (!global_config.relative_lines) { for (int i = 0; i < env->line_count; ++i) { env->lines[i]->is_current = 0; } } redraw_text(); place_cursor_actual(); } } return 0; } BIM_COMMAND(buffers,"buffers","Show the open buffers") { for (int i = 0; i < buffers_len; ++i) { render_commandline_message("%d: %s\n", i, buffers[i]->file_name ? buffers[i]->file_name : "(no name)"); } redraw_tabbar(); redraw_commandline(); pause_for_key(); return 0; } BIM_COMMAND(keyname,"keyname","Press and key and get its name.") { int c; render_commandline_message("(press a key)"); while ((c = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); render_commandline_message("%d = %s", c, name_from_key(c)); return 0; } int isSubstitutionSymbol(int c) { if (c >= '!' && c <= '/') return 1; if (c >= ':' && c <= '@') return 1; if (c >= '[' && c <= '`') return 1; if (c >= '{' && c <= '~') return 1; return 0; } int alldigits(const char * c) { while (*c) { if (!isdigit(*c)) return 0; c++; } return 1; } /** * Process a user command. */ int process_krk_command(const char * cmd, KrkValue * outVal); int process_command(char * cmd) { if (cmd[0] == '-' && alldigits(&cmd[1])) { goto_line(env->line_no-atoi(&cmd[1])); return 0; } else if (cmd[0] == '+' && alldigits(&cmd[1])) { goto_line(env->line_no+atoi(&cmd[1])); return 0; } else if (alldigits(cmd)) { goto_line(atoi(cmd)); return 0; } else if (cmd[0] == '!') { return _prefix_command_run_script(cmd); } else if (cmd[0] == 's' && isSubstitutionSymbol(cmd[1])) { return bim_command_repsome(cmd, 0, NULL); } else if (cmd[0] == '%' && cmd[1] == 's') { return bim_command_repall(cmd, 0, NULL); } /* See if it's a bim command in the classic format */ { char * argv[3] = {NULL, NULL, NULL}; int argc = !!(*cmd); char cmd_name[512] = {0}; for (char * c = (char*)cmd; *c; ++c) { if (c-cmd == 511) break; if (*c == ' ') { cmd_name[c-cmd] = '\0'; while (*c == ' ') c++; argv[1] = c; if (*argv[1]) argc++; break; } cmd_name[c-cmd] = *c; } argv[0] = cmd_name; argv[argc] = NULL; for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { if (!strcmp(argv[0], c->name)) { krk_resetStack(); return c->command((char*)cmd, argc, argv); } } } int retval = process_krk_command(cmd, NULL); return retval; } struct Candidate { char * text; int type; }; static int biased_strcmp(const char *l, const char *r) { for (; *l == *r && *l; l++, r++); /* Treat / like \0 */ if (*l == '/') return -1; if (*r == '/') return 1; return *(unsigned char *)l - *(unsigned char *)r; } /** * Wrap strcmp for use with qsort. */ int compare_candidate(const void * a, const void * b) { const struct Candidate *_a = a; const struct Candidate *_b = b; return biased_strcmp(_a->text, _b->text); } /** * List of file extensions to ignore when tab completing. * TODO this should be configurable */ const char * tab_complete_ignore[] = {".o",".lo",NULL}; /** * Wrapper around krk_valueGetAttribute... */ static KrkValue findFromProperty(KrkValue current, KrkToken next) { KrkValue member = OBJECT_VAL(krk_copyString(next.start, next.literalWidth)); krk_push(member); KrkValue value = krk_valueGetAttribute_default(current, AS_CSTRING(member), NONE_VAL()); krk_pop(); return value; } /** * Macros for use in command mode. */ #define _syn_command() do { env->syntax = global_config.command_syn; } while (0) #define _syn_restore() do { env->syntax = global_config.command_syn_back; } while (0) /* Forward declarations because I don't want to move these. */ extern void eat_mouse(void); extern void eat_mouse_sgr(void); /** * Clear everything before the cursor in the command in put buffer. * Generally part of serialization/deserialization when moving things * into and out of the command buffer for tab completion. */ static void command_buffer_clear_before(void) { _syn_command(); while (global_config.command_col_no > 1) { line_delete(global_config.command_buffer, global_config.command_col_no - 1, -1); global_config.command_col_no--; } _syn_restore(); } /** * Serialize the command buffer, up to the cursor, to a utf8 string, * and then delete that portion of the buffer. */ static char * command_buffer_serialize(void) { struct StringBuilder sb = {0}; for (int i = 0; i < global_config.command_col_no-1; ++i) { char buf[10]; size_t t = to_eight(global_config.command_buffer->text[i].codepoint, buf); krk_pushStringBuilderStr(&sb, buf, t); } krk_pushStringBuilder(&sb, '\0'); command_buffer_clear_before(); return sb.bytes; } /** * Fill the command buffer, from the cursor, with a string. */ static void command_buffer_deserialize(char * tmp) { uint32_t state = 0, c= 0; char * t = tmp; _syn_command(); while (*t) { if (!decode(&state, &c, *t)) { char_t _c = {codepoint_width(c), 0, c}; global_config.command_buffer = line_insert(global_config.command_buffer, _c, global_config.command_col_no - 1, -1); global_config.command_col_no++; } t++; } _syn_restore(); free(tmp); } /** * Tab completion for command mode. */ char * command_tab_complete(char * buffer) { /* Figure out which argument this is and where it starts */ int arg = 0; char * buf = strdup(buffer); char * b = buf; int args_count = 0; int args_space = 4; char ** args = malloc(sizeof(char*)*args_space); #define add_arg(argument) \ do { \ if (args_count == args_space) { \ args_space *= 2; \ args = realloc(args, sizeof(char*) * args_space); \ } \ args[args_count++] = argument; \ } while (0) int candidate_count= 0; int candidate_space = 4; struct Candidate * candidates = malloc(sizeof(struct Candidate)*candidate_space); /* Accept whitespace before first argument */ while (*b == ' ') b++; char * start = b; add_arg(start); while (*b && *b != ' ') b++; while (*b) { while (*b == ' ') { *b = '\0'; b++; } start = b; arg++; add_arg(start); break; } /** * Check a possible candidate and add it to the * candidates list, expanding as necessary, * if it matches for the current argument. */ #define add_candidate(candidate,candtype) \ do { \ char * _arg = args[arg]; \ int r = strncmp(_arg, candidate, strlen(_arg)); \ if (!r) { \ int skip = 0; \ for (int i = 0; i < candidate_count; ++i) { \ if (!strcmp(candidates[i].text,candidate)) { skip = 1; break; } \ } \ if (skip) break; \ if (candidate_count == candidate_space) { \ candidate_space *= 2; \ candidates = realloc(candidates,sizeof(struct Candidate) * candidate_space); \ } \ candidates[candidate_count].text = strdup(candidate); \ candidates[candidate_count].type = candtype; \ candidate_count++; \ } \ } while (0) #define Candidate_Normal 0 #define Candidate_Command 1 #define Candidate_Builtin 2 int _candidates_are_files = 0; if (arg == 0 || (arg == 1 && !strcmp(args[0], "help"))) { /* Complete command names */ for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { add_candidate(c->name,Candidate_Command); } for (struct command_def * c = prefix_commands; prefix_commands && c->name; ++c) { add_candidate(c->name,Candidate_Command); } goto _try_kuroko; } if (arg == 1 && !strcmp(args[0], "syntax")) { /* Complete syntax options */ add_candidate("none", Candidate_Builtin); for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { add_candidate(s->name, Candidate_Builtin); } goto _accept_candidate; } if (arg == 1 && (!strcmp(args[0], "theme") || !strcmp(args[0], "colorscheme"))) { /* Complete color theme names */ for (struct theme_def * s = themes; themes && s->name; ++s) { add_candidate(s->name, Candidate_Builtin); } goto _accept_candidate; } if (arg == 1 && (!strcmp(args[0], "setcolor"))) { for (struct ColorName * c = color_names; c->name; ++c) { add_candidate(c->name, Candidate_Builtin); } goto _accept_candidate; } if (arg == 1 && (!strcmp(args[0], "action"))) { for (struct action_def * a = mappable_actions; a->name; ++a) { add_candidate(a->name, Candidate_Builtin); } goto _accept_candidate; } if (arg == 1 && (!strcmp(args[0], "mapkey"))) { for (int i = 0; args[arg][i]; ++i) { if (args[arg][i] == ' ') { while (args[arg][i] == ' ') { args[arg][i] = '\0'; i++; } start = &args[arg][i]; arg++; add_arg(start); i = 0; } } if (arg == 1) { for (struct mode_names * m = mode_names; m->name; ++m) { add_candidate(m->name, Candidate_Builtin); } } else if (arg == 2) { for (unsigned int i = 0; i < sizeof(KeyNames)/sizeof(KeyNames[0]); ++i) { add_candidate(KeyNames[i].name, Candidate_Builtin); } } else if (arg == 3) { for (struct action_def * a = mappable_actions; a->name; ++a) { add_candidate(a->name, Candidate_Builtin); } add_candidate("none", Candidate_Builtin); } else if (arg == 4) { for (char * c = "racnwmb"; *c; ++c) { char tmp[] = {*c,'\0'}; add_candidate(tmp, Candidate_Builtin); } } goto _accept_candidate; } if (arg == 1 && (!strcmp(args[0], "e") || !strcmp(args[0], "tabnew") || !strcmp(args[0],"split") || !strcmp(args[0],"w") || !strcmp(args[0],"runscript") || !strcmp(args[0],"rundir") || args[0][0] == '!')) { /* Complete file paths */ /* First, find the deepest directory match */ char * tmp = strdup(args[arg]); char * last_slash = strrchr(tmp, '/'); DIR * dirp; if (last_slash) { *last_slash = '\0'; if (last_slash == tmp) { /* Started with slash, and it was the only slash */ dirp = opendir("/"); } else { char * home; if (*tmp == '~' && (home = getenv("HOME"))) { char * t = malloc(strlen(tmp) + strlen(home) + 4); sprintf(t, "%s%s",home,tmp+1); dirp = opendir(t); free(t); } else { dirp = opendir(tmp); } } } else { /* No directory match, completing from current directory */ dirp = opendir("."); tmp[0] = '\0'; } if (!dirp) { /* Directory match doesn't exist, no candidates to populate */ free(tmp); goto done; } _candidates_are_files = 1; struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] != '.' || (last_slash ? (last_slash[1] == '.') : (tmp[0] == '.'))) { struct stat statbuf; /* Figure out if this file is a directory */ if (last_slash) { char * x; char * home; if (tmp[0] == '~' && (home = getenv("HOME"))) { x = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1 + strlen(home) + 1); snprintf(x, strlen(tmp) + 1 + strlen(ent->d_name) + 1 + strlen(home) + 1, "%s%s/%s",home,tmp+1,ent->d_name); } else { x = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1); snprintf(x, strlen(tmp) + 1 + strlen(ent->d_name) + 1, "%s/%s",tmp,ent->d_name); } stat(x, &statbuf); free(x); } else { stat(ent->d_name, &statbuf); } /* Build the complete argument name to tab complete */ int type = Candidate_Normal; char s[1024] = {0}; if (last_slash == tmp) { strcat(s,"/"); } else if (*tmp) { strcat(s,tmp); strcat(s,"/"); } strcat(s,ent->d_name); /* * If it is a directory, add a / to the end so the next completion * attempt will complete the directory's contents. */ if (S_ISDIR(statbuf.st_mode)) { strcat(s,"/"); type = Candidate_Command; } int skip = 0; for (const char ** c = tab_complete_ignore; *c; ++c) { if (str_ends_with(s, *c)) { skip = 1; break; } } if (!skip) { add_candidate(s, type); } } ent = readdir(dirp); } closedir(dirp); free(tmp); goto _accept_candidate; } /* Hacky port of the kuroko repl completer */ _try_kuroko: { KrkScanner scanner = krk_initScanner(buffer); KrkToken * space = malloc(sizeof(KrkToken) * (strlen(buffer) + 2)); int count = 0; do { space[count++] = krk_scanToken(&scanner); } while (space[count-1].type != TOKEN_EOF && space[count-1].type != TOKEN_ERROR); if (count == 1) { goto _cleanup; } int base = 2; int n = base; if (space[count-base].type == TOKEN_DOT) { /* Dots we need to look back at the previous tokens for */ n--; base--; } else if (space[count-base].type >= TOKEN_IDENTIFIER && space[count-base].type <= TOKEN_WITH) { /* Something alphanumeric; only for the last element */ } else { /* Some other symbol */ goto _cleanup; } while (n < count) { if (space[count-n-1].type != TOKEN_DOT) break; n++; if (n == count) break; if (space[count-n-1].type != TOKEN_IDENTIFIER) break; n++; } if (n <= count) { /* Now work forwards, starting from the current globals. */ KrkValue root = OBJECT_VAL(krk_currentThread.module); int isGlobal = 1; while (n > base) { /* And look at the potential fields for instances/classes */ KrkValue next = findFromProperty(root, space[count-n]); if (IS_NONE(next)) { /* If we hit None, we found something invalid (or literally hit a None * object, but really the difference is minimal in this case: Nothing * useful to tab complete from here. */ if (!isGlobal) goto _cleanup; /* Does this match a builtin? */ if (!krk_tableGet_fast(&vm.builtins->fields, krk_copyString(space[count-n].start,space[count-n].literalWidth), &next) || IS_NONE(next)) { goto _cleanup; } } isGlobal = 0; root = next; n -= 2; /* To skip every other dot. */ } if (isGlobal && n < count && (space[count-n-1].type == TOKEN_IMPORT || space[count-n-1].type == TOKEN_FROM)) { KrkInstance * modules = krk_newInstance(vm.baseClasses->objectClass); root = OBJECT_VAL(modules); krk_push(root); for (size_t i = 0; i < vm.modules.capacity; ++i) { KrkTableEntry * entry = &vm.modules.entries[i]; if (IS_KWARGS(entry->key)) continue; krk_attachNamedValue(&modules->fields, AS_CSTRING(entry->key), NONE_VAL()); } } /* Now figure out what we're completing - did we already have a partial symbol name? */ int length = (space[count-base].type == TOKEN_DOT) ? 0 : (space[count-base].length); isGlobal = isGlobal && (length != 0); /* Take the last symbol name from the chain and get its member list from dir() */ static char * syn_krk_keywords[] = { "and","class","def","else","for","if","in","import","del", "let","not","or","return","while","try","except","raise", "continue","break","as","from","elif","lambda","with","is", "pass","assert","yield","finally","async","await", NULL }; KrkInstance * fakeKeywordsObject = NULL; for (;;) { KrkValue dirList = krk_dirObject(1,(KrkValue[]){root},0); krk_push(dirList); if (!IS_INSTANCE(dirList)) { render_error("Internal error while tab completing."); goto _cleanup; } for (size_t i = 0; i < AS_LIST(dirList)->count; ++i) { KrkString * s = AS_STRING(AS_LIST(dirList)->values[i]); krk_push(OBJECT_VAL(s)); KrkToken asToken = {.start = s->chars, .literalWidth = s->length}; KrkValue thisValue = findFromProperty(root, asToken); krk_push(thisValue); if (IS_CLOSURE(thisValue) || IS_BOUND_METHOD(thisValue) || IS_NATIVE(thisValue)) { size_t allocSize = s->length + 2; char * tmp = malloc(allocSize); size_t len = snprintf(tmp, allocSize, "%s(", s->chars); s = krk_takeString(tmp, len); krk_pop(); krk_push(OBJECT_VAL(s)); } /* If this symbol is shorter than the current submatch, skip it. */ if (length && (int)s->length < length) continue; if (!memcmp(s->chars, space[count-base].start, length)) { char * tmp = malloc(strlen(args[arg]) + s->length + 1); sprintf(tmp,"%s%s", args[arg], s->chars + length); int type = Candidate_Normal; if (IS_OBJECT(root) && AS_OBJECT(root) == (KrkObj*)vm.builtins) { type = Candidate_Builtin; } else if (IS_OBJECT(root) && AS_OBJECT(root) == (KrkObj*)fakeKeywordsObject) { type = Candidate_Command; } add_candidate(tmp, type); free(tmp); } } /* * If the object we were scanning was the current module, * then we should also throw the builtins into the ring. */ if (isGlobal && AS_OBJECT(root) == (KrkObj*)krk_currentThread.module) { root = OBJECT_VAL(vm.builtins); continue; } else if (isGlobal && AS_OBJECT(root) == (KrkObj*)vm.builtins) { fakeKeywordsObject = krk_newInstance(vm.baseClasses->objectClass); root = OBJECT_VAL(fakeKeywordsObject); krk_push(root); for (char ** keyword = syn_krk_keywords; *keyword; keyword++) { krk_attachNamedValue(&fakeKeywordsObject->fields, *keyword, NONE_VAL()); } continue; } else { break; } } } _cleanup: free(space); krk_resetStack(); } _accept_candidate: if (candidate_count == 0) { goto done; } if (candidate_count == 1) { /* Only one completion possibility */ struct StringBuilder sb = {0}; krk_pushStringBuilderStr(&sb, buffer, start - buf); for (unsigned int i = 0; i < strlen(candidates[0].text); ++i) { krk_pushStringBuilder(&sb, candidates[0].text[i]); } krk_pushStringBuilder(&sb, '\0'); /* Accept this new buffer data */ free(buffer); buffer = sb.bytes; } else { /* Sort candidates */ qsort(candidates, candidate_count, sizeof(candidates[0]), compare_candidate); int selection = 0; struct StringBuilder cmd = {0}; /* Try to prefill the buffer with a common substring. If this actually resulted * in some amount of prefill, then the selection will start in a special mode and * won't be filled with one of the candidates. */ krk_pushStringBuilderStr(&cmd, buffer, start - buf); for (int i = 0; ; ++i) { if ((signed char)candidates[0].text[i] <= 0) goto _end_prefill; /* TODO continuation bytes */ for (int j = 1; j < candidate_count; ++j) { if (candidates[0].text[i] != candidates[j].text[i]) goto _end_prefill; } krk_pushStringBuilder(&cmd, candidates[0].text[i]); } _end_prefill: if (cmd.length > strlen(buffer)) { selection = -2; } else { /* That didn't do anything new, so never mind. */ krk_discardStringBuilder(&cmd); } while (1) { /* Print candidates in status bar */ struct StringBuilder sb = {0}; int offset = 0; int selection_found = (selection < 0); for (int i = 0; i < candidate_count; ++i) { char * printed_candidate = candidates[i].text; if (_candidates_are_files) { /* If the candidates are files, then the candidate can be a full path * but we are only completing the last element, so we should restrict * what we show to just that final element. */ for (char * c = printed_candidate; *c; ++c) { if (c[0] == '/' && c[1] != '\0') { printed_candidate = c+1; } } } else { /* Otherwise, try to be smart about completing things within a * Kuroko expression, skipping over previous member accesses or * function calls. */ for (char * c = printed_candidate; *c; ++c) { if ((c[0] == '.' || c[0] == '(') && c[1] != '\0') { printed_candidate = c+1; } } } if (offset + 1 + (signed)display_width_of_string(printed_candidate) > global_config.term_width - 3) { if (selection_found) { /* We have run out of space but we have shown the current selected candidate. * Stop here and show that there are more candidates available off-screen. */ krk_pushStringBuilderFormat(&sb, " %s>", color_string(COLOR_STATUS_BG, COLOR_STATUS_FG)); krk_pushStringBuilderFormat(&sb, "%s", color_string(COLOR_STATUS_FG, COLOR_STATUS_BG)); break; } /* We have run out of space but still haven't shown the current selected candidate, * so clear out the current status line and start over from here. */ krk_discardStringBuilder(&sb); offset = 0; } /* Did we find the selected candidate ? */ if (i == selection) selection_found = 1; /* Put a space before any candidate that isn't the first on the line. */ if (offset > 0) { krk_pushStringBuilderFormat(&sb, " "); offset++; } /* Color the candidate based on its type, unless it's the selected * candidate, in which case we use the search highlight colors. */ const char * color_fg = candidates[i].type == Candidate_Normal ? COLOR_STATUS_FG : candidates[i].type == Candidate_Command ? COLOR_KEYWORD : candidates[i].type == Candidate_Builtin ? COLOR_TYPE : COLOR_STATUS_FG; const char * colorString = (i == selection) ? color_string(COLOR_SEARCH_FG, COLOR_SEARCH_BG) : color_string(color_fg, COLOR_STATUS_BG); krk_pushStringBuilderFormat(&sb, "%s%s", colorString, printed_candidate); krk_pushStringBuilderFormat(&sb, "%s", color_string(COLOR_STATUS_FG, COLOR_STATUS_BG)); offset += display_width_of_string(printed_candidate); } /* Finally, render the message we built. */ krk_pushStringBuilder(&sb, '\0'); render_status_message("%s", sb.bytes); krk_discardStringBuilder(&sb); /* Reuse the previous string builder to fill the current selected * candidate into the input buffer and redraw the input buffer */ if (selection >= 0) { krk_pushStringBuilderStr(&cmd, buffer, start - buf); for (unsigned int i = 0; i < strlen(candidates[selection].text); ++i) { krk_pushStringBuilder(&cmd, candidates[selection].text[i]); } } else if (selection == -1) { /* With selection == -1, we want to redraw the original input. */ krk_pushStringBuilderStr(&cmd, buffer, strlen(buffer)); } /* else selection == -2 and we already filled it before printing the candidate list */ krk_pushStringBuilder(&cmd, '\0'); command_buffer_deserialize(cmd.bytes); render_command_input_buffer(); cmd = (struct StringBuilder){0}; int k = 0; while ((k = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); switch (k) { /* Move selection to next candidate. */ case KEY_RIGHT: case KEY_TAB: selection = (selection < 0) ? 0 : selection + 1; if (selection == candidate_count) selection = -1; command_buffer_clear_before(); continue; /* Move selection to previous candidate. */ case KEY_LEFT: case KEY_SHIFT_TAB: selection = (selection >= 0 ? selection : candidate_count) - 1; command_buffer_clear_before(); continue; /* Abort tab completion. Clear anything we put in the buffer, * and let the command mode figure out what to do next, which * is probably just exit command mode. */ case KEY_ESCAPE: bim_unget(27); command_buffer_clear_before(); break; /* TODO: Consider supporting mouse input for picking a selection? */ case KEY_MOUSE: eat_mouse(); command_buffer_clear_before(); continue; case KEY_MOUSE_SGR: eat_mouse_sgr(); command_buffer_clear_before(); continue; /* On anything else, accept the current candidate and then try * to let the command mode handle the entered text if it wasn't a special key. */ default: if (k < 256) bim_unget(k); /* Replace the old buffer data with empty data, we have already written out * our completion to the command buffer. */ free(buffer); buffer = strdup(""); break; } break; } } /* Free candidates */ for (int i = 0; i < candidate_count; ++i) { free(candidates[i].text); } done: redraw_statusbar(); free(candidates); free(buf); return buffer; } /** * Draw the command buffer and any prefix. */ void render_command_input_buffer(void) { if (!global_config.command_buffer) return; /* Place the cursor at the bottom of the screen */ place_cursor(1, global_config.term_height); paint_line(COLOR_BG); set_colors(COLOR_ALT_FG, COLOR_BG); /* If there's a mode name to render, draw it first */ int _left_gutter = 0; if (env->mode == MODE_LINE_SELECTION) { _left_gutter = printf("(LINE %d:%d)", (env->start_line < env->line_no) ? env->start_line : env->line_no, (env->start_line < env->line_no) ? env->line_no : env->start_line); } else if (env->mode == MODE_COL_SELECTION) { _left_gutter = printf("(COL %d:%d %d)", (env->start_line < env->line_no) ? env->start_line : env->line_no, (env->start_line < env->line_no) ? env->line_no : env->start_line, (env->sel_col)); } else if (env->mode == MODE_CHAR_SELECTION) { _left_gutter = printf("(CHAR)"); } /* Figure out the cursor position and adjust the offset if necessary */ int x = 2 + _left_gutter - global_config.command_offset; for (int i = 0; i < global_config.command_col_no - 1; ++i) { char_t * c = &global_config.command_buffer->text[i]; x += c->display_width; } if (x > global_config.term_width - 1) { int diff = x - (global_config.term_width - 1); global_config.command_offset += diff; x -= diff; } if (x < 2 + _left_gutter) { int diff = (2 + _left_gutter) - x; global_config.command_offset -= diff; x += diff; } /* If the input buffer is horizontally shifted because it's too long, indicate that. */ if (global_config.command_offset) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("<"); } else { /* Otherwise indicate buffer mode (search / ?, or command :) */ set_colors(COLOR_FG, COLOR_BG); if (global_config.overlay_mode == OVERLAY_MODE_SEARCH) { printf(global_config.search_direction == 0 ? "?" : "/"); } else if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { printf("_"); } else { printf(":"); } } /* Render the buffer */ render_line(global_config.command_buffer, global_config.term_width-1-_left_gutter, global_config.command_offset, -1); /* Place and display the cursor */ place_cursor(x, global_config.term_height); show_cursor(); } BIM_ACTION(command_discard, 0, "Discard the input buffer and cancel command or search." ,void) { free(global_config.command_buffer); global_config.command_buffer = NULL; if (global_config.overlay_mode == OVERLAY_MODE_SEARCH) { env->line_no = global_config.prev_line; env->col_no = global_config.prev_col; /* Unhighlight search matches */ for (int i = 0; i < env->line_count; ++i) { for (int j = 0; j < env->lines[i]->actual; ++j) { env->lines[i]->text[j].flags &= (~FLAG_SEARCH); } rehighlight_search(env->lines[i]); } } global_config.overlay_mode = OVERLAY_MODE_NONE; redraw_all(); /* TODO exit some other modes? */ } BIM_ACTION(enter_command, 0, "Enter command input mode." ,void) { global_config.overlay_mode = OVERLAY_MODE_COMMAND; global_config.command_offset = 0; global_config.command_col_no = 1; if (global_config.command_buffer) { free(global_config.command_buffer); } global_config.command_buffer = calloc(sizeof(line_t)+sizeof(char_t)*32,1); global_config.command_buffer->available = 32; global_config.command_syn_back = env->syntax; global_config.command_syn = find_syntax_calculator("bimcmd"); global_config.history_point = -1; render_command_input_buffer(); } static char * command_buffer_to_utf8(void) { size_t size = 0; for (int i = 0; i < global_config.command_buffer->actual; ++i) { char tmp[8] = {0}; size += to_eight(global_config.command_buffer->text[i].codepoint, tmp); } char * tmp = malloc(size + 8); /* for overflow from to_eight */ char * t = tmp; for (int i = 0; i < global_config.command_buffer->actual; ++i) { t += to_eight(global_config.command_buffer->text[i].codepoint, t); } *t = '\0'; return tmp; } BIM_ACTION(command_accept, 0, "Accept the command input and run the requested command." ,void) { /* Convert command buffer to UTF-8 char-array string */ char * tmp = command_buffer_to_utf8(); /* Free the original editing buffer */ free(global_config.command_buffer); global_config.command_buffer = NULL; /* Run the converted command */ global_config.break_from_selection = 0; insert_command_history(command_history, tmp); global_config.overlay_mode = OVERLAY_MODE_NONE; process_command(tmp); free(tmp); if (!global_config.break_from_selection && env->mode != MODE_DIRECTORY_BROWSE) { if (env->mode == MODE_LINE_SELECTION || env->mode == MODE_CHAR_SELECTION || env->mode == MODE_COL_SELECTION) { recalculate_selected_lines(); } env->mode = MODE_NORMAL; } /* Leave command mode */ } BIM_ACTION(command_word_delete, 0, "Delete the previous word from the input buffer." ,void) { _syn_command(); while (global_config.command_col_no > 1 && (global_config.command_buffer->text[global_config.command_col_no-2].codepoint == ' ' || global_config.command_buffer->text[global_config.command_col_no-2].codepoint == '/')) { line_delete(global_config.command_buffer, global_config.command_col_no - 1, -1); global_config.command_col_no--; } while (global_config.command_col_no > 1 && global_config.command_buffer->text[global_config.command_col_no-2].codepoint != ' ' && global_config.command_buffer->text[global_config.command_col_no-2].codepoint != '/') { line_delete(global_config.command_buffer, global_config.command_col_no - 1, -1); global_config.command_col_no--; } _syn_restore(); } BIM_ACTION(command_tab_complete_buffer, 0, "Complete command names and arguments in the input buffer." ,void) { /* command_tab_complete should probably just be adjusted to deal with the buffer... */ char * tmp = command_buffer_serialize(); tmp = command_tab_complete(tmp); command_buffer_deserialize(tmp); } BIM_ACTION(command_backspace, 0, "Erase the character before the cursor in the input buffer." ,void) { if (global_config.command_col_no <= 1) { if (global_config.command_buffer->actual == 0) { command_discard(); } return; } _syn_command(); line_delete(global_config.command_buffer, global_config.command_col_no - 1, -1); _syn_restore(); global_config.command_col_no--; global_config.command_offset = 0; } static void _restore_history(unsigned char **which_history, int point) { unsigned char * t = which_history[point]; global_config.command_col_no = 1; global_config.command_buffer->actual = 0; _syn_command(); uint32_t state = 0; uint32_t c = 0; while (*t) { if (!decode(&state, &c, *t)) { char_t _c = {codepoint_width(c), 0, c}; global_config.command_buffer = line_insert(global_config.command_buffer, _c, global_config.command_col_no - 1, -1); global_config.command_col_no++; } else if (state == UTF8_REJECT) state = 0; t++; } _syn_restore(); } static void _scroll_history(int direction, unsigned char **which_history, int * which_point) { if (direction == -1) { if (which_history[*which_point+1]) { _restore_history(which_history, *which_point+1); (*which_point)++; } } else { if (*which_point > 0) { (*which_point)--; _restore_history(which_history, *which_point); } else { *which_point = -1; global_config.command_col_no = 1; global_config.command_buffer->actual = 0; } } } BIM_ACTION(command_scroll_history, ARG_IS_CUSTOM, "Scroll through command input history." ,int direction) { _scroll_history(direction, command_history, &global_config.history_point); } BIM_ACTION(command_scroll_search_history, ARG_IS_CUSTOM, "Scroll through search input history." ,int direction) { _scroll_history(direction, search_history, &global_config.search_point); } BIM_ACTION(command_word_left, 0, "Move to the start of the previous word in the input buffer." ,void) { if (global_config.command_col_no > 1) { do { global_config.command_col_no--; } while (isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no > 1); if (global_config.command_col_no == 1) return; do { global_config.command_col_no--; } while (!isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no > 1); if (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++; } } BIM_ACTION(command_word_right, 0, "Move to the start of the next word in the input buffer." ,void) { if (global_config.command_col_no < global_config.command_buffer->actual) { do { global_config.command_col_no++; if (global_config.command_col_no > global_config.command_buffer->actual) { global_config.command_col_no = global_config.command_buffer->actual+1; break; } } while (!isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no <= global_config.command_buffer->actual); do { global_config.command_col_no++; if (global_config.command_col_no > global_config.command_buffer->actual) { global_config.command_col_no = global_config.command_buffer->actual+1; break; } } while (isspace(global_config.command_buffer->text[global_config.command_col_no-1].codepoint) && global_config.command_col_no <= global_config.command_buffer->actual); if (global_config.command_col_no > global_config.command_buffer->actual) { global_config.command_col_no = global_config.command_buffer->actual+1; } } } BIM_ACTION(command_cursor_left, 0, "Move the cursor one character left in the input buffer." ,void) { if (global_config.command_col_no > 1) global_config.command_col_no--; } BIM_ACTION(command_cursor_right, 0, "Move the cursor one character right in the input buffer." ,void) { if (global_config.command_col_no < global_config.command_buffer->actual+1) global_config.command_col_no++; } BIM_ACTION(command_cursor_home, 0, "Move the cursor to the start of the input buffer." ,void) { global_config.command_col_no = 1; } BIM_ACTION(command_cursor_end, 0, "Move the cursor to the end of the input buffer." ,void) { global_config.command_col_no = global_config.command_buffer->actual + 1; } BIM_ACTION(eat_mouse, 0, "(temporary) Read, but ignore mouse input." ,void) { bim_getch(); bim_getch(); bim_getch(); } BIM_ACTION(command_insert_char, ARG_IS_INPUT, "Insert one character into the input buffer." ,int c) { char_t _c = {codepoint_width(c), 0, c}; _syn_command(); global_config.command_buffer = line_insert(global_config.command_buffer, _c, global_config.command_col_no - 1, -1); _syn_restore(); global_config.command_col_no++; } /** * Determine whether a string should be searched * case-sensitive or not based on whether it contains * any upper-case letters. */ int smart_case(uint32_t * str) { if (!global_config.smart_case) return 0; for (uint32_t * s = str; *s; ++s) { if (tolower(*s) != (int)*s) { return 0; } } return 1; } /** * Search forward from the given cursor position * to find a basic search match. * * This could be more complicated... */ void find_match(int from_line, int from_col, int * out_line, int * out_col, uint32_t * str, int * matchlen) { int col = from_col; int ignorecase = smart_case(str); for (int i = from_line; i <= env->line_count; ++i) { line_t * line = env->lines[i - 1]; int j = col - 1; while (j < line->actual + 1) { if (subsearch_matches(line, j, str, ignorecase, matchlen)) { *out_line = i; *out_col = j + 1; return; } j++; } col = 0; } } /** * Search backwards for matching string. */ void find_match_backwards(int from_line, int from_col, int * out_line, int * out_col, uint32_t * str) { int col = from_col; int ignorecase = smart_case(str); for (int i = from_line; i >= 1; --i) { line_t * line = env->lines[i-1]; int j = col - 1; while (j > -1) { if (subsearch_matches(line, j, str, ignorecase, NULL)) { *out_line = i; *out_col = j + 1; return; } j--; } col = (i > 1) ? (env->lines[i-2]->actual + 1) : -1; } } /** * Re-mark search matches while editing text. * This gets called after recalculate_syntax, so it works as we're typing or * whenever syntax calculation would redraw other lines. * XXX There's a bunch of code duplication between the (now three) search match functions. * and search should be improved to support regex anyway, so this needs to be fixed. */ void rehighlight_search(line_t * line) { if (!global_config.search) return; int j = 0; while (j < line->actual) { line->text[j].flags &= ~(FLAG_SEARCH); j++; } int ignorecase = smart_case(global_config.search); j = 0; while (j < line->actual) { int matchlen = 0; if (subsearch_matches(line, j, global_config.search, ignorecase, &matchlen)) { for (int i = j; matchlen > 0; ++i, matchlen--) { line->text[i].flags |= FLAG_SEARCH; } } j++; } } /** * Draw the matched search result. */ void draw_search_match(uint32_t * buffer, int redraw_buffer) { for (int i = 0; i < env->line_count; ++i) { for (int j = 0; j < env->lines[i]->actual; ++j) { env->lines[i]->text[j].flags &= (~FLAG_SEARCH); } } int my_index = 0, match_count = 0; int line = -1, col = -1, _line = 1, _col = 1; do { int matchlen; find_match(_line, _col, &line, &col, buffer, &matchlen); if (line != -1) { line_t * l = env->lines[line-1]; for (int i = col; matchlen > 0; ++i, --matchlen) { l->text[i-1].flags |= FLAG_SEARCH; } match_count += 1; if (col == env->col_no && line == env->line_no) my_index = match_count; } _line = line; _col = col+1; line = -1; col = -1; } while (_line != -1); redraw_text(); place_cursor_actual(); redraw_statusbar(); redraw_commandline(); set_fg_color(COLOR_ALT_FG); printf("[%d/%d] ", my_index, match_count); set_fg_color(COLOR_KEYWORD); printf(redraw_buffer == 1 ? "/" : "?"); set_fg_color(COLOR_FG); uint32_t * c = buffer; while (*c) { char tmp[7] = {0}; /* Max six bytes, use 7 to ensure last is always nil */ to_eight(*c, tmp); printf("%s", tmp); c++; } } BIM_ACTION(start_file_search, 0, "Search for open files and switch tabs quickly.",void) { global_config.overlay_mode = OVERLAY_MODE_FILESEARCH; global_config.command_offset = 0; global_config.command_col_no = 1; if (global_config.command_buffer) { free(global_config.command_buffer); } global_config.command_buffer = calloc(sizeof(line_t)+sizeof(char_t)*32,1); global_config.command_buffer->available = 32; global_config.command_syn_back = env->syntax; global_config.command_syn = NULL; /* uh, dunno for now */ render_command_input_buffer(); } BIM_ACTION(file_search_accept, 0, "Open the requested tab",void) { if (!global_config.command_buffer->actual) { goto _finish; } /* See if the command buffer matches any open buffers */ buffer_t * match = NULL; for (int i = global_config.tab_offset; i < buffers_len; i++) { buffer_t * _env = buffers[i]; if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { if (global_config.command_buffer->actual) { char * f = _env->file_name ? file_basename(_env->file_name) : ""; /* TODO: Support unicode input here; needs conversion */ int i = 0; for (; i < global_config.command_buffer->actual && f[i] == global_config.command_buffer->text[i].codepoint; ++i); if (global_config.command_buffer->actual == i) { match = _env; break; } } } } if (match) { env = match; if (left_buffer && (left_buffer != env && right_buffer != env)) unsplit(); } _finish: /* Free the original editing buffer */ free(global_config.command_buffer); global_config.command_buffer = NULL; /* Leave command mode */ global_config.overlay_mode = OVERLAY_MODE_NONE; redraw_all(); } BIM_ACTION(enter_search, ARG_IS_CUSTOM, "Enter search mode." ,int direction) { global_config.overlay_mode = OVERLAY_MODE_SEARCH; global_config.command_offset = 0; global_config.command_col_no = 1; global_config.prev_line = env->line_no; global_config.prev_col = env->col_no; global_config.prev_coffset = env->coffset; global_config.prev_offset = env->offset; global_config.search_direction = direction; if (global_config.command_buffer) { free(global_config.command_buffer); } global_config.command_buffer = calloc(sizeof(line_t)+sizeof(char_t)*32,1); global_config.command_buffer->available = 32; global_config.command_syn_back = env->syntax; global_config.command_syn = NULL; /* Disable syntax highlighting in search; maybe use buffer's mode instead? */ render_command_input_buffer(); } BIM_ACTION(search_accept, 0, "Accept the search term and return to the previous mode." ,void) { /* Store the accepted search */ if (!global_config.command_buffer->actual) { if (global_config.search) { search_next(); } goto _finish; } if (global_config.search) { free(global_config.search); } global_config.search = malloc((global_config.command_buffer->actual + 1) * sizeof(uint32_t)); for (int i = 0; i < global_config.command_buffer->actual; ++i) { global_config.search[i] = global_config.command_buffer->text[i].codepoint; } global_config.search[global_config.command_buffer->actual] = 0; char * tmp = command_buffer_to_utf8(); insert_command_history(search_history, tmp); free(tmp); _finish: /* Free the original editing buffer */ free(global_config.command_buffer); global_config.command_buffer = NULL; /* Leave command mode */ global_config.overlay_mode = OVERLAY_MODE_NONE; if (global_config.search) draw_search_match(global_config.search, global_config.search_direction); } /** * Find the next search result, or loop back around if at the end. */ BIM_ACTION(search_next, 0, "Jump to the next search match." ,void) { if (!global_config.search) return; if (env->coffset) env->coffset = 0; int line = -1, col = -1, wrapped = 0; find_match(env->line_no, env->col_no+1, &line, &col, global_config.search, NULL); if (line == -1) { if (!global_config.search_wraps) return; find_match(1,1, &line, &col, global_config.search, NULL); if (line == -1) return; wrapped = 1; } env->col_no = col; env->line_no = line; set_preferred_column(); draw_search_match(global_config.search, 1); if (wrapped) { set_fg_color(COLOR_ALT_FG); printf(" (search wrapped to top)"); } } /** * Find the previous search result, or loop to the end of the file. */ BIM_ACTION(search_prev, 0, "Jump to the preceding search match." ,void) { if (!global_config.search) return; if (env->coffset) env->coffset = 0; int line = -1, col = -1, wrapped = 0; find_match_backwards(env->line_no, env->col_no-1, &line, &col, global_config.search); if (line == -1) { if (!global_config.search_wraps) return; find_match_backwards(env->line_count, env->lines[env->line_count-1]->actual, &line, &col, global_config.search); if (line == -1) return; wrapped = 1; } env->col_no = col; env->line_no = line; set_preferred_column(); draw_search_match(global_config.search, 0); if (wrapped) { set_fg_color(COLOR_ALT_FG); printf(" (search wrapped to bottom)"); } } /** * Find the matching paren for this one. * * This approach skips having to do its own syntax parsing * to deal with, eg., erroneous parens in comments. It does * this by finding the matching paren with the same flag * value, thus parens in strings will match, parens outside * of strings will match, but parens in strings won't * match parens outside of strings and so on. */ void find_matching_paren(int * out_line, int * out_col, int in_col) { if (env->col_no - in_col + 1 > env->lines[env->line_no-1]->actual) { return; /* Invalid cursor position */ } int paren_match = 0; int direction = 0; int start = env->lines[env->line_no-1]->text[env->col_no-in_col].codepoint; int flags = env->lines[env->line_no-1]->text[env->col_no-in_col].flags & 0x1F; int count = 0; for (int i = 0; global_config.paren_pairs[i]; ++i) { if ((uint32_t)start == global_config.paren_pairs[i]) { direction = (i % 2 == 0) ? 1 : -1; paren_match = global_config.paren_pairs[(i % 2 == 0) ? (i+1) : (i-1)]; break; } } if (!paren_match) return; /* Scan for match */ int line = env->line_no; int col = env->col_no - in_col + 1; do { while (col > 0 && col < env->lines[line-1]->actual + 1) { /* Only match on same syntax */ if ((env->lines[line-1]->text[col-1].flags & 0x1F) == flags) { /* Count up on same direction */ if (env->lines[line-1]->text[col-1].codepoint == start) count++; /* Count down on opposite direction */ if (env->lines[line-1]->text[col-1].codepoint == paren_match) { count--; /* When count == 0 we have a match */ if (count == 0) goto _match_found; } } col += direction; } line += direction; /* Reached first/last line with no match */ if (line == 0 || line == env->line_count + 1) { return; } /* Reset column to start/end of line, depending on direction */ if (direction > 0) { col = 1; } else { col = env->lines[line-1]->actual; } } while (1); _match_found: *out_line = line; *out_col = col; } /** * Switch to the left split view * (Primarily to handle cases where the left and right are the same buffer) */ BIM_ACTION(use_left_buffer, 0, "Switch to the left split view." ,void) { if (left_buffer == right_buffer && env->left != 0) { view_right_offset = env->offset; env->width = env->left; env->left = 0; env->offset = view_left_offset; } env = left_buffer; update_title(); } /** * Switch to the right split view * (Primarily to handle cases where the left and right are the same buffer) */ BIM_ACTION(use_right_buffer, 0, "Switch to the right split view." ,void) { if (left_buffer == right_buffer && env->left == 0) { view_left_offset = env->offset; env->left = env->width; env->width = global_config.term_width - env->width; env->offset = view_right_offset; } env = right_buffer; update_title(); } void handle_common_mouse(int buttons, int x, int y) { if (buttons == 64) { /* Scroll up */ if (global_config.shift_scrolling) { env->loading = 1; int shifted = 0; for (int i = 0; i < global_config.scroll_amount; ++i) { if (env->offset > 0) { env->offset--; if (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - global_config.tabs_visible - global_config.cursor_padding) { cursor_up(); } shifted++; } } env->loading = 0; if (!shifted) return; if (global_config.can_scroll && !left_buffer) { if (!global_config.can_insert) { shift_down(shifted); redraw_tabbar(); } else { insert_lines_at(global_config.tabs_visible ? 2 : 1, shifted); } for (int i = 0; i < shifted; ++i) { redraw_line(env->offset+i); } } else { redraw_tabbar(); redraw_text(); } redraw_statusbar(); redraw_commandline(); place_cursor_actual(); } else { for (int i = 0; i < global_config.scroll_amount; ++i) { cursor_up(); } } return; } else if (buttons == 65) { /* Scroll down */ if (global_config.shift_scrolling) { env->loading = 1; int shifted = 0; for (int i = 0; i < global_config.scroll_amount; ++i) { if (env->offset < env->line_count-1) { env->offset++; int e = (env->offset == 0) ? env->offset : env->offset + global_config.cursor_padding; if (env->line_no <= e) { cursor_down(); } shifted++; } } env->loading = 0; if (!shifted) return; if (global_config.can_scroll && !left_buffer) { if (!global_config.can_insert) { shift_up(shifted); redraw_tabbar(); } else { delete_lines_at(global_config.tabs_visible ? 2 : 1, shifted); } int l = global_config.term_height - global_config.bottom_size - global_config.tabs_visible; for (int i = 0; i < shifted; ++i) { if (env->offset + l - i < env->line_count + 1) { redraw_line(env->offset + l-1-i); } else { draw_excess_line(l - 1 - i); } } } else { redraw_tabbar(); redraw_text(); } redraw_statusbar(); redraw_commandline(); place_cursor_actual(); } else { for (int i = 0; i < global_config.scroll_amount; ++i) { cursor_down(); } } return; } else if (buttons == 3) { /* Move cursor to position */ if (x < 0) return; if (y < 0) return; if (y == 1 && global_config.tabs_visible) { /* Pick from tabs */ if (env->mode != MODE_NORMAL && env->mode != MODE_INSERT) return; /* Don't let the tab be switched in other modes for now */ int _x = 0; if (global_config.tab_offset) _x = 1; if (global_config.tab_offset && _x >= x) { global_config.tab_offset--; redraw_tabbar(); return; } for (int i = global_config.tab_offset; i < buffers_len; i++) { buffer_t * _env = buffers[i]; char tmp[64]; int size = 0; int filled = draw_tab_name(_env, tmp, global_config.term_width - _x, &size); _x += size; if (_x >= x) { if (left_buffer && buffers[i] != left_buffer && buffers[i] != right_buffer) unsplit(); env = buffers[i]; redraw_all(); update_title(); return; } if (filled) break; } if (x > _x && global_config.tab_offset < buffers_len - 1) { global_config.tab_offset++; redraw_tabbar(); } return; } if (env->mode == MODE_NORMAL || env->mode == MODE_INSERT) { int current_mode = env->mode; if (x < env->left && env == right_buffer) { use_left_buffer(); } else if (x > env->width && env == left_buffer) { use_right_buffer(); } env->mode = current_mode; redraw_all(); } if (env->left) { x -= env->left; } /* Figure out y coordinate */ int line_no = y + env->offset - global_config.tabs_visible; int col_no = -1; if (line_no > env->line_count) { line_no = env->line_count; } if (line_no != env->line_no) { env->coffset = 0; } /* Account for the left hand gutter */ int num_size = num_width() + gutter_width(); int _x = num_size - (line_no == env->line_no ? env->coffset : 0); /* Determine where the cursor is physically */ for (int i = 0; i < env->lines[line_no-1]->actual; ++i) { char_t * c = &env->lines[line_no-1]->text[i]; _x += c->display_width; if (_x > x-1) { col_no = i+1; break; } } if (col_no == -1 || col_no > env->lines[line_no-1]->actual) { col_no = env->lines[line_no-1]->actual; } env->line_no = line_no; env->col_no = col_no; set_history_break(); set_preferred_column(); redraw_statusbar(); place_cursor_actual(); } return; } /** * Handle mouse event */ BIM_ACTION(handle_mouse, 0, "Process mouse actions." ,void) { int buttons = bim_getch() - 32; int x = bim_getch() - 32; int y = bim_getch() - 32; handle_common_mouse(buttons, x, y); } BIM_ACTION(eat_mouse_sgr, 0, "Receive, but do not process, mouse actions." ,void) { do { int _c = bim_getch(); if (_c == -1 || _c == 'm' || _c == 'M') break; } while (1); } BIM_ACTION(handle_mouse_sgr, 0, "Process SGR-style mouse actions." ,void) { int values[3] = {0}; char tmp[512] = {0}; char * c = tmp; int buttons = 0; do { int _c = bim_getch(); if (_c == -1) { break; } if (_c == 'm') { buttons = 3; break; } else if (_c == 'M') { buttons = 0; break; } *c = _c; ++c; } while (1); char * j = tmp; char * last = tmp; int i = 0; while (*j) { if (*j == ';') { *j = '\0'; values[i] = atoi(last); last = j+1; i++; if (i == 3) break; } j++; } if (last && i < 3) values[i] = atoi(last); if (buttons != 3) { buttons = values[0]; } int x = values[1]; int y = values[2]; handle_common_mouse(buttons, x, y); } BIM_ACTION(insert_char, ARG_IS_INPUT | ACTION_IS_RW, "Insert one character." ,unsigned int c) { if (!c) { render_error("Inserted nil byte?"); return; } char_t _c; _c.codepoint = c; _c.flags = 0; _c.display_width = codepoint_width(c); line_t * line = env->lines[env->line_no - 1]; line_t * nline = line_insert(line, _c, env->col_no - 1, env->line_no - 1); if (line != nline) { env->lines[env->line_no - 1] = nline; } env->col_no += 1; set_preferred_column(); set_modified(); } BIM_ACTION(replace_char, ARG_IS_PROMPT | ACTION_IS_RW, "Replace a single character." ,unsigned int c) { if (env->col_no < 1 || env->col_no > env->lines[env->line_no-1]->actual) return; if (c >= KEY_ESCAPE) { render_error("Invalid key for replacement"); return; } char_t _c; _c.codepoint = c; _c.flags = 0; _c.display_width = codepoint_width(c); line_replace(env->lines[env->line_no-1], _c, env->col_no-1, env->line_no-1); redraw_line(env->line_no-1); set_modified(); } BIM_ACTION(undo_history, ACTION_IS_RW, "Undo history until the last breakpoint." ,void) { if (!global_config.history_enabled) return; env->loading = 1; history_t * e = env->history; if (e->type == HISTORY_SENTINEL) { env->loading = 0; render_commandline_message("Already at oldest change"); return; } int count_chars = 0; int count_lines = 0; do { if (e->type == HISTORY_SENTINEL) break; switch (e->type) { case HISTORY_INSERT: /* Delete */ line_delete( env->lines[e->contents.insert_delete_replace.lineno], e->contents.insert_delete_replace.offset+1, e->contents.insert_delete_replace.lineno ); count_chars++; break; case HISTORY_DELETE: { char_t _c = {codepoint_width(e->contents.insert_delete_replace.old_codepoint),0,e->contents.insert_delete_replace.old_codepoint}; env->lines[e->contents.insert_delete_replace.lineno] = line_insert( env->lines[e->contents.insert_delete_replace.lineno], _c, e->contents.insert_delete_replace.offset-1, e->contents.insert_delete_replace.lineno ); } count_chars++; break; case HISTORY_REPLACE: { char_t _o = {codepoint_width(e->contents.insert_delete_replace.old_codepoint),0,e->contents.insert_delete_replace.old_codepoint}; line_replace( env->lines[e->contents.insert_delete_replace.lineno], _o, e->contents.insert_delete_replace.offset, e->contents.insert_delete_replace.lineno ); } count_chars++; break; case HISTORY_REMOVE_LINE: env->lines = add_line(env->lines, e->contents.remove_replace_line.lineno); replace_line(env->lines, e->contents.remove_replace_line.lineno, e->contents.remove_replace_line.old_contents); count_lines++; break; case HISTORY_ADD_LINE: env->lines = remove_line(env->lines, e->contents.add_merge_split_lines.lineno); count_lines++; break; case HISTORY_REPLACE_LINE: replace_line(env->lines, e->contents.remove_replace_line.lineno, e->contents.remove_replace_line.old_contents); count_lines++; break; case HISTORY_SPLIT_LINE: env->lines = merge_lines(env->lines, e->contents.add_merge_split_lines.lineno+1); count_lines++; break; case HISTORY_MERGE_LINES: env->lines = split_line(env->lines, e->contents.add_merge_split_lines.lineno-1, e->contents.add_merge_split_lines.split); count_lines++; break; case HISTORY_BREAK: /* Ignore break */ break; default: render_error("Unknown type %d!\n", e->type); break; } env->line_no = env->history->line; env->col_no = env->history->col; env->history = e->previous; e = env->history; } while (e->type != HISTORY_BREAK); if (env->line_no > env->line_count) env->line_no = env->line_count; if (env->line_no < 1) env->line_no = 1; if (env->col_no > env->lines[env->line_no-1]->actual) env->col_no = env->lines[env->line_no-1]->actual; if (env->col_no < 1) env->col_no = 1; env->modified = (env->history != env->last_save_history); env->loading = 0; for (int i = 0; i < env->line_count; ++i) { env->lines[i]->istate = 0; recalculate_tabs(env->lines[i]); } schedule_complete_recalc(); place_cursor_actual(); update_title(); redraw_all(); render_commandline_message("%d character%s, %d line%s changed", count_chars, (count_chars == 1) ? "" : "s", count_lines, (count_lines == 1) ? "" : "s"); } BIM_ACTION(redo_history, ACTION_IS_RW, "Redo history until the next breakpoint." ,void) { if (!global_config.history_enabled) return; env->loading = 1; history_t * e = env->history->next; if (!e) { env->loading = 0; render_commandline_message("Already at newest change"); return; } int count_chars = 0; int count_lines = 0; while (e) { if (e->type == HISTORY_BREAK) { env->history = e; break; } switch (e->type) { case HISTORY_INSERT: { char_t _c = {codepoint_width(e->contents.insert_delete_replace.codepoint),0,e->contents.insert_delete_replace.codepoint}; env->lines[e->contents.insert_delete_replace.lineno] = line_insert( env->lines[e->contents.insert_delete_replace.lineno], _c, e->contents.insert_delete_replace.offset, e->contents.insert_delete_replace.lineno ); } count_chars++; break; case HISTORY_DELETE: /* Delete */ line_delete( env->lines[e->contents.insert_delete_replace.lineno], e->contents.insert_delete_replace.offset, e->contents.insert_delete_replace.lineno ); count_chars++; break; case HISTORY_REPLACE: { char_t _o = {codepoint_width(e->contents.insert_delete_replace.codepoint),0,e->contents.insert_delete_replace.codepoint}; line_replace( env->lines[e->contents.insert_delete_replace.lineno], _o, e->contents.insert_delete_replace.offset, e->contents.insert_delete_replace.lineno ); } count_chars++; break; case HISTORY_ADD_LINE: env->lines = add_line(env->lines, e->contents.remove_replace_line.lineno); count_lines++; break; case HISTORY_REMOVE_LINE: env->lines = remove_line(env->lines, e->contents.remove_replace_line.lineno); count_lines++; break; case HISTORY_REPLACE_LINE: replace_line(env->lines, e->contents.remove_replace_line.lineno, e->contents.remove_replace_line.contents); count_lines++; break; case HISTORY_MERGE_LINES: env->lines = merge_lines(env->lines, e->contents.add_merge_split_lines.lineno); count_lines++; break; case HISTORY_SPLIT_LINE: env->lines = split_line(env->lines, e->contents.add_merge_split_lines.lineno, e->contents.add_merge_split_lines.split); count_lines++; break; case HISTORY_BREAK: /* Ignore break */ break; default: render_error("Unknown type %d!\n", e->type); break; } env->history = e; e = e->next; } env->line_no = env->history->line; env->col_no = env->history->col; if (env->line_no > env->line_count) env->line_no = env->line_count; if (env->line_no < 1) env->line_no = 1; if (env->col_no > env->lines[env->line_no-1]->actual) env->col_no = env->lines[env->line_no-1]->actual; if (env->col_no < 1) env->col_no = 1; env->modified = (env->history != env->last_save_history); env->loading = 0; for (int i = 0; i < env->line_count; ++i) { env->lines[i]->istate = 0; recalculate_tabs(env->lines[i]); } schedule_complete_recalc(); place_cursor_actual(); update_title(); redraw_all(); render_commandline_message("%d character%s, %d line%s changed", count_chars, (count_chars == 1) ? "" : "s", count_lines, (count_lines == 1) ? "" : "s"); } int is_whitespace(int codepoint) { return codepoint == ' ' || codepoint == '\t'; } int is_normal(int codepoint) { return isalnum(codepoint) || codepoint == '_'; } int is_special(int codepoint) { return !is_normal(codepoint) && !is_whitespace(codepoint); } BIM_ACTION(word_left, 0, "Move the cursor left to the previous word." ,void) { if (!env->lines[env->line_no-1]) return; while (env->col_no > 1 && is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint)) { env->col_no -= 1; } if (env->col_no == 1) { if (env->line_no == 1) goto _place; env->line_no--; env->col_no = env->lines[env->line_no-1]->actual; goto _place; } int (*inverse_comparator)(int) = is_special; if (env->col_no > 1 && is_special(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint)) { inverse_comparator = is_normal; } do { if (env->col_no > 1) { env->col_no -= 1; } } 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)); _place: set_preferred_column(); place_cursor_actual(); } BIM_ACTION(big_word_left, 0, "Move the cursor left to the previous WORD." ,void) { int line_no = env->line_no; int col_no = env->col_no; do { col_no--; while (col_no == 0) { line_no--; if (line_no == 0) { goto_line(1); set_preferred_column(); return; } col_no = env->lines[line_no-1]->actual; } } while (isspace(env->lines[line_no-1]->text[col_no-1].codepoint)); do { col_no--; if (col_no == 0) { env->col_no = 1; env->line_no = line_no; set_preferred_column(); redraw_statusbar(); place_cursor_actual(); return; } } while (!isspace(env->lines[line_no-1]->text[col_no-1].codepoint)); env->col_no = col_no; env->line_no = line_no; set_preferred_column(); cursor_right(); } BIM_ACTION(word_right, 0, "Move the cursor right to the start of the next word." ,void) { if (!env->lines[env->line_no-1]) return; if (env->col_no >= env->lines[env->line_no-1]->actual) { /* next line */ if (env->line_no == env->line_count) return; env->line_no++; env->col_no = 0; if (env->col_no >= env->lines[env->line_no-1]->actual) { goto _place; } } if (env->col_no < env->lines[env->line_no-1]->actual && is_whitespace(env->lines[env->line_no-1]->text[env->col_no - 1].codepoint)) { while (env->col_no < env->lines[env->line_no-1]->actual && is_whitespace(env->lines[env->line_no-1]->text[env->col_no - 1].codepoint)) { env->col_no++; } goto _place; } int (*inverse_comparator)(int) = is_special; if (is_special(env->lines[env->line_no - 1]->text[env->col_no - 1].codepoint)) { inverse_comparator = is_normal; } while (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)) { env->col_no++; } while (env->col_no < env->lines[env->line_no-1]->actual && is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 1].codepoint)) { env->col_no++; } _place: set_preferred_column(); place_cursor_actual(); } BIM_ACTION(big_word_right, 0, "Move the cursor right to the start of the next WORD." ,void) { int line_no = env->line_no; int col_no = env->col_no; do { col_no++; if (col_no > env->lines[line_no-1]->actual) { line_no++; if (line_no > env->line_count) { env->line_no = env->line_count; env->col_no = env->lines[env->line_no-1]->actual; set_preferred_column(); redraw_statusbar(); place_cursor_actual(); return; } col_no = 0; break; } } while (!isspace(env->lines[line_no-1]->text[col_no-1].codepoint)); do { col_no++; while (col_no > env->lines[line_no-1]->actual) { line_no++; if (line_no >= env->line_count) { env->col_no = env->lines[env->line_count-1]->actual; env->line_no = env->line_count; set_preferred_column(); redraw_statusbar(); place_cursor_actual(); return; } col_no = 1; } } while (isspace(env->lines[line_no-1]->text[col_no-1].codepoint)); env->col_no = col_no; env->line_no = line_no; set_preferred_column(); redraw_statusbar(); place_cursor_actual(); return; } BIM_ACTION(delete_at_cursor, ACTION_IS_RW, "Delete the character at the cursor, or merge with previous line." ,void) { if (env->col_no > 1) { line_delete(env->lines[env->line_no - 1], env->col_no - 1, env->line_no - 1); env->col_no -= 1; if (env->coffset > 0) env->coffset--; redraw_line(env->line_no-1); set_modified(); redraw_statusbar(); place_cursor_actual(); } else if (env->line_no > 1) { int tmp = env->lines[env->line_no - 2]->actual; merge_lines(env->lines, env->line_no - 1); env->line_no -= 1; env->col_no = tmp+1; set_preferred_column(); redraw_text(); set_modified(); redraw_statusbar(); place_cursor_actual(); } } BIM_ACTION(delete_word, ACTION_IS_RW, "Delete the previous word." ,void) { if (!env->lines[env->line_no-1]) return; if (env->col_no > 1) { /* Start by deleting whitespace */ while (env->col_no > 1 && is_whitespace(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint)) { line_delete(env->lines[env->line_no - 1], env->col_no - 1, env->line_no - 1); env->col_no -= 1; if (env->coffset > 0) env->coffset--; } int (*inverse_comparator)(int) = is_special; if (env->col_no > 1 && is_special(env->lines[env->line_no - 1]->text[env->col_no - 2].codepoint)) { inverse_comparator = is_normal; } do { if (env->col_no > 1) { line_delete(env->lines[env->line_no - 1], env->col_no - 1, env->line_no - 1); env->col_no -= 1; if (env->coffset > 0) env->coffset--; } } 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)); set_preferred_column(); redraw_text(); set_modified(); redraw_statusbar(); place_cursor_actual(); } } BIM_ACTION(insert_line_feed, ACTION_IS_RW, "Insert a line break, splitting the current line into two." ,void) { if (env->indent) { if ((env->lines[env->line_no-1]->text[env->col_no-2].flags & 0x1F) == FLAG_COMMENT && (env->lines[env->line_no-1]->text[env->col_no-2].codepoint == ' ') && (env->col_no > 3) && (env->lines[env->line_no-1]->text[env->col_no-3].codepoint == '*')) { delete_at_cursor(); } } if (env->col_no == env->lines[env->line_no - 1]->actual + 1) { env->lines = add_line(env->lines, env->line_no); } else { env->lines = split_line(env->lines, env->line_no-1, env->col_no - 1); } unhighlight_matching_paren(); env->coffset = 0; env->col_no = 1; env->line_no += 1; set_preferred_column(); add_indent(env->line_no-1,env->line_no-2,0); if (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - 1) { env->offset += 1; } if (env->highlighting_paren && env->highlighting_paren > env->line_no) env->highlighting_paren++; set_modified(); } BIM_ACTION(yank_lines, 0, "Copy lines into the paste buffer." ,void) { int start = env->start_line; int end = env->line_no; if (global_config.yanks) { for (unsigned int i = 0; i < global_config.yank_count; ++i) { free(global_config.yanks[i]); } free(global_config.yanks); } int lines_to_yank; int start_point; if (start <= end) { lines_to_yank = end - start + 1; start_point = start - 1; } else { lines_to_yank = start - end + 1; start_point = end - 1; } global_config.yanks = malloc(sizeof(line_t *) * lines_to_yank); global_config.yank_count = lines_to_yank; global_config.yank_is_full_lines = 1; for (int i = 0; i < lines_to_yank; ++i) { global_config.yanks[i] = malloc(sizeof(line_t) + sizeof(char_t) * (env->lines[start_point+i]->available)); global_config.yanks[i]->available = env->lines[start_point+i]->available; global_config.yanks[i]->actual = env->lines[start_point+i]->actual; global_config.yanks[i]->istate = 0; memcpy(&global_config.yanks[i]->text, &env->lines[start_point+i]->text, sizeof(char_t) * (env->lines[start_point+i]->actual)); for (int j = 0; j < global_config.yanks[i]->actual; ++j) { global_config.yanks[i]->text[j].flags = 0; } } } /** * Helper to yank part of a line into a new yank line. */ void yank_partial_line(int yank_no, int line_no, int start_off, int count) { if (start_off + count > env->lines[line_no]->actual) { if (start_off >= env->lines[line_no]->actual) { start_off = env->lines[line_no]->actual; count = 0; } else { count = env->lines[line_no]->actual - start_off; } } global_config.yanks[yank_no] = malloc(sizeof(line_t) + sizeof(char_t) * (count + 1)); global_config.yanks[yank_no]->available = count + 1; /* ensure extra space */ global_config.yanks[yank_no]->actual = count; global_config.yanks[yank_no]->istate = 0; memcpy(&global_config.yanks[yank_no]->text, &env->lines[line_no]->text[start_off], sizeof(char_t) * count); for (int i = 0; i < count; ++i) { global_config.yanks[yank_no]->text[i].flags = 0; } } /** * Yank text... */ void yank_text(int start_line, int start_col, int end_line, int end_col) { if (global_config.yanks) { for (unsigned int i = 0; i < global_config.yank_count; ++i) { free(global_config.yanks[i]); } free(global_config.yanks); } int lines_to_yank = end_line - start_line + 1; int start_point = start_line - 1; global_config.yanks = malloc(sizeof(line_t *) * lines_to_yank); global_config.yank_count = lines_to_yank; global_config.yank_is_full_lines = 0; if (lines_to_yank == 1) { yank_partial_line(0, start_point, start_col - 1, (end_col - start_col + 1)); } else { yank_partial_line(0, start_point, start_col - 1, (env->lines[start_point]->actual - start_col + 1)); /* Yank middle lines */ for (int i = 1; i < lines_to_yank - 1; ++i) { global_config.yanks[i] = malloc(sizeof(line_t) + sizeof(char_t) * (env->lines[start_point+i]->available)); global_config.yanks[i]->available = env->lines[start_point+i]->available; global_config.yanks[i]->actual = env->lines[start_point+i]->actual; global_config.yanks[i]->istate = 0; memcpy(&global_config.yanks[i]->text, &env->lines[start_point+i]->text, sizeof(char_t) * (env->lines[start_point+i]->actual)); for (int j = 0; j < global_config.yanks[i]->actual; ++j) { global_config.yanks[i]->text[j].flags = 0; } } /* Yank end line */ yank_partial_line(lines_to_yank-1, end_line - 1, 0, end_col); } } BIM_ACTION(delete_at_column, ARG_IS_CUSTOM | ACTION_IS_RW, "Delete from the current column backwards (``) or forwards (``)." ,int direction) { if (direction == -1 && env->sel_col <= 0) return; int prev_width = 0; int s = (env->line_no < env->start_line) ? env->line_no : env->start_line; int e = (env->line_no < env->start_line) ? env->start_line : env->line_no; for (int i = s; i <= e; i++) { line_t * line = env->lines[i - 1]; int _x = 0; int col = 1; int j = 0; for (; j < line->actual; ++j) { char_t * c = &line->text[j]; _x += c->display_width; col = j+1; if (_x > env->sel_col) break; prev_width = c->display_width; } if ((direction == -1) && (_x == env->sel_col && j == line->actual)) { line_delete(line, line->actual, i - 1); set_modified(); } else if (_x > env->sel_col) { line_delete(line, col - (direction == -1 ? 1 : 0), i - 1); set_modified(); } } if (direction == -1) { env->sel_col -= prev_width; env->col_no--; } redraw_text(); } void realign_column_cursor(void) { line_t * line = env->lines[env->line_no - 1]; int _x = 0, col = 1, j = 0; for (; j < line->actual; ++j) { char_t * c = &line->text[j]; _x += c->display_width; col = j + 1; if (_x > env->sel_col) break; } env->col_no = col; } BIM_ACTION(column_left, 0, "Move the column cursor left.",void) { if (env->sel_col > 0) { env->sel_col -= 1; env->preferred_column = env->sel_col; /* Figure out where the cursor should be */ realign_column_cursor(); redraw_all(); } } BIM_ACTION(column_right, 0, "Move the column cursor right.",void) { env->sel_col += 1; env->preferred_column = env->sel_col; /* Figure out where the cursor should be */ realign_column_cursor(); redraw_all(); } BIM_ACTION(column_up, 0, "Move the column cursor up.",void) { if (env->line_no > 1 && env->start_line > 1) { env->line_no--; env->start_line--; /* Figure out where the cursor should be */ realign_column_cursor(); place_cursor_actual(); redraw_all(); } } BIM_ACTION(column_down, 0, "Move the column cursor down.",void) { if (env->line_no < env->line_count && env->start_line < env->line_count) { env->line_no++; env->start_line++; /* Figure out where the cursor should be */ realign_column_cursor(); place_cursor_actual(); redraw_all(); } } uint32_t * get_word_under_cursor(void) { /* Figure out size */ int c_before = 0; int c_after = 0; int i = env->col_no; while (i > 0) { if (!simple_keyword_qualifier(env->lines[env->line_no-1]->text[i-1].codepoint)) break; c_before++; i--; } i = env->col_no+1; while (i < env->lines[env->line_no-1]->actual+1) { if (!simple_keyword_qualifier(env->lines[env->line_no-1]->text[i-1].codepoint)) break; c_after++; i++; } if (!c_before && !c_after) return NULL; /* Populate with characters */ uint32_t * out = malloc(sizeof(uint32_t) * (c_before+c_after+1)); int j = 0; while (c_before) { out[j] = env->lines[env->line_no-1]->text[env->col_no-c_before].codepoint; c_before--; j++; } int x = 0; while (c_after) { out[j] = env->lines[env->line_no-1]->text[env->col_no+x].codepoint; j++; x++; c_after--; } out[j] = 0; return out; } BIM_ACTION(search_under_cursor, 0, "Search for the word currently under the cursor." ,void) { if (global_config.search) free(global_config.search); global_config.search = get_word_under_cursor(); /* Find it */ search_next(); } BIM_ACTION(find_character_forward, ARG_IS_PROMPT | ARG_IS_INPUT, "Find a character forward on the current line and place the cursor on (`f`) or before (`t`) it." ,int type, int c) { for (int i = env->col_no+1; i <= env->lines[env->line_no-1]->actual; ++i) { if (env->lines[env->line_no-1]->text[i-1].codepoint == c) { env->col_no = i - !!(type == 't'); place_cursor_actual(); set_preferred_column(); return; } } } BIM_ACTION(find_character_backward, ARG_IS_PROMPT | ARG_IS_INPUT, "Find a character backward on the current line and place the cursor on (`F`) or after (`T`) it." ,int type, int c) { for (int i = env->col_no-1; i >= 1; --i) { if (env->lines[env->line_no-1]->text[i-1].codepoint == c) { env->col_no = i + !!(type == 'T'); place_cursor_actual(); set_preferred_column(); return; } } } /** * Clear the navigation number buffer */ void reset_nav_buffer(int c) { if (c == KEY_TIMEOUT) return; if (nav_buffer && (c < '0' || c > '9')) { nav_buffer = 0; redraw_commandline(); } } /** * Determine if a column + line number are within range of the * current character selection specified by start_line, etc. * * Used to determine how syntax flags should be set when redrawing * selected text in CHAR SELECTION mode. */ int point_in_range(int start_line, int end_line, int start_col, int end_col, int line, int col) { if (start_line == end_line) { if ( end_col < start_col) { int tmp = end_col; end_col = start_col; start_col = tmp; } return (col >= start_col && col <= end_col); } if (start_line > end_line) { int tmp = end_line; end_line = start_line; start_line = tmp; tmp = end_col; end_col = start_col; start_col = tmp; } if (line < start_line || line > end_line) return 0; if (line == start_line) { return col >= start_col; } if (line == end_line) { return col <= end_col; } return 1; } /** * Macro for redrawing selected lines with appropriate highlighting. */ #define _redraw_line(line, force_start_line) \ do { \ if (!(force_start_line) && (line) == env->start_line) break; \ if ((line) > env->line_count + 1) { \ if ((line) - env->offset - 1 < global_config.term_height - global_config.bottom_size - 1) { \ draw_excess_line((line) - env->offset - 1); \ } \ break; \ } \ if ((env->line_no < env->start_line && ((line) < env->line_no || (line) > env->start_line)) || \ (env->line_no > env->start_line && ((line) > env->line_no || (line) < env->start_line)) || \ (env->line_no == env->start_line && (line) != env->start_line)) { \ for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ env->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \ } \ } else { \ for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ env->lines[(line)-1]->text[j].flags |= FLAG_SELECT; \ } \ } \ redraw_line((line)-1); \ } while (0) #define _redraw_line_char(line, force_start_line) \ do { \ if (!(force_start_line) && (line) == env->start_line) break; \ if ((line) > env->line_count + 1) { \ if ((line) - env->offset - 1 < global_config.term_height - global_config.bottom_size - 1) { \ draw_excess_line((line) - env->offset - 1); \ } \ break; \ } \ if ((env->line_no < env->start_line && ((line) < env->line_no || (line) > env->start_line)) || \ (env->line_no > env->start_line && ((line) > env->line_no || (line) < env->start_line)) || \ (env->line_no == env->start_line && (line) != env->start_line)) { \ /* Line is completely outside selection */ \ for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ env->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \ } \ } else { \ if ((line) == env->start_line || (line) == env->line_no) { \ for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ env->lines[(line)-1]->text[j].flags &= ~(FLAG_SELECT); \ } \ } \ for (int j = 0; j < env->lines[(line)-1]->actual; ++j) { \ if (point_in_range(env->start_line, env->line_no,env->start_col, env->col_no, (line), j+1)) { \ env->lines[(line)-1]->text[j].flags |= FLAG_SELECT; \ } \ } \ } \ redraw_line((line)-1); \ } while (0) #define _redraw_line_col(line, force_start_line) \ do {\ if (!(force_start_line) && (line) == env->start_line) break; \ if ((line) > env->line_count + 1) { \ if ((line) - env->offset - 1 < global_config.term_height - global_config.bottom_size - 1) { \ draw_excess_line((line) - env->offset - 1); \ } \ break; \ } \ redraw_line((line)-1); \ } while (0) BIM_ACTION(adjust_indent, ARG_IS_CUSTOM | ACTION_IS_RW, "Adjust the indentation on the selected lines (`` for deeper, `` for shallower)." ,int direction) { int lines_to_cover = 0; int start_point = 0; if (env->start_line <= env->line_no) { start_point = env->start_line - 1; lines_to_cover = env->line_no - env->start_line + 1; } else { start_point = env->line_no - 1; lines_to_cover = env->start_line - env->line_no + 1; } for (int i = 0; i < lines_to_cover; ++i) { if (env->lines[start_point + i]->actual < 1) continue; if (direction == -1) { if (env->tabs) { if (env->lines[start_point + i]->text[0].codepoint == '\t') { line_delete(env->lines[start_point + i],1,start_point+i); _redraw_line(start_point+i+1,1); } } else { if (env->lines[start_point + i]->text[0].codepoint == '\t') { line_delete(env->lines[start_point + i],1,start_point+i); _redraw_line(start_point+i+1,1); } else { for (int j = 0; j < env->tabstop; ++j) { if (env->lines[start_point + i]->text[0].codepoint == ' ') { line_delete(env->lines[start_point + i],1,start_point+i); } } } _redraw_line(start_point+i+1,1); } } else if (direction == 1) { if (env->tabs) { char_t c; c.codepoint = '\t'; c.display_width = env->tabstop; c.flags = FLAG_SELECT; env->lines[start_point + i] = line_insert(env->lines[start_point + i], c, 0, start_point + i); } else { for (int j = 0; j < env->tabstop; ++j) { char_t c; c.codepoint = ' '; c.display_width = 1; c.flags = FLAG_SELECT; env->lines[start_point + i] = line_insert(env->lines[start_point + i], c, 0, start_point + i); } } _redraw_line(start_point+i+1,1); } } if (env->col_no > env->lines[env->line_no-1]->actual) { env->col_no = env->lines[env->line_no-1]->actual; } set_preferred_column(); set_modified(); } void recalculate_selected_lines(void) { int start = env->line_no < env->start_line ? env->line_no : env->start_line; int end = env->line_no > env->start_line ? env->line_no : env->start_line; if (start < 1) start = 1; if (start > env->line_count) start = env->line_count; if (end < 1) end = 1; if (end > env->line_count) end = env->line_count; for (int i = (start > 1) ? (start-1) : (start); i <= end; ++i) { for (int j = 0; j < env->lines[i-1]->actual; j++) { env->lines[i-1]->text[j].flags &= ~(FLAG_SELECT); } } redraw_all(); } BIM_ACTION(enter_line_selection, 0, "Enter line selection mode." ,void) { /* Set mode */ env->mode = MODE_LINE_SELECTION; /* Store start position */ env->start_line = env->line_no; env->prev_line = env->start_line; env->start_col = env->col_no; /* Redraw commandline to get -- LINE SELECTION -- text */ redraw_commandline(); unhighlight_matching_paren(); /* Set this line as selected for syntax highlighting */ for (int j = 0; j < env->lines[env->line_no-1]->actual; ++j) { env->lines[env->line_no-1]->text[j].flags |= FLAG_SELECT; } /* And redraw it */ redraw_line(env->line_no-1); } BIM_ACTION(switch_selection_mode, ARG_IS_CUSTOM, "Swap between LINE and CHAR selection modes." ,int mode) { env->mode = mode; if (mode == MODE_LINE_SELECTION) { int start = env->line_no < env->start_line ? env->line_no : env->start_line; int end = env->line_no > env->start_line ? env->line_no : env->start_line; for (int i = start; i <= end; ++i) { _redraw_line(i, 1); } } else if (mode == MODE_CHAR_SELECTION) { int start = env->line_no < env->start_line ? env->line_no : env->start_line; int end = env->line_no > env->start_line ? env->line_no : env->start_line; for (int i = start; i <= end; ++i) { _redraw_line_char(i, 1); } } } BIM_ACTION(delete_and_yank_lines, 0, "Delete and yank the selected lines." ,void) { yank_lines(); if (env->start_line <= env->line_no) { int lines_to_delete = env->line_no - env->start_line + 1; for (int i = 0; i < lines_to_delete; ++i) { env->lines = remove_line(env->lines, env->start_line-1); } env->line_no = env->start_line; } else { int lines_to_delete = env->start_line - env->line_no + 1; for (int i = 0; i < lines_to_delete; ++i) { env->lines = remove_line(env->lines, env->line_no-1); } } if (env->line_no > env->line_count) { env->line_no = env->line_count; } if (env->col_no > env->lines[env->line_no-1]->actual) { env->col_no = env->lines[env->line_no-1]->actual; } set_preferred_column(); set_modified(); } BIM_ACTION(enter_insert, ACTION_IS_RW, "Enter insert mode." ,void) { env->mode = MODE_INSERT; set_history_break(); } BIM_ACTION(delete_lines_and_enter_insert, ACTION_IS_RW, "Delete and yank the selected lines and then enter insert mode." ,void) { delete_and_yank_lines(); env->lines = add_line(env->lines, env->line_no-1); redraw_text(); env->mode = MODE_INSERT; } BIM_ACTION(replace_chars_in_line, ARG_IS_PROMPT | ACTION_IS_RW, "Replace characters in the selected lines." ,int c) { if (c >= KEY_ESCAPE) { render_error("Invalid key for replacement"); return; } char_t _c = {codepoint_width(c), 0, c}; int start_point = env->start_line < env->line_no ? env->start_line : env->line_no; int end_point = env->start_line < env->line_no ? env->line_no : env->start_line; for (int line = start_point; line <= end_point; ++line) { for (int i = 0; i < env->lines[line-1]->actual; ++i) { line_replace(env->lines[line-1], _c, i, line-1); } } } BIM_ACTION(leave_selection, 0, "Leave selection modes and return to normal mode." ,void) { set_history_break(); env->mode = MODE_NORMAL; recalculate_selected_lines(); } BIM_ACTION(insert_char_at_column, ARG_IS_INPUT | ACTION_IS_RW, "Insert a character on all lines at the current column." ,int c) { char_t _c; _c.codepoint = c; _c.flags = 0; _c.display_width = codepoint_width(c); int inserted_width = 0; /* For each line */ int s = (env->line_no < env->start_line) ? env->line_no : env->start_line; int e = (env->line_no < env->start_line) ? env->start_line : env->line_no; for (int i = s; i <= e; i++) { line_t * line = env->lines[i - 1]; int _x = 0; int col = 1; int j = 0; for (; j < line->actual; ++j) { char_t * c = &line->text[j]; _x += c->display_width; col = j+1; if (_x > env->sel_col) break; } if ((_x == env->sel_col && j == line->actual)) { _x = env->sel_col + 1; col = line->actual + 1; } if (_x > env->sel_col) { line_t * nline = line_insert(line, _c, col - 1, i - 1); if (line != nline) { env->lines[i - 1] = nline; line = nline; } set_modified(); } recalculate_tabs(line); inserted_width = line->text[col-1].display_width; if (i == env->line_no) env->col_no = col + 1; } env->sel_col += inserted_width; env->preferred_column = env->sel_col; place_cursor_actual(); } BIM_ACTION(insert_tab_at_column, ACTION_IS_RW, "Insert an indentation character on multiple lines." ,void) { if (env->tabs) { insert_char_at_column('\t'); } else { for (int i = 0; i < env->tabstop; ++i) { insert_char_at_column(' '); } } } BIM_ACTION(enter_col_insert, ACTION_IS_RW, "Enter column insert mode." ,void) { env->mode = MODE_COL_INSERT; } BIM_ACTION(enter_col_insert_after, ACTION_IS_RW, "Enter column insert mode after the selected column." ,void) { env->sel_col += 1; enter_col_insert(); } BIM_ACTION(enter_col_selection, 0, "Enter column selection mode." ,void) { /* Set mode */ env->mode = MODE_COL_SELECTION; /* Store cursor */ env->start_line = env->line_no; env->sel_col = env->preferred_column; env->prev_line = env->start_line; /* Redraw commandline */ redraw_commandline(); /* Nothing else to do here; rely on cursor */ } BIM_ACTION(yank_characters, 0, "Yank the selected characters to the paste buffer." ,void) { int end_line = env->line_no; int end_col = env->col_no; if (env->start_line == end_line) { if (env->start_col > end_col) { int tmp = env->start_col; env->start_col = end_col; end_col = tmp; } } else if (env->start_line > end_line) { int tmp = env->start_line; env->start_line = end_line; end_line = tmp; tmp = env->start_col; env->start_col = end_col; end_col = tmp; } yank_text(env->start_line, env->start_col, end_line, end_col); } BIM_ACTION(delete_and_yank_chars, ACTION_IS_RW, "Delete and yank the selected characters." ,void) { int end_line = env->line_no; int end_col = env->col_no; if (env->start_line == end_line) { if (env->start_col > end_col) { int tmp = env->start_col; env->start_col = end_col; end_col = tmp; } yank_text(env->start_line, env->start_col, end_line, end_col); for (int i = env->start_col; i <= end_col; ++i) { line_delete(env->lines[env->start_line-1], env->start_col, env->start_line - 1); } env->col_no = env->start_col; } else { if (env->start_line > end_line) { int tmp = env->start_line; env->start_line = end_line; end_line = tmp; tmp = env->start_col; env->start_col = end_col; end_col = tmp; } /* Copy lines */ yank_text(env->start_line, env->start_col, end_line, end_col); /* Delete lines */ for (int i = env->start_line+1; i < end_line; ++i) { env->lines = remove_line(env->lines, env->start_line); } /* end_line is no longer valid; should be start_line+1*/ /* Delete from env->start_col forward */ int tmp = env->lines[env->start_line-1]->actual; for (int i = env->start_col; i <= tmp; ++i) { line_delete(env->lines[env->start_line-1], env->start_col, env->start_line - 1); } for (int i = 1; i <= end_col; ++i) { line_delete(env->lines[env->start_line], 1, env->start_line); } /* Merge start and end lines */ merge_lines(env->lines, env->start_line); env->line_no = env->start_line; env->col_no = env->start_col; } if (env->line_no > env->line_count) { env->line_no = env->line_count; } set_preferred_column(); set_modified(); } BIM_ACTION(delete_chars_and_enter_insert, ACTION_IS_RW, "Delete and yank the selected characters and then enter insert mode." ,void) { delete_and_yank_chars(); redraw_text(); enter_insert(); } BIM_ACTION(replace_chars, ARG_IS_PROMPT | ACTION_IS_RW, "Replace the selected characters." ,int c) { if (c >= KEY_ESCAPE) { render_error("Invalid key for replacement"); return; } char_t _c = {codepoint_width(c), 0, c}; /* This should probably be a function line "do_over_range" or something */ if (env->start_line == env->line_no) { int s = (env->start_col < env->col_no) ? env->start_col : env->col_no; int e = (env->start_col < env->col_no) ? env->col_no : env->start_col; for (int i = s; i <= e; ++i) { line_replace(env->lines[env->start_line-1], _c, i-1, env->start_line-1); } redraw_text(); } else { if (env->start_line < env->line_no) { for (int s = env->start_col-1; s < env->lines[env->start_line-1]->actual; ++s) { line_replace(env->lines[env->start_line-1], _c, s, env->start_line-1); } for (int line = env->start_line + 1; line < env->line_no; ++line) { for (int i = 0; i < env->lines[line-1]->actual; ++i) { line_replace(env->lines[line-1], _c, i, line-1); } } for (int s = 0; s < env->col_no; ++s) { line_replace(env->lines[env->line_no-1], _c, s, env->line_no-1); } } else { for (int s = env->col_no-1; s < env->lines[env->line_no-1]->actual; ++s) { line_replace(env->lines[env->line_no-1], _c, s, env->line_no-1); } for (int line = env->line_no + 1; line < env->start_line; ++line) { for (int i = 0; i < env->lines[line-1]->actual; ++i) { line_replace(env->lines[line-1], _c, i, line-1); } } for (int s = 0; s < env->start_col; ++s) { line_replace(env->lines[env->start_line-1], _c, s, env->start_line-1); } } } } BIM_ACTION(enter_char_selection, 0, "Enter character selection mode." ,void) { /* Set mode */ env->mode = MODE_CHAR_SELECTION; /* Set cursor positions */ env->start_line = env->line_no; env->start_col = env->col_no; env->prev_line = env->start_line; /* Redraw commandline for -- CHAR SELECTION -- */ redraw_commandline(); unhighlight_matching_paren(); /* Select single character */ env->lines[env->line_no-1]->text[env->col_no-1].flags |= FLAG_SELECT; redraw_line(env->line_no-1); } BIM_ACTION(insert_at_end_of_selection, ACTION_IS_RW, "Move the cursor to the end of the selection and enter insert mode." ,void) { recalculate_selected_lines(); if (env->line_no == env->start_line) { env->col_no = env->col_no > env->start_col ? env->col_no + 1 : env->start_col + 1; } else if (env->line_no < env->start_line) { env->col_no = env->start_col + 1; env->line_no = env->start_line; } else { env->col_no += 1; } env->mode = MODE_INSERT; } void free_completion_match(struct completion_match * match) { if (match->string) free(match->string); if (match->file) free(match->file); if (match->search) free(match->search); } /** * Read ctags file to find matches for a symbol */ int read_tags(uint32_t * comp, struct completion_match **matches, int * matches_count, int complete_match) { int _matches_len = 4; int *matches_len = &_matches_len; *matches_count = 0; *matches = malloc(sizeof(struct completion_match) * (*matches_len)); FILE * tags = fopen("tags","r"); if (tags) { char tmp[4096]; /* max line */ while (!feof(tags) && fgets(tmp, 4096, tags)) { if (tmp[0] == '!') continue; int i = 0; while (comp[i] && comp[i] == (unsigned int)tmp[i]) i++; if (comp[i] == '\0') { if (complete_match && tmp[i] != '\t') continue; int j = i; while (tmp[j] != '\t' && tmp[j] != '\n' && tmp[j] != '\0') j++; tmp[j] = '\0'; j++; char * file = &tmp[j]; while (tmp[j] != '\t' && tmp[j] != '\n' && tmp[j] != '\0') j++; tmp[j] = '\0'; j++; char * search = &tmp[j]; while (!(tmp[j] == '/' && tmp[j+1] == ';' && tmp[j+2] == '"' && tmp[j+3] == '\t') /* /normal searches/ */ && !(tmp[j] == ';' && tmp[j+1] == '"' && tmp[j+2] == '\t') /* Old ctags line number searches */ && (tmp[j] != '\n' && tmp[j] != '\0')) j++; tmp[j] = '\0'; j++; add_match(tmp,file,search); } } fclose(tags); } if (env->syntax && env->syntax->completion_matcher) { env->syntax->completion_matcher(comp,matches,matches_count,complete_match,matches_len, env); } return 0; } /** * Draw an autocomplete popover with matches. */ void draw_completion_matches(uint32_t * tmp, struct completion_match *matches, int matches_count, int index) { int original_length = 0; while (tmp[original_length]) original_length++; int max_width = 0; for (int i = 0; i < matches_count; ++i) { /* TODO unicode width */ unsigned int my_width = strlen(matches[i].string) + (matches[i].file ? strlen(matches[i].file) + 1 : 0); if (my_width > (unsigned int)max_width) { max_width = my_width; } } /* Figure out how much space we have to display the window */ int cursor_y = env->line_no - env->offset + global_config.tabs_visible; int max_y = global_config.term_height - global_config.bottom_size - cursor_y; /* Find a good place to put the box horizontally */ int num_size = num_width() + gutter_width(); int x = num_size + 1 - env->coffset; /* Determine where the cursor is physically */ for (int i = 0; i < env->col_no - 1 - original_length; ++i) { char_t * c = &env->lines[env->line_no-1]->text[i]; x += c->display_width; } int box_width = max_width; int box_x = x; int box_y = cursor_y + 1; if (max_width > env->width - num_width() - gutter_width()) { box_width = env->width - num_width() - gutter_width(); box_x = num_width() + gutter_width() + 1; } else if (env->width - x < max_width) { box_width = max_width; box_x = env->width - max_width; } int max_count = (max_y < matches_count) ? max_y - 1 : matches_count; for (int x = index; x < max_count+index; ++x) { int i = x % matches_count; place_cursor(box_x + env->left, box_y+x-index); set_colors(COLOR_KEYWORD, COLOR_STATUS_BG); /* TODO wide characters */ int match_width = strlen(matches[i].string); int file_width = matches[i].file ? strlen(matches[i].file) : 0; for (int j = 0; j < box_width; ++j) { if (j == original_length) set_colors(i == index ? COLOR_NUMERAL : COLOR_STATUS_FG, COLOR_STATUS_BG); if (j == match_width) set_colors(COLOR_TYPE, COLOR_STATUS_BG); if (j < match_width) printf("%c", matches[i].string[j]); else if (j > match_width && j - match_width - 1 < file_width) printf("%c", matches[i].file[j-match_width-1]); else printf(" "); } } if (max_count == 0) { place_cursor(box_x + env->left, box_y); set_colors(COLOR_STATUS_FG, COLOR_STATUS_BG); printf(" (no matches) "); } else if (max_count != matches_count) { place_cursor(box_x + env->left, box_y+max_count); set_colors(COLOR_STATUS_FG, COLOR_STATUS_BG); printf(" (%d more) ", matches_count-max_count); } } /** * Autocomplete words (function/variable names, etc.) in input mode. */ int omni_complete(int quit_quietly_on_none) { int retval = 0; int index = 0; int c; int (*qualifier)(int c) = simple_keyword_qualifier; if (env->syntax && env->syntax->completion_qualifier) { qualifier = env->syntax->completion_qualifier; } /* Pull the word from before the cursor */ int c_before = 0; int i = env->col_no-1; while (i > 0) { int c = env->lines[env->line_no-1]->text[i-1].codepoint; if (!qualifier(c)) break; c_before++; i--; } if (!c_before && quit_quietly_on_none) return 0; /* Populate with characters */ uint32_t * tmp = malloc(sizeof(uint32_t) * (c_before+1)); int j = 0; while (c_before) { tmp[j] = env->lines[env->line_no-1]->text[env->col_no-c_before-1].codepoint; c_before--; j++; } tmp[j] = 0; /* * TODO matches should probably be a struct with more data than just * the matching string; maybe offset where the needle was found, * class information, source file information - anything we can extract * from ctags, but also other information for other sources of completion. */ struct completion_match *matches; int matches_count; /* TODO just reading ctags is rather mediocre; can we do something cool here? */ if (read_tags(tmp, &matches, &matches_count, 0)) { goto _completion_done; } /* Draw box with matches at cursor-width(tmp) */ if (quit_quietly_on_none && matches_count == 0) { free(tmp); free(matches); return 0; } draw_completion_matches(tmp, matches, matches_count, 0); _completion_done: place_cursor_actual(); while (1) { c = bim_getch(); if (c == -1) continue; if (matches_count < 1) { redraw_all(); break; } if (c == 15) { index = (index + 1) % matches_count; draw_completion_matches(tmp, matches, matches_count, index); place_cursor_actual(); continue; } else if (c == '\t') { for (unsigned int i = j; i < strlen(matches[index].string); ++i) { insert_char(matches[index].string[i]); } set_preferred_column(); redraw_text(); place_cursor_actual(); goto _finish_completion; } else if (isgraph(c) && c != '}') { /* insert and continue matching */ insert_char(c); set_preferred_column(); redraw_text(); place_cursor_actual(); retval = 1; goto _finish_completion; } else if (c == DELETE_KEY || c == BACKSPACE_KEY) { delete_at_cursor(); set_preferred_column(); redraw_text(); place_cursor_actual(); retval = 1; goto _finish_completion; } /* TODO: Keyboard navigation of the matches list would be nice */ redraw_all(); break; } bim_unget(c); _finish_completion: for (int i = 0; i < matches_count; ++i) { free_completion_match(&matches[i]); } free(matches); free(tmp); return retval; } /** * Set the search string from a UTF-8 sequence. * Since the search string is normally a series of codepoints, this saves * some effort when trying to search for things we pulled from the outside world. * (eg., ctags search terms) */ static void set_search_from_bytes(char * bytes) { if (global_config.search) free(global_config.search); global_config.search = malloc(sizeof(uint32_t) * (strlen(bytes) * 2 + 1)); uint32_t * s = global_config.search; char * tmp = bytes; uint32_t c, istate = 0; while (*tmp) { if (strchr("\\.()[]+*?", *tmp)) { *s++ = '\\'; *s++ = *tmp; *s = 0; tmp++; continue; } if (!decode(&istate, &c, *tmp)) { *s = c; s++; *s = 0; } else if (istate == UTF8_REJECT) { istate = 0; } tmp++; } } static void _perform_correct_search(struct completion_match * matches, int i) { if (matches[i].search[0] == '/') { set_search_from_bytes(&matches[i].search[1]); search_next(); } else { goto_line(atoi(matches[i].search)); } } BIM_ACTION(goto_definition, 0, "Jump to the definition of the word under under cursor." ,void) { uint32_t * word = get_word_under_cursor(); if (!word) { render_error("No match"); return; } struct completion_match *matches; int matches_count; if (read_tags(word, &matches, &matches_count, 1)) { render_error("No tags file"); goto _done; } if (!matches_count) { render_error("No match"); goto _done; } if (env->file_name && !strcmp(matches[0].file, env->file_name)) { _perform_correct_search(matches, 0); } else { /* Check if there were other matches that are in this file */ for (int i =1; env->file_name && i < matches_count; ++i) { if (!strcmp(matches[i].file, env->file_name)) { _perform_correct_search(matches, i); goto _done; } } /* Check buffers */ for (int i = 0; i < buffers_len; ++i) { if (buffers[i]->file_name && !strcmp(matches[0].file,buffers[i]->file_name)) { if (left_buffer && buffers[i] != left_buffer && buffers[i] != right_buffer) unsplit(); env = buffers[i]; redraw_tabbar(); _perform_correct_search(matches, i); goto _done; } } /* Okay, let's try opening */ buffer_t * old_buf = env; open_file(matches[0].file); if (env != old_buf) { _perform_correct_search(matches, 0); } else { render_error("Could not locate file containing definition"); } } _done: for (int i = 0; i < matches_count; ++i) { free_completion_match(&matches[i]); } free(matches); free(word); } /** * Read one codepoint, with verbatim support. */ int read_one_character(char * message) { /* Read one character and replace */ if (!global_config.overlay_mode) { render_commandline_message(message); place_cursor_actual(); } int c; while ((c = bim_getkey(DEFAULT_KEY_WAIT))) { if (c == KEY_TIMEOUT) continue; if (c == KEY_CTRL_V) { if (!global_config.overlay_mode) { render_commandline_message(message); printf(" ^V"); place_cursor_actual(); } while ((c = bim_getch()) == -1); break; } break; } redraw_commandline(); return c; } int read_one_byte(char * message) { if (!global_config.overlay_mode) { render_commandline_message(message); place_cursor_actual(); } int c; while ((c = bim_getch())) { if (c == -1) continue; break; } redraw_commandline(); return c; } BIM_ACTION(cursor_left_with_wrap, 0, "Move the cursor one position left, wrapping to the previous line." ,void) { if (env->line_no > 1 && env->col_no == 1) { env->line_no--; env->col_no = env->lines[env->line_no-1]->actual; set_preferred_column(); place_cursor_actual(); } else { cursor_left(); } } BIM_ACTION(prepend_and_insert, ACTION_IS_RW, "Insert a new line before the current line and enter insert mode." ,void) { set_history_break(); env->lines = add_line(env->lines, env->line_no-1); env->col_no = 1; add_indent(env->line_no-1,env->line_no,0); if (env->highlighting_paren && env->highlighting_paren > env->line_no) env->highlighting_paren++; redraw_text(); set_preferred_column(); set_modified(); place_cursor_actual(); env->mode = MODE_INSERT; } BIM_ACTION(append_and_insert, ACTION_IS_RW, "Insert a new line after the current line and enter insert mode." ,void) { set_history_break(); unhighlight_matching_paren(); env->lines = add_line(env->lines, env->line_no); env->col_no = 1; env->line_no += 1; add_indent(env->line_no-1,env->line_no-2,0); set_preferred_column(); if (env->line_no > env->offset + global_config.term_height - global_config.bottom_size - 1) { env->offset += 1; } if (env->highlighting_paren && env->highlighting_paren > env->line_no) env->highlighting_paren++; redraw_text(); set_modified(); place_cursor_actual(); env->mode = MODE_INSERT; } BIM_ACTION(insert_after_cursor, ACTION_IS_RW, "Place the cursor after the selected character and enter insert mode." ,void) { if (env->col_no < env->lines[env->line_no-1]->actual + 1) { env->col_no += 1; } enter_insert(); } BIM_ACTION(delete_forward, ACTION_IS_RW, "Delete the character under the cursor." ,void) { if (env->col_no <= env->lines[env->line_no-1]->actual) { line_delete(env->lines[env->line_no-1], env->col_no, env->line_no-1); redraw_text(); } else if (env->col_no == env->lines[env->line_no-1]->actual + 1 && env->line_count > env->line_no) { merge_lines(env->lines, env->line_no); redraw_text(); } set_modified(); redraw_statusbar(); place_cursor_actual(); } BIM_ACTION(delete_forward_and_insert, ACTION_IS_RW, "Delete the character under the cursor and enter insert mode." ,void) { set_history_break(); delete_forward(); env->mode = MODE_INSERT; } BIM_ACTION(paste, ARG_IS_CUSTOM | ACTION_IS_RW, "Paste yanked text before (`P`) or after (`p`) the cursor." ,int direction) { if (global_config.yanks) { env->slowop = 1; if (!global_config.yank_is_full_lines) { /* Handle P for paste before, p for past after */ int target_column = (direction == -1 ? (env->col_no) : (env->col_no+1)); if (target_column > env->lines[env->line_no-1]->actual + 1) { target_column = env->lines[env->line_no-1]->actual + 1; } if (global_config.yank_count > 1) { /* Spit the current line at the current position */ env->lines = split_line(env->lines, env->line_no - 1, target_column - 1); /* Split after */ } /* Insert first line at current position */ for (int i = 0; i < global_config.yanks[0]->actual; ++i) { env->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); } if (global_config.yank_count > 1) { /* Insert full lines */ for (unsigned int i = 1; i < global_config.yank_count - 1; ++i) { env->lines = add_line(env->lines, env->line_no); } for (unsigned int i = 1; i < global_config.yank_count - 1; ++i) { replace_line(env->lines, env->line_no + i - 1, global_config.yanks[i]); } /* Insert characters from last line into (what was) the next line */ for (int i = 0; i < global_config.yanks[global_config.yank_count-1]->actual; ++i) { env->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); } } } else { /* Insert full lines */ for (unsigned int i = 0; i < global_config.yank_count; ++i) { env->lines = add_line(env->lines, env->line_no - (direction == -1 ? 1 : 0)); } for (unsigned int i = 0; i < global_config.yank_count; ++i) { replace_line(env->lines, env->line_no - (direction == -1 ? 1 : 0) + i, global_config.yanks[i]); } } env->slowop = 0; schedule_complete_recalc(); /* Recalculate whole document syntax */ if (direction == 1) { if (global_config.yank_is_full_lines) { env->line_no += 1; } else { if (global_config.yank_count == 1) { env->col_no = env->col_no + global_config.yanks[0]->actual; } else { env->line_no = env->line_no + global_config.yank_count - 1; env->col_no = global_config.yanks[global_config.yank_count-1]->actual; } } } if (global_config.yank_is_full_lines) { env->col_no = 1; for (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) { if (!is_whitespace(env->lines[env->line_no-1]->text[i].codepoint)) { env->col_no = i + 1; break; } } } set_history_break(); set_modified(); redraw_all(); } } BIM_ACTION(insert_at_end, ACTION_IS_RW, "Move the cursor to the end of the current line and enter insert mode." ,void) { env->col_no = env->lines[env->line_no-1]->actual+1; env->mode = MODE_INSERT; set_history_break(); } BIM_ACTION(enter_replace, ACTION_IS_RW, "Enter replace mode." ,void) { env->mode = MODE_REPLACE; set_history_break(); } BIM_ACTION(toggle_numbers, 0, "Toggle the display of line numbers." ,void) { env->numbers = !env->numbers; redraw_all(); place_cursor_actual(); } BIM_ACTION(toggle_gutter, 0, "Toggle the display of the revision status gutter." ,void) { env->gutter = !env->gutter; redraw_all(); place_cursor_actual(); } BIM_ACTION(toggle_indent, 0, "Toggle smart indentation." ,void) { env->indent = !env->indent; redraw_statusbar(); place_cursor_actual(); } BIM_ACTION(toggle_smartcomplete, 0, "Toggle smart completion." ,void) { global_config.smart_complete = !global_config.smart_complete; redraw_statusbar(); place_cursor_actual(); } BIM_ACTION(expand_split_right, 0, "Move the view split divider to the right." ,void) { global_config.split_percent += 1; update_split_size(); redraw_all(); } BIM_ACTION(expand_split_left, 0, "Move the view split divider to the left." ,void) { global_config.split_percent -= 1; update_split_size(); redraw_all(); } BIM_ACTION(go_page_up, 0, "Jump up a screenfull." ,void) { int destination = env->line_no - (global_config.term_height - 6); if (destination < 1) destination = 1; goto_line(destination); } BIM_ACTION(go_page_down, 0, "Jump down a screenfull." ,void) { goto_line(env->line_no + (global_config.term_height - 6)); } BIM_ACTION(jump_to_matching_bracket, 0, "Find and jump to the matching bracket for the character under the cursor." ,void) { recalculate_selected_lines(); /* Search forward first */ for (int i = env->col_no; i <= env->lines[env->line_no-1]->actual; ++i) { if (is_paren(env->lines[env->line_no-1]->text[i-1].codepoint)) { env->col_no = i; break; } } /* Then find match */ int paren_line = -1, paren_col = -1; find_matching_paren(&paren_line, &paren_col, 1); if (paren_line != -1) { env->line_no = paren_line; env->col_no = paren_col; set_preferred_column(); place_cursor_actual(); redraw_statusbar(); } } BIM_ACTION(jump_to_previous_blank, 0, "Jump to the preceding blank line before the cursor." ,void) { env->col_no = 1; if (env->line_no == 1) return; do { env->line_no--; if (env->lines[env->line_no-1]->actual == 0) break; } while (env->line_no > 1); set_preferred_column(); redraw_statusbar(); } BIM_ACTION(jump_to_next_blank, 0, "Jump to the next blank line after the cursor." ,void) { env->col_no = 1; if (env->line_no == env->line_count) return; do { env->line_no++; if (env->lines[env->line_no-1]->actual == 0) break; } while (env->line_no < env->line_count); set_preferred_column(); redraw_statusbar(); } BIM_ACTION(first_non_whitespace, 0, "Jump to the first non-whitespace character in the current line." ,void) { for (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) { if (!is_whitespace(env->lines[env->line_no-1]->text[i].codepoint)) { env->col_no = i + 1; break; } } set_preferred_column(); redraw_statusbar(); } BIM_ACTION(next_line_non_whitespace, 0, "Jump to the first non-whitespace character in the next next line." ,void) { if (env->line_no < env->line_count) { env->line_no++; env->col_no = 1; } else { return; } first_non_whitespace(); } BIM_ACTION(smart_backspace, ACTION_IS_RW, "Delete the preceding character, with special handling for indentation." ,void) { if (!env->tabs && env->col_no > 1) { int i; for (i = 0; i < env->col_no-1; ++i) { if (!is_whitespace(env->lines[env->line_no-1]->text[i].codepoint)) break; } if (i == env->col_no-1) { /* Backspace until aligned */ delete_at_cursor(); while (env->col_no > 1 && (env->col_no-1) % env->tabstop) { delete_at_cursor(); } return; } } delete_at_cursor(); } BIM_ACTION(perform_omni_completion, ACTION_IS_RW, "(temporary) Perform smart symbol completion from ctags." ,void) { /* This should probably be a submode */ while (omni_complete(0) == 1); } BIM_ACTION(smart_tab, ACTION_IS_RW, "Insert a tab or spaces depending on indent mode. (Use ^V to guarantee a literal tab)" ,void) { if (env->tabs) { insert_char('\t'); } else { for (int i = 0; i < env->tabstop; ++i) { insert_char(' '); } } } BIM_ACTION(smart_comment_end, ARG_IS_INPUT | ACTION_IS_RW, "Insert a `/` ending a C-style comment." ,int c) { /* smart *end* of comment anyway */ if (env->indent) { if ((env->lines[env->line_no-1]->text[env->col_no-2].flags & 0x1F) == FLAG_COMMENT && (env->lines[env->line_no-1]->text[env->col_no-2].codepoint == ' ') && (env->col_no > 3) && (env->lines[env->line_no-1]->text[env->col_no-3].codepoint == '*')) { env->col_no--; replace_char('/'); env->col_no++; place_cursor_actual(); return; } } insert_char(c); } BIM_ACTION(smart_brace_end, ARG_IS_INPUT | ACTION_IS_RW, "Insert a closing brace and smartly position it if it is the first character on a line." ,int c) { if (env->indent) { int was_whitespace = 1; for (int i = 0; i < env->lines[env->line_no-1]->actual; ++i) { if (env->lines[env->line_no-1]->text[i].codepoint != ' ' && env->lines[env->line_no-1]->text[i].codepoint != '\t') { was_whitespace = 0; break; } } insert_char(c); if (was_whitespace) { int line = -1, col = -1; env->col_no--; find_matching_paren(&line,&col, 1); if (line != -1) { line = find_brace_line_start(line, col); while (env->lines[env->line_no-1]->actual) { line_delete(env->lines[env->line_no-1], env->lines[env->line_no-1]->actual, env->line_no-1); } add_indent(env->line_no-1,line-1,1); env->col_no = env->lines[env->line_no-1]->actual + 1; insert_char(c); } } set_preferred_column(); return; } insert_char(c); } BIM_ACTION(enter_line_selection_and_cursor_up, 0, "Enter line selection and move the cursor up one line." ,void) { enter_line_selection(); cursor_up(); } BIM_ACTION(enter_line_selection_and_cursor_down, 0, "Enter line selection and move the cursor down one line." ,void) { enter_line_selection(); cursor_down(); } BIM_ACTION(shift_horizontally, ARG_IS_CUSTOM, "Shift the current line or screen view horizontally, depending on settings." ,int amount) { env->coffset += amount; if (env->coffset < 0) env->coffset = 0; redraw_text(); } static int state_before_paste = 0; static int line_before_paste = 0; BIM_ACTION(paste_begin, 0, "Begin bracketed paste; disable indentation, completion, etc.",void) { if (global_config.smart_complete) state_before_paste |= 0x01; if (env->indent) state_before_paste |= 0x02; global_config.smart_complete = 0; env->indent = 0; env->slowop = 1; line_before_paste = env->line_no; } BIM_ACTION(paste_end, 0, "End bracketed paste; restore indentation, completion, etc.",void) { if (state_before_paste & 0x01) global_config.smart_complete = 1; if (state_before_paste & 0x02) env->indent = 1; env->slowop = 0; int line_to_recalculate = (line_before_paste > 1 ? line_before_paste - 1 : 0); recalculate_syntax(env->lines[line_to_recalculate], line_to_recalculate); redraw_all(); } #define MAP_ACTION(key, func, opts, arg) {key, opts, {{(uintptr_t)func, arg}}} struct action_map _NORMAL_MAP[] = { MAP_ACTION(KEY_BACKSPACE, cursor_left_with_wrap, opt_rep, 0), MAP_ACTION('V', enter_line_selection, 0, 0), MAP_ACTION('v', enter_char_selection, 0, 0), MAP_ACTION(KEY_CTRL_V, enter_col_selection, 0, 0), MAP_ACTION('O', prepend_and_insert, opt_rw, 0), MAP_ACTION('o', append_and_insert, opt_rw, 0), MAP_ACTION('a', insert_after_cursor, opt_rw, 0), MAP_ACTION('s', delete_forward_and_insert, opt_rw, 0), MAP_ACTION('x', delete_forward, opt_rep | opt_rw, 0), MAP_ACTION('P', paste, opt_arg | opt_rw, -1), MAP_ACTION('p', paste, opt_arg | opt_rw, 1), MAP_ACTION('r', replace_char, opt_char | opt_rw, 0), MAP_ACTION('A', insert_at_end, opt_rw, 0), MAP_ACTION('u', undo_history, opt_rw, 0), MAP_ACTION(KEY_CTRL_R, redo_history, opt_rw, 0), MAP_ACTION(KEY_CTRL_L, redraw_all, 0, 0), MAP_ACTION(KEY_CTRL_G, goto_definition, 0, 0), MAP_ACTION('i', enter_insert, opt_rw, 0), MAP_ACTION('R', enter_replace, opt_rw, 0), MAP_ACTION(KEY_SHIFT_UP, enter_line_selection_and_cursor_up, 0, 0), MAP_ACTION(KEY_SHIFT_DOWN, enter_line_selection_and_cursor_down, 0, 0), MAP_ACTION(KEY_ALT_UP, previous_tab, 0, 0), MAP_ACTION(KEY_ALT_DOWN, next_tab, 0, 0), MAP_ACTION(KEY_CTRL_UNDERSCORE, start_file_search, 0, 0), MAP_ACTION(-1, NULL, 0, 0), }; struct action_map _INSERT_MAP[] = { MAP_ACTION(KEY_ESCAPE, leave_insert, 0, 0), MAP_ACTION(KEY_DELETE, delete_forward, 0, 0), MAP_ACTION(KEY_CTRL_C, leave_insert, 0, 0), MAP_ACTION(KEY_BACKSPACE, smart_backspace, 0, 0), MAP_ACTION(KEY_ENTER, insert_line_feed, 0, 0), MAP_ACTION(KEY_CTRL_O, perform_omni_completion, 0, 0), MAP_ACTION(KEY_CTRL_V, insert_char, opt_byte, 0), MAP_ACTION(KEY_CTRL_W, delete_word, 0, 0), MAP_ACTION('\t', smart_tab, 0, 0), MAP_ACTION('/', smart_comment_end, opt_arg, '/'), MAP_ACTION('}', smart_brace_end, opt_arg, '}'), MAP_ACTION(KEY_PASTE_BEGIN, paste_begin, 0, 0), MAP_ACTION(KEY_PASTE_END, paste_end, 0, 0), MAP_ACTION(-1, NULL, 0, 0), }; struct action_map _REPLACE_MAP[] = { MAP_ACTION(KEY_ESCAPE, leave_insert, 0, 0), MAP_ACTION(KEY_DELETE, delete_forward, 0, 0), MAP_ACTION(KEY_BACKSPACE, cursor_left_with_wrap, 0, 0), MAP_ACTION(KEY_ENTER, insert_line_feed, 0, 0), MAP_ACTION(-1, NULL, 0, 0), }; struct action_map _LINE_SELECTION_MAP[] = { MAP_ACTION(KEY_ESCAPE, leave_selection, 0, 0), MAP_ACTION(KEY_CTRL_C, leave_selection, 0, 0), MAP_ACTION('V', leave_selection, 0, 0), MAP_ACTION('v', switch_selection_mode, opt_arg, MODE_CHAR_SELECTION), MAP_ACTION('y', yank_lines, opt_norm, 0), MAP_ACTION(KEY_BACKSPACE, cursor_left_with_wrap, 0, 0), MAP_ACTION('\t', adjust_indent, opt_arg | opt_rw, 1), MAP_ACTION(KEY_SHIFT_TAB, adjust_indent, opt_arg | opt_rw, -1), MAP_ACTION('D', delete_and_yank_lines, opt_rw | opt_norm, 0), MAP_ACTION('d', delete_and_yank_lines, opt_rw | opt_norm, 0), MAP_ACTION('x', delete_and_yank_lines, opt_rw | opt_norm, 0), MAP_ACTION('s', delete_lines_and_enter_insert, opt_rw, 0), MAP_ACTION('r', replace_chars_in_line, opt_char | opt_rw, 0), MAP_ACTION(KEY_SHIFT_UP, cursor_up, 0, 0), MAP_ACTION(KEY_SHIFT_DOWN, cursor_down, 0, 0), MAP_ACTION(-1, NULL, 0, 0), }; struct action_map _CHAR_SELECTION_MAP[] = { MAP_ACTION(KEY_ESCAPE, leave_selection, 0, 0), MAP_ACTION(KEY_CTRL_C, leave_selection, 0, 0), MAP_ACTION('v', leave_selection, 0, 0), MAP_ACTION('V', switch_selection_mode, opt_arg, MODE_LINE_SELECTION), MAP_ACTION('y', yank_characters, opt_norm, 0), MAP_ACTION(KEY_BACKSPACE, cursor_left_with_wrap, 0, 0), MAP_ACTION('D', delete_and_yank_chars, opt_rw | opt_norm, 0), MAP_ACTION('d', delete_and_yank_chars, opt_rw | opt_norm, 0), MAP_ACTION('x', delete_and_yank_chars, opt_rw | opt_norm, 0), MAP_ACTION('s', delete_chars_and_enter_insert, opt_rw, 0), MAP_ACTION('r', replace_chars, opt_char | opt_rw, 0), MAP_ACTION('A', insert_at_end_of_selection, opt_rw, 0), MAP_ACTION(-1, NULL, 0, 0), }; struct action_map _COL_SELECTION_MAP[] = { MAP_ACTION(KEY_ESCAPE, leave_selection, 0, 0), MAP_ACTION(KEY_CTRL_C, leave_selection, 0, 0), MAP_ACTION(KEY_CTRL_V, leave_selection, 0, 0), MAP_ACTION('I', enter_col_insert, opt_rw, 0), MAP_ACTION('a', enter_col_insert_after, opt_rw, 0), MAP_ACTION('d', delete_at_column, opt_arg | opt_rw, 1), MAP_ACTION(-1, NULL, 0, 0), }; struct action_map _COL_INSERT_MAP[] = { MAP_ACTION(KEY_ESCAPE, leave_selection, 0, 0), MAP_ACTION(KEY_CTRL_C, leave_selection, 0, 0), MAP_ACTION(KEY_BACKSPACE, delete_at_column, opt_arg, -1), MAP_ACTION(KEY_DELETE, delete_at_column, opt_arg, 1), MAP_ACTION(KEY_ENTER, NULL, 0, 0), MAP_ACTION(KEY_CTRL_W, NULL, 0, 0), MAP_ACTION(KEY_CTRL_V, insert_char_at_column, opt_char, 0), MAP_ACTION('\t', insert_tab_at_column, 0, 0), MAP_ACTION(KEY_LEFT, column_left, 0, 0), MAP_ACTION(KEY_RIGHT, column_right, 0, 0), MAP_ACTION(KEY_UP, column_up, 0, 0), MAP_ACTION(KEY_DOWN, column_down, 0, 0), MAP_ACTION(-1, NULL, 0, 0), }; struct action_map _NAVIGATION_MAP[] = { /* Common navigation */ MAP_ACTION(KEY_CTRL_B, go_page_up, opt_rep, 0), MAP_ACTION(KEY_CTRL_F, go_page_down, opt_rep, 0), MAP_ACTION(':', enter_command, 0, 0), MAP_ACTION('/', enter_search, opt_arg, 1), MAP_ACTION('?', enter_search, opt_arg, 0), MAP_ACTION('n', search_next, opt_rep, 0), MAP_ACTION('N', search_prev, opt_rep, 0), MAP_ACTION('j', cursor_down, opt_rep, 0), MAP_ACTION('k', cursor_up, opt_rep, 0), MAP_ACTION('h', cursor_left, opt_rep, 0), MAP_ACTION('l', cursor_right, opt_rep, 0), MAP_ACTION('b', word_left, opt_rep, 0), MAP_ACTION('w', word_right, opt_rep, 0), MAP_ACTION('B', big_word_left, opt_rep, 0), MAP_ACTION('W', big_word_right, opt_rep, 0), MAP_ACTION('<', shift_horizontally, opt_arg, -1), MAP_ACTION('>', shift_horizontally, opt_arg, 1), MAP_ACTION('f', find_character_forward, opt_rep | opt_arg | opt_char, 'f'), MAP_ACTION('F', find_character_backward, opt_rep | opt_arg | opt_char, 'F'), MAP_ACTION('t', find_character_forward, opt_rep | opt_arg | opt_char, 't'), MAP_ACTION('T', find_character_backward, opt_rep | opt_arg | opt_char, 'T'), MAP_ACTION('G', goto_line, opt_nav, 0), MAP_ACTION('*', search_under_cursor, 0, 0), MAP_ACTION(' ', go_page_down, opt_rep, 0), MAP_ACTION('%', jump_to_matching_bracket, 0, 0), MAP_ACTION('{', jump_to_previous_blank, opt_rep, 0), MAP_ACTION('}', jump_to_next_blank, opt_rep, 0), MAP_ACTION('$', cursor_end, 0, 0), MAP_ACTION('|', cursor_home, 0, 0), MAP_ACTION(KEY_ENTER, next_line_non_whitespace, opt_rep, 0), MAP_ACTION('^', first_non_whitespace, 0, 0), MAP_ACTION('0', cursor_home, 0, 0), MAP_ACTION(-1, NULL, 0, 0), }; struct action_map _ESCAPE_MAP[] = { MAP_ACTION(KEY_F1, toggle_numbers, 0, 0), MAP_ACTION(KEY_F2, toggle_indent, 0, 0), MAP_ACTION(KEY_F3, toggle_gutter, 0, 0), MAP_ACTION(KEY_F4, toggle_smartcomplete, 0, 0), MAP_ACTION(KEY_MOUSE, handle_mouse, 0, 0), MAP_ACTION(KEY_MOUSE_SGR, handle_mouse_sgr, 0, 0), MAP_ACTION(KEY_UP, cursor_up, opt_rep, 0), MAP_ACTION(KEY_DOWN, cursor_down, opt_rep, 0), MAP_ACTION(KEY_RIGHT, cursor_right, opt_rep, 0), MAP_ACTION(KEY_CTRL_RIGHT, big_word_right, opt_rep, 0), MAP_ACTION(KEY_SHIFT_RIGHT, word_right, opt_rep, 0), MAP_ACTION(KEY_ALT_RIGHT, expand_split_right, opt_rep, 0), MAP_ACTION(KEY_ALT_SHIFT_RIGHT, use_right_buffer, opt_rep, 0), MAP_ACTION(KEY_LEFT, cursor_left, opt_rep, 0), MAP_ACTION(KEY_CTRL_LEFT, big_word_left, opt_rep, 0), MAP_ACTION(KEY_SHIFT_LEFT, word_left, opt_rep, 0), MAP_ACTION(KEY_ALT_LEFT, expand_split_left, opt_rep, 0), MAP_ACTION(KEY_ALT_SHIFT_LEFT, use_left_buffer, opt_rep, 0), MAP_ACTION(KEY_HOME, cursor_home, 0, 0), MAP_ACTION(KEY_END, cursor_end, 0, 0), MAP_ACTION(KEY_PAGE_UP, go_page_up, opt_rep, 0), MAP_ACTION(KEY_PAGE_DOWN, go_page_down, opt_rep, 0), MAP_ACTION(KEY_CTRL_Z, suspend, 0, 0), MAP_ACTION(-1, NULL, 0, 0) }; struct action_map _COMMAND_MAP[] = { MAP_ACTION(KEY_ENTER, command_accept, 0, 0), MAP_ACTION('\t', command_tab_complete_buffer, 0, 0), MAP_ACTION(KEY_UP, command_scroll_history, opt_arg, -1), /* back */ MAP_ACTION(KEY_DOWN, command_scroll_history, opt_arg, 1), /* forward */ MAP_ACTION(-1, NULL, 0, 0) }; struct action_map _FILESEARCH_MAP[] = { MAP_ACTION(KEY_ENTER, file_search_accept, 0, 0), MAP_ACTION(KEY_UP, NULL, 0, 0), MAP_ACTION(KEY_DOWN, NULL, 0, 0), MAP_ACTION(-1, NULL, 0, 0) }; struct action_map _SEARCH_MAP[] = { MAP_ACTION(KEY_ENTER, search_accept, 0, 0), MAP_ACTION(KEY_UP, command_scroll_search_history, opt_arg, -1), /* back */ MAP_ACTION(KEY_DOWN, command_scroll_search_history, opt_arg, 1), /* forward */ MAP_ACTION(-1, NULL, 0, 0) }; struct action_map _INPUT_BUFFER_MAP[] = { /* These are generic and shared with search */ MAP_ACTION(KEY_ESCAPE, command_discard, 0, 0), MAP_ACTION(KEY_CTRL_C, command_discard, 0, 0), MAP_ACTION(KEY_BACKSPACE, command_backspace, 0, 0), MAP_ACTION(KEY_CTRL_W, command_word_delete, 0, 0), MAP_ACTION(KEY_MOUSE, eat_mouse, 0, 0), MAP_ACTION(KEY_MOUSE_SGR, eat_mouse_sgr, 0, 0), MAP_ACTION(KEY_LEFT, command_cursor_left, 0, 0), MAP_ACTION(KEY_CTRL_LEFT, command_word_left, 0, 0), MAP_ACTION(KEY_RIGHT, command_cursor_right, 0, 0), MAP_ACTION(KEY_CTRL_RIGHT,command_word_right, 0, 0), MAP_ACTION(KEY_HOME, command_cursor_home, 0, 0), MAP_ACTION(KEY_END, command_cursor_end, 0, 0), MAP_ACTION(-1, NULL, 0, 0) }; /* DIRECTORY_BROWSE_MAP is only to override KEY_ENTER and should not be remapped, * so unlike the others it is not going to be redefined as a pointer. */ struct action_map DIRECTORY_BROWSE_MAP[] = { MAP_ACTION(KEY_ENTER, open_file_from_line, 0, 0), MAP_ACTION(-1, NULL, 0, 0) }; struct action_map * NORMAL_MAP = NULL; struct action_map * INSERT_MAP = NULL; struct action_map * REPLACE_MAP = NULL; struct action_map * LINE_SELECTION_MAP = NULL; struct action_map * CHAR_SELECTION_MAP = NULL; struct action_map * COL_SELECTION_MAP = NULL; struct action_map * COL_INSERT_MAP = NULL; struct action_map * NAVIGATION_MAP = NULL; struct action_map * ESCAPE_MAP = NULL; struct action_map * COMMAND_MAP = NULL; struct action_map * SEARCH_MAP = NULL; struct action_map * FILESEARCH_MAP = NULL; struct action_map * INPUT_BUFFER_MAP = NULL; struct mode_names mode_names[] = { {"Normal","norm",&NORMAL_MAP}, {"Insert","insert",&INSERT_MAP}, {"Replace","replace",&REPLACE_MAP}, {"Line Selection","line",&LINE_SELECTION_MAP}, {"Char Selection","char",&CHAR_SELECTION_MAP}, {"Col Selection","col",&COL_SELECTION_MAP}, {"Col Insert","colinsert",&COL_INSERT_MAP}, {"Navigation (Select)","nav",&NAVIGATION_MAP}, {"Escape (Select, Insert)","esc",&ESCAPE_MAP}, {"Command","command",&COMMAND_MAP}, {"Search","search",&SEARCH_MAP}, {"Input (Command, Search)","input",&INPUT_BUFFER_MAP}, {NULL,NULL,NULL}, }; typedef void (*action_no_arg)(void); typedef void (*action_one_arg)(int); typedef void (*action_two_arg)(int,int); typedef void (*action_three_arg)(int,int,int); int handle_action(struct action_map * basemap, int key) { for (struct action_map * map = basemap; map->key != -1; map++) { if (map->key == key) { if (!map->method) return 1; if ((map->options & opt_rw) && (env->readonly)) { render_error("Buffer is read-only"); return 2; } /* Determine how to format this request */ int reps = (map->options & opt_rep) ? ((nav_buffer) ? atoi(nav_buf) : 1) : 1; int c = 0; if (map->options & opt_char) { c = read_one_character(name_from_key(key)); } if (map->options & opt_byte) { c = read_one_byte(name_from_key(key)); } for (int i = 0; i < reps; ++i) { if (((map->options & opt_char) || (map->options & opt_byte)) && (map->options & opt_arg)) { ((action_two_arg)map->method)(map->arg, c); } else if ((map->options & opt_char) || (map->options & opt_byte)) { ((action_one_arg)map->method)(c); } else if (map->options & opt_arg) { ((action_one_arg)map->method)(map->arg); } else if (map->options & opt_nav) { if (nav_buffer) { ((action_one_arg)map->method)(atoi(nav_buf)); reset_nav_buffer(0); } else { ((action_one_arg)map->method)(-1); } } else if (map->options & opt_krk) { ptrdiff_t before = krk_currentThread.stackTop - krk_currentThread.stack; krk_push(map->callable); krk_push(INTEGER_VAL(key)); krk_push(map->callable); KrkValue result = krk_callStack(2); krk_currentThread.stackTop = krk_currentThread.stack + before; if (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { render_error("Exception during action: %s", AS_INSTANCE(krk_currentThread.currentException)->_class->name->chars); krk_dumpTraceback(); int key = 0; while ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); } } else { ((action_no_arg)map->method)(); } } if (map->options & opt_norm) { if (env->mode == MODE_INSERT || env->mode == MODE_REPLACE) leave_insert(); else if (env->mode == MODE_LINE_SELECTION || env->mode == MODE_CHAR_SELECTION || env->mode == MODE_COL_SELECTION) leave_selection(); else { env->mode = MODE_NORMAL; redraw_all(); } } return 1; } } return 0; } int handle_nav_buffer(int key) { if ((key >= '1' && key <= '9') || (key == '0' && nav_buffer)) { if (nav_buffer < NAV_BUFFER_MAX) { /* Up to NAV_BUFFER_MAX=10 characters; that should be enough for most tasks */ nav_buf[nav_buffer] = key; nav_buf[nav_buffer+1] = 0; nav_buffer++; /* Print the number buffer */ redraw_commandline(); } return 0; } return 1; } /** * NORMAL mode * * Default editor mode - just cursor navigation and keybinds * to enter the other modes. */ void normal_mode(void) { int last_mode = MODE_NORMAL; int refresh = 0; while (1) { if (global_config.overlay_mode) { if (global_config.overlay_mode == OVERLAY_MODE_COMMAND) { if (refresh) { render_command_input_buffer(); refresh = 0; } int key = bim_getkey(DEFAULT_KEY_WAIT); if (key != KEY_TIMEOUT) { refresh = 1; if (!handle_action(COMMAND_MAP, key)) if (!handle_action(INPUT_BUFFER_MAP, key)) if (key < KEY_ESCAPE) command_insert_char(key); } continue; } else if (global_config.overlay_mode == OVERLAY_MODE_FILESEARCH) { if (refresh) { redraw_tabbar(); render_command_input_buffer(); refresh = 0; } int key = bim_getkey(DEFAULT_KEY_WAIT); if (key != KEY_TIMEOUT) { refresh = 1; if (!handle_action(FILESEARCH_MAP, key)) if (!handle_action(INPUT_BUFFER_MAP, key)) if (key < KEY_ESCAPE) command_insert_char(key); } continue; } else if (global_config.overlay_mode == OVERLAY_MODE_SEARCH) { if (refresh) { render_command_input_buffer(); refresh = 0; } int key = bim_getkey(DEFAULT_KEY_WAIT); if (key != KEY_TIMEOUT) { refresh = 1; if (!handle_action(SEARCH_MAP, key)) { if (!handle_action(INPUT_BUFFER_MAP, key)) { if (key < KEY_ESCAPE) command_insert_char(key); } } if (global_config.overlay_mode == OVERLAY_MODE_SEARCH) { /* Find the next search match */ uint32_t * buffer = malloc(sizeof(uint32_t) * (global_config.command_buffer->actual+1)); for (int i = 0; i < global_config.command_buffer->actual; ++i) { buffer[i] = global_config.command_buffer->text[i].codepoint; } buffer[global_config.command_buffer->actual] = 0; int line = -1, col = -1; if (global_config.search_direction == 1) { find_match(global_config.prev_line, global_config.prev_col, &line, &col, buffer, NULL); if (line == -1 && global_config.search_wraps) { find_match(1, 1, &line, &col, buffer, NULL); } } else { find_match_backwards(global_config.prev_line, global_config.prev_col, &line, &col, buffer); if (line == -1 && global_config.search_wraps) { find_match_backwards(env->line_count, env->lines[env->line_count-1]->actual, &line, &col, buffer); } } if (line != -1) { env->col_no = col; env->line_no = line; set_preferred_column(); } else { env->coffset = global_config.prev_coffset; env->offset = global_config.prev_offset; env->col_no = global_config.prev_col; set_preferred_column(); env->line_no = global_config.prev_line; } draw_search_match(buffer, 0); free(buffer); } } continue; } } if (env->mode != last_mode) { redraw_statusbar(); redraw_commandline(); last_mode = env->mode; } if (env->mode == MODE_NORMAL) { place_cursor_actual(); int key = 0; do { key = bim_getkey(DEFAULT_KEY_WAIT); } while (key == KEY_TIMEOUT); if (handle_nav_buffer(key)) { if (!handle_action(NORMAL_MAP, key)) if (!handle_action(NAVIGATION_MAP, key)) handle_action(ESCAPE_MAP, key); } reset_nav_buffer(key); } else if (env->mode == MODE_INSERT) { if (!refresh) place_cursor_actual(); int key = bim_getkey(refresh ? 10 : DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) { place_cursor_actual(); if (refresh > 1) { redraw_text(); } else if (refresh) { redraw_line(env->line_no-1); } refresh = 0; } else if (handle_action(INSERT_MAP, key)) { refresh = 2; } else if (handle_action(ESCAPE_MAP, key)) { /* Do nothing */ } else if (key < KEY_ESCAPE) { insert_char(key); if (global_config.smart_complete) { redraw_line(env->line_no-1); while (omni_complete(1) == 1); } refresh |= 1; } } else if (env->mode == MODE_REPLACE) { place_cursor_actual(); int key = bim_getkey(DEFAULT_KEY_WAIT); if (key != KEY_TIMEOUT) { if (handle_action(REPLACE_MAP, key)) { redraw_text(); } else if (!handle_action(ESCAPE_MAP, key)) { /* Perform replacement */ if (key < KEY_ESCAPE) { if (env->col_no <= env->lines[env->line_no - 1]->actual) { replace_char(key); env->col_no += 1; } else { insert_char(key); redraw_line(env->line_no-1); } set_preferred_column(); } } } } else if (env->mode == MODE_LINE_SELECTION) { place_cursor_actual(); int key = bim_getkey(DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) continue; if (handle_nav_buffer(key)) { if (!handle_action(LINE_SELECTION_MAP, key)) if (!handle_action(NAVIGATION_MAP, key)) handle_action(ESCAPE_MAP, key); } reset_nav_buffer(key); if (env->mode == MODE_LINE_SELECTION) { /* Mark current line */ _redraw_line(env->line_no,0); _redraw_line(env->start_line,1); /* Properly mark everything in the span we just moved through */ if (env->prev_line < env->line_no) { for (int i = env->prev_line; i < env->line_no; ++i) { _redraw_line(i,0); } env->prev_line = env->line_no; } else if (env->prev_line > env->line_no) { for (int i = env->line_no + 1; i <= env->prev_line; ++i) { _redraw_line(i,0); } env->prev_line = env->line_no; } redraw_commandline(); } } else if (env->mode == MODE_CHAR_SELECTION) { place_cursor_actual(); int key = bim_getkey(DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) continue; if (handle_nav_buffer(key)) { if (!handle_action(CHAR_SELECTION_MAP, key)) if (!handle_action(NAVIGATION_MAP, key)) handle_action(ESCAPE_MAP, key); } reset_nav_buffer(key); if (env->mode == MODE_CHAR_SELECTION) { /* Mark current line */ _redraw_line_char(env->line_no,1); /* Properly mark everything in the span we just moved through */ if (env->prev_line < env->line_no) { for (int i = env->prev_line; i < env->line_no; ++i) { _redraw_line_char(i,1); } env->prev_line = env->line_no; } else if (env->prev_line > env->line_no) { for (int i = env->line_no + 1; i <= env->prev_line; ++i) { _redraw_line_char(i,1); } env->prev_line = env->line_no; } } } else if (env->mode == MODE_COL_SELECTION) { place_cursor_actual(); int key = bim_getkey(DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) continue; if (handle_nav_buffer(key)) { if (!handle_action(COL_SELECTION_MAP, key)) if (!handle_action(NAVIGATION_MAP, key)) handle_action(ESCAPE_MAP, key); } reset_nav_buffer(key); if (env->mode == MODE_COL_SELECTION) { _redraw_line_col(env->line_no, 0); /* Properly mark everything in the span we just moved through */ if (env->prev_line < env->line_no) { for (int i = env->prev_line; i < env->line_no; ++i) { _redraw_line_col(i,0); } env->prev_line = env->line_no; } else if (env->prev_line > env->line_no) { for (int i = env->line_no + 1; i <= env->prev_line; ++i) { _redraw_line_col(i,0); } env->prev_line = env->line_no; } redraw_commandline(); } } else if (env->mode == MODE_COL_INSERT) { int key = bim_getkey(refresh ? 10 : DEFAULT_KEY_WAIT); if (key == KEY_TIMEOUT) { if (refresh) { redraw_commandline(); redraw_text(); } refresh = 0; } else if (handle_action(COL_INSERT_MAP, key)) { refresh = 2; } else if (key < KEY_ESCAPE) { insert_char_at_column(key); refresh = 1; } } if (env->mode == MODE_DIRECTORY_BROWSE) { place_cursor_actual(); int key = bim_getkey(DEFAULT_KEY_WAIT); if (handle_nav_buffer(key)) { if (!handle_action(DIRECTORY_BROWSE_MAP, key)) if (!handle_action(NAVIGATION_MAP, key)) handle_action(ESCAPE_MAP, key); } reset_nav_buffer(key); } } } KrkClass * CommandDef; struct CommandDef { KrkInstance inst; struct command_def * command; }; int process_krk_command(const char * cmd, KrkValue * outVal) { place_cursor(global_config.term_width, global_config.term_height); fprintf(stdout, "\n"); /* By resetting, we're at 0 frames. */ krk_resetStack(); /* Push something so we're not at the bottom of the stack when an * exception happens, or we'll get the normal interpreter behavior * and won't be able to examine the exception ourselves. */ krk_push(NONE_VAL()); /* If we don't set outSlots for the top frame a syntax error will * get printed by the interpreter and we can't catch it here. */ krk_currentThread.frames[0].outSlots = 1; /* Call the interpreter */ KrkValue out = krk_interpret(cmd,""); /* If the user typed just a command name, try to execute it. */ if (krk_isInstanceOf(out,CommandDef)) { krk_push(out); out = krk_callStack(0); } if (outVal) *outVal = out; int retval = (IS_INTEGER(out)) ? AS_INTEGER(out) : 0; int hadOutput = 0; /* If we got an exception during execution, print it now */ if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) { if (krk_isInstanceOf(krk_currentThread.currentException, vm.exceptions->syntaxError)) { } set_fg_color(COLOR_RED); fflush(stdout); krk_dumpTraceback(); set_fg_color(COLOR_FG); fflush(stdout); hadOutput = 1; krk_resetStack(); } /* Otherwise, we can look at the result here. */ if (!IS_NONE(out) && !(IS_INTEGER(out) && AS_INTEGER(out) == 0)) { krk_attachNamedValue(&vm.builtins->fields, "_", out); krk_push(out); KrkValue repr = krk_callDirect(krk_getType(out)->_reprer, 1); if (IS_STRING(repr)) { fprintf(stdout, " => %s\n", AS_CSTRING(repr)); clear_to_end(); } krk_resetStack(); hadOutput = 1; } /* If we had either an exception or a non-zero, non-None result, * we want to wait for a key press before continuing, and avoid * clearing the screen if the user is going to enter another command. */ if (hadOutput) { int c; while ((c = bim_getch())== -1); if (c != ':') { bim_unget(c); } else { enter_command(); global_config.command_offset = 0; global_config.command_col_no = 1; render_command_input_buffer(); return retval; } } global_config.break_from_selection = 1; if (!global_config.had_error) redraw_all(); global_config.had_error = 0; return retval; } /** * Show help text for -? */ static void show_usage(char * argv[]) { #define _s "\033[3m" #define _e "\033[0m\n" printf( "bim - Text editor\n" "\n" "usage: %s [options] [file]\n" " %s [options] -- -\n" "\n" " -R " _s "open initial buffer read-only" _e " -O " _s "set various options; examples:" _e " noaltscreen " _s "disable alternate screen buffer" _e " nounicode " _s "disable unicode display" _e " nosyntax " _s "disable syntax highlighting on load" _e " nohistory " _s "disable undo/redo" _e " nomouse " _s "disable mouse support" _e " cansgrmouse " _s "enable SGR mouse escape sequences" _e " -c,-C " _s "print file to stdout with syntax highlighting" _e " " _s "-C includes line numbers, -c does not" _e " -u " _s "override bimrc file" _e " -? " _s "show this help text" _e "\n" "Long options:\n" " --help " _s "show this help text" _e " --version " _s "show version information and available plugins" _e " --dump-mappings " _s "dump markdown description of key mappings" _e " --dump-commands " _s "dump markdown description of all commands" _e " --dump-config " _s "dump key mappings as a bimscript" _e " --html FILE " _s "convert FILE to syntax-highlighted HTML" _e "\n", argv[0], argv[0]); #undef _e #undef _s } BIM_COMMAND(runkrk,"runkrk", "Run a kuroko script") { if (argc < 2) return 1; krk_runfile(argv[1],argv[1]); redraw_all(); return 0; } /** * Enable or disable terminal features/quirks and other options. * Used by -O and by the `quirks` bimrc command. */ int set_capability(char * arg) { char * argname; int value; if (strstr(arg,"no") == arg) { argname = &arg[2]; value = 0; } else if (strstr(arg,"can") == arg) { argname = &arg[3]; value = 1; } else { render_error("Capabilities must by 'no{CAP}' or 'can{CAP}': %s", arg); return 2; } /* Terminal features / quirks */ if (!strcmp(argname, "24bit")) global_config.can_24bit = value; else if (!strcmp(argname, "256color")) global_config.can_256color = value; else if (!strcmp(argname, "altscreen")) global_config.can_altscreen = value; else if (!strcmp(argname, "bce")) global_config.can_bce = value; else if (!strcmp(argname, "bright")) global_config.can_bright = value; else if (!strcmp(argname, "hideshow")) global_config.can_hideshow = value; else if (!strcmp(argname, "italic")) global_config.can_italic = value; else if (!strcmp(argname, "mouse")) global_config.can_mouse = value; else if (!strcmp(argname, "scroll")) global_config.can_scroll = value; else if (!strcmp(argname, "title")) global_config.can_title = value; else if (!strcmp(argname, "unicode")) global_config.can_unicode = value; else if (!strcmp(argname, "insert")) global_config.can_insert = value; else if (!strcmp(argname, "paste")) global_config.can_bracketedpaste = value; else if (!strcmp(argname, "sgrmouse")) global_config.can_sgrmouse = value; /* Startup options */ else if (!strcmp(argname, "syntax")) global_config.highlight_on_open = value; else if (!strcmp(argname, "history")) global_config.history_enabled = value; else { render_error("Unknown capability: %s", argname); return 1; } return 0; } BIM_COMMAND(setcap, "setcap", "Enable or disable quirks/features.") { for (int i = 1; i < argc; ++i) { if (set_capability(argv[i])) return 1; } return 0; } BIM_COMMAND(quirk,"quirk","Handle quirks based on environment variables") { if (argc < 3) goto _quirk_arg_error; char * varname = argv[1]; char * teststr = argv[2]; char * value = getenv(varname); if (!value) return 0; if (strstr(value, teststr) == value) { /* Process quirk strings */ for (int i = 3; i < argc; ++i) { set_capability(argv[i]); } } return 0; _quirk_arg_error: render_error("Usage: quirk ENVVAR value no... can..."); return 1; } /** * Load bimrc configuration file. * * At the moment, this a simple key=value list. */ void load_bimrc(void) { if (!global_config.bimrc_path) return; /* Default is ~/.bimrc */ char * tmp = strdup(global_config.bimrc_path); if (!*tmp) { free(tmp); return; } /* Parse ~ at the front of the path. */ if (*tmp == '~') { char path[1024] = {0}; char * home = getenv("HOME"); if (!home) { /* $HOME is unset? */ free(tmp); return; } /* New path is $HOME/.bimrc */ snprintf(path, 1024, "%s%s", home, tmp+1); free(tmp); tmp = strdup(path); } struct stat statbuf; if (stat(tmp, &statbuf)) { free(tmp); return; } krk_runfile(tmp,tmp); free(tmp); } static KrkValue krk_bim_syntax_dict; KRK_Function(bindHighlighter) { KrkValue cls; if (!krk_parseArgs("V!", (const char*[]){"cls"}, KRK_BASE_CLASS(type), &cls)) return NONE_VAL(); if (!krk_isSubClass(AS_CLASS(cls), syntaxStateClass)) return krk_runtimeError(vm.exceptions->typeError, "Can not register '%s' as a syntax highlighter, expected subclass of SyntaxState.", krk_typeName(cls)); KrkValue name = krk_valueGetAttribute_default(cls, "name", NONE_VAL()); KrkValue extensions = krk_valueGetAttribute_default(cls, "extensions", NONE_VAL()); KrkValue spaces = krk_valueGetAttribute_default(cls, "spaces", BOOLEAN_VAL(0)); KrkValue calculate = krk_valueGetAttribute_default(cls, "calculate", NONE_VAL()); if (!IS_STRING(name)) return krk_runtimeError(vm.exceptions->typeError, "%s.name must be str", AS_CLASS(cls)->name->chars); if (!IS_TUPLE(extensions)) return krk_runtimeError(vm.exceptions->typeError, "%s.extensions must be tuple", AS_CLASS(cls)->name->chars); if (!IS_BOOLEAN(spaces)) return krk_runtimeError(vm.exceptions->typeError, "%s.spaces must be bool", AS_CLASS(cls)->name->chars); if (!IS_CLOSURE(calculate)) return krk_runtimeError(vm.exceptions->typeError, "%s.calculate must be method, not '%s'", AS_CLASS(cls)->name->chars, krk_typeName(calculate)); /* Convert tuple of strings */ char ** ext = malloc(sizeof(char *) * (AS_TUPLE(extensions)->values.count + 1)); /* +1 for NULL */ ext[AS_TUPLE(extensions)->values.count] = NULL; for (size_t i = 0; i < AS_TUPLE(extensions)->values.count; ++i) { if (!IS_STRING(AS_TUPLE(extensions)->values.values[i])) { free(ext); return krk_runtimeError(vm.exceptions->typeError, "%s.extensions must by tuple", AS_CLASS(cls)->name->chars); } ext[i] = AS_CSTRING(AS_TUPLE(extensions)->values.values[i]); } add_syntax((struct syntax_definition) { AS_CSTRING(name), /* name */ ext, /* NULL-terminated array of extensions */ NULL, /* calculate function */ AS_BOOLEAN(spaces), /* spaces */ NULL, /* qualifier */ NULL, /* matcher */ AS_OBJECT(calculate), /* krkFunc */ AS_OBJECT(cls), }); /* And save it in the module stuff. */ krk_tableSet(AS_DICT(krk_bim_syntax_dict), name, cls); return NONE_VAL(); } static KrkValue krk_bim_theme_dict; KRK_Function(defineTheme) { KrkClosure * theme; if (!krk_parseArgs("O!", (const char*[]){"theme"}, KRK_BASE_CLASS(function), &theme)) return NONE_VAL(); KrkValue name = OBJECT_VAL(theme->function->name); add_colorscheme((struct theme_def) { AS_CSTRING(name), theme }); krk_tableSet(AS_DICT(krk_bim_theme_dict), name, OBJECT_VAL(theme)); return OBJECT_VAL(theme); } static int c_keyword_qualifier(int c) { return isalnum(c) || (c == '_'); } #define KRK_BIM_STATE() struct syntax_state * state = &self->state; static KrkTuple * _bim_state_chars = NULL; #define IS_SyntaxState(o) (krk_isInstanceOf(o,syntaxStateClass)) #define AS_SyntaxState(o) ((struct SyntaxState*)AS_OBJECT(o)) #define CURRENT_CTYPE struct SyntaxState* KRK_Method(SyntaxState,state) { KRK_BIM_STATE(); return INTEGER_VAL(state->state); } KRK_Method(SyntaxState,__mod__) { KRK_BIM_STATE(); return INTEGER_VAL(state->state); } KRK_Method(SyntaxState,__lshift__) { KRK_BIM_STATE(); METHOD_TAKES_EXACTLY(1); state->state = AS_INTEGER(argv[1]); /* Be fast about it */ return INTEGER_VAL(state->state); } KRK_Method(SyntaxState,i) { KRK_BIM_STATE(); return INTEGER_VAL(state->i); } KRK_Method(SyntaxState,lineno) { KRK_BIM_STATE(); return INTEGER_VAL(state->line_no); } KRK_Method(SyntaxState,__getitem__) { KRK_BIM_STATE(); METHOD_TAKES_EXACTLY(1); /* non-slice item */ if (IS_INTEGER(argv[1])) { long arg = AS_INTEGER(argv[1]); int charRel = charrel(arg); if (charRel == -1) return NONE_VAL(); if (charRel >= 32 && charRel <= 126) return _bim_state_chars->values.values[charRel - 32]; char tmp[8] = {0}; size_t len = to_eight(charRel, tmp); return OBJECT_VAL(krk_copyString(tmp,len)); } else if (IS_slice(argv[1])) { struct StringBuilder sb = {0}; extern 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); krk_integer_type start, end, step; if (krk_extractSlicer("__getitem__", argv[1], state->line->actual - state->i, &start, &end, &step)) { return NONE_VAL(); } krk_integer_type i = start; while ((step < 0) ? (i > end) : (i < end)) { int charRel = charrel(i); if (charRel == -1) break; char tmp[8] = {0}; size_t len = to_eight(charRel, tmp); pushStringBuilderStr(&sb, tmp, len); i += step; } return finishStringBuilder(&sb); } else { return TYPE_ERROR(int or slice,argv[1]); } } KRK_StaticMethod(SyntaxState,isdigit) { FUNCTION_TAKES_EXACTLY(1); if (IS_NONE(argv[0])) return BOOLEAN_VAL(0); if (!IS_STRING(argv[0]) || AS_STRING(argv[0])->codesLength != 1) return TYPE_ERROR(str of length 1,argv[0]); unsigned int c = krk_unicodeCodepoint(AS_STRING(argv[0]), 0); return BOOLEAN_VAL(!!isdigit(c)); } KRK_StaticMethod(SyntaxState,isxdigit) { FUNCTION_TAKES_EXACTLY(1); if (IS_NONE(argv[0])) return BOOLEAN_VAL(0); if (!IS_STRING(argv[0]) || AS_STRING(argv[0])->codesLength != 1) return TYPE_ERROR(str of length 1,argv[0]); unsigned int c = krk_unicodeCodepoint(AS_STRING(argv[0]), 0); return BOOLEAN_VAL(!!isxdigit(c)); } KRK_Method(SyntaxState,paint) { KRK_BIM_STATE(); METHOD_TAKES_EXACTLY(2); long howMuch = AS_INTEGER(argv[1]); long whatFlag = AS_INTEGER(argv[2]); if (howMuch == -1) howMuch = state->line->actual; paint(howMuch, whatFlag); return NONE_VAL(); } KRK_Method(SyntaxState,paintComment) { KRK_BIM_STATE(); paint_comment(state); return NONE_VAL(); } KRK_Method(SyntaxState,skip) { KRK_BIM_STATE(); skip(); return NONE_VAL(); } /* Identical to the above 'paint' */ KRK_Method(SyntaxState,__setitem__) { KRK_BIM_STATE(); METHOD_TAKES_EXACTLY(2); long howMuch = AS_INTEGER(argv[1]); long whatFlag = AS_INTEGER(argv[2]); if (howMuch == -1) howMuch = state->line->actual; paint(howMuch, whatFlag); return NONE_VAL(); } #define KRK_STRING_FAST(string,offset) (uint32_t)\ ((string->obj.flags & KRK_OBJ_FLAGS_STRING_MASK) <= (KRK_OBJ_FLAGS_STRING_UCS1) ? ((uint8_t*)string->codes)[offset] : \ ((string->obj.flags & KRK_OBJ_FLAGS_STRING_MASK) == (KRK_OBJ_FLAGS_STRING_UCS2) ? ((uint16_t*)string->codes)[offset] : \ ((uint32_t*)string->codes)[offset])) KRK_Method(SyntaxState,__contains__) { KRK_BIM_STATE(); METHOD_TAKES_EXACTLY(1); int c = charrel(0); KrkValue arg = argv[1]; if (IS_NONE(arg)) return BOOLEAN_VAL((c == -1)); if (!IS_STRING(arg)) return TYPE_ERROR(str,arg); KrkString * s = AS_STRING(arg); krk_unicodeString(s); for (size_t i = 0; i < s->codesLength; ++i) { int cp = (int)KRK_STRING_FAST(s,i); if (c == cp) return BOOLEAN_VAL(1); } return BOOLEAN_VAL(0); } KRK_StaticMethod(SyntaxState,cKeywordQualifier) { FUNCTION_TAKES_EXACTLY(1); if (IS_INTEGER(argv[0])) return BOOLEAN_VAL(!!c_keyword_qualifier(AS_INTEGER(argv[0]))); if (!IS_STRING(argv[0])) return BOOLEAN_VAL(0); if (AS_STRING(argv[0])->length > 1) return BOOLEAN_VAL(0); return BOOLEAN_VAL(!!c_keyword_qualifier(AS_CSTRING(argv[0])[0])); } static int callQualifier(KrkValue qualifier, int codepoint) { if (IS_NATIVE(qualifier) && AS_NATIVE(qualifier)->function == FUNC_NAME(SyntaxState,cKeywordQualifier)) return AS_BOOLEAN(!!c_keyword_qualifier(codepoint)); krk_push(qualifier); krk_push(INTEGER_VAL(codepoint)); KrkValue result = krk_callStack(1); if (IS_BOOLEAN(result)) return AS_BOOLEAN(result); return 0; } KRK_Method(SyntaxState,findKeywords) { KRK_BIM_STATE(); KrkValue kwList; int flag; KrkValue qualifier; if (!krk_parseArgs(".V!iV",(const char*[]){"keywords","flag","qualifier"}, KRK_BASE_CLASS(list), &kwList, &flag, &qualifier)) return NONE_VAL(); if (callQualifier(qualifier, lastchar())) return BOOLEAN_VAL(0); if (!callQualifier(qualifier, charat())) return BOOLEAN_VAL(0); for (size_t keyword = 0; keyword < AS_LIST(kwList)->count; ++keyword) { if (!IS_STRING(AS_LIST(kwList)->values[keyword])) { return TYPE_ERROR(list of str,AS_LIST(kwList)->values[keyword]); } KrkString * me = AS_STRING(AS_LIST(kwList)->values[keyword]); size_t d = 0; if ((me->obj.flags & KRK_OBJ_FLAGS_STRING_MASK) == KRK_OBJ_FLAGS_STRING_ASCII) { while (state->i + (int)d < state->line->actual && d < me->codesLength && state->line->text[state->i+d].codepoint == me->chars[d]) d++; } else { krk_unicodeString(me); while (state->i + (int)d < state->line->actual && d < me->codesLength && state->line->text[state->i+d].codepoint == KRK_STRING_FAST(me,d)) d++; } if (d == me->codesLength && (state->i + (int)d >= state->line->actual || !callQualifier(qualifier,state->line->text[state->i+d].codepoint))) { paint((int)me->codesLength, flag); return BOOLEAN_VAL(1); } } return BOOLEAN_VAL(0); } KRK_Method(SyntaxState,matchAndPaint) { KRK_BIM_STATE(); KrkString * me; int flag; KrkValue qualifier; if (!krk_parseArgs(".O!iV",(const char*[]){"match","flag","qualifier"}, KRK_BASE_CLASS(str), &me, &flag, &qualifier)) return NONE_VAL(); size_t d = 0; if ((me->obj.flags & KRK_OBJ_FLAGS_STRING_MASK) == KRK_OBJ_FLAGS_STRING_ASCII) { while (state->i + (int)d < state->line->actual && d < me->codesLength && state->line->text[state->i+d].codepoint == me->chars[d]) d++; } else { krk_unicodeString(me); while (state->i + (int)d < state->line->actual && d < me->codesLength && state->line->text[state->i+d].codepoint == KRK_STRING_FAST(me,d)) d++; } if (d == me->codesLength && (state->i + (int)d >= state->line->actual || !callQualifier(qualifier,state->line->text[state->i+d].codepoint))) { paint((int)me->codesLength, flag); return BOOLEAN_VAL(1); } return BOOLEAN_VAL(0); } KRK_Method(SyntaxState,rewind) { KRK_BIM_STATE(); int offset; if (!krk_parseArgs(".i",(const char*[]){"offset"}, &offset)) return NONE_VAL(); state->i -= offset; return NONE_VAL(); } KRK_Method(SyntaxState,commentBuzzwords) { KRK_BIM_STATE(); return BOOLEAN_VAL(common_comment_buzzwords(state)); } KRK_Method(SyntaxState,__init__) { KRK_BIM_STATE(); struct SyntaxState * other; if (!krk_parseArgs(".O!",(const char*[]){"other"},syntaxStateClass,&other)) return NONE_VAL(); *state = other->state; return NONE_VAL(); } KRK_Function(getCommands) { KrkValue myList = krk_list_of(0, NULL,0); krk_push(myList); for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { krk_writeValueArray(AS_LIST(myList), OBJECT_VAL(krk_copyString(c->name,strlen(c->name)))); } for (struct command_def * c = prefix_commands; prefix_commands && c->name; ++c) { krk_writeValueArray(AS_LIST(myList), OBJECT_VAL(krk_copyString(c->name,strlen(c->name)))); } return krk_pop(); } KrkClass * ActionDef; struct ActionDef { KrkInstance inst; struct action_def * action; }; #define IS_Action(o) (krk_isInstanceOf(o,ActionDef)) #define AS_Action(o) ((struct ActionDef*)AS_OBJECT(o)) #undef CURRENT_CTYPE #define CURRENT_CTYPE struct ActionDef* KRK_Method(Action,__call__) { /* Figure out arguments */ int args = 0; if (self->action->options & ARG_IS_CUSTOM) args++; if (self->action->options & ARG_IS_INPUT) args++; if (self->action->options & ARG_IS_PROMPT) args++; int argsAsInts[3] = { 0, 0, 0 }; for (int i = 0; i < args; i++) { if (argc < i + 2) return krk_runtimeError(vm.exceptions->argumentError, "%s() takes %d argument%s", self->action->name, args, args == 1 ? "" : "s"); if (IS_INTEGER(argv[i+1])) { argsAsInts[i] = AS_INTEGER(argv[i+1]); } else if (IS_STRING(argv[i+1]) && AS_STRING(argv[i+1])->codesLength == 1) { argsAsInts[i] = krk_unicodeCodepoint(AS_STRING(argv[i+1]), 0); } else if (IS_BOOLEAN(argv[i+1])) { argsAsInts[i] = AS_BOOLEAN(argv[i+1]); } else { return krk_runtimeError(vm.exceptions->typeError, "argument to %s() must be int, bool, or str of len 1", self->action->name); } } ((action_three_arg)self->action->action)(argsAsInts[0], argsAsInts[1], argsAsInts[2]); return NONE_VAL(); } #define IS_Command(o) (krk_isInstanceOf(o,CommandDef)) #define AS_Command(o) ((struct CommandDef*)AS_OBJECT(o)) #undef CURRENT_CTYPE #define CURRENT_CTYPE struct CommandDef* KRK_Method(Command,__call__) { char ** args = malloc(sizeof(char*)*argc); args[0] = strdup(self->command->name); for (int i = 1; i < argc; ++i) { if (IS_STRING(argv[i])) { args[i] = strdup(AS_CSTRING(argv[i])); } else { krk_push(argv[i]); KrkValue asString = krk_callDirect(krk_getType(argv[i])->_tostr, 1); args[i] = strdup(AS_CSTRING(asString)); } } int result = self->command->command(args[0], argc, args); for (int i = 0; i < argc; ++i) { free(args[i]); } free(args); return INTEGER_VAL(result); } void import_directory(char * dirName) { const char * extra = ""; char * dirpath = NULL; char file[4096]; if (vm.binpath) { char * tmp = strdup(vm.binpath); dirpath = strdup(dirname(tmp)); extra = "/"; free(tmp); sprintf(file, "%s/%s", dirpath, dirName); } else { sprintf(file, "%s", dirName); } DIR * dirp = opendir(file); if (!dirp && dirpath) { /* Try ../share/bim/dirName */ sprintf(file, "%s/../share/bim/%s", dirpath, dirName); extra = "/../share/bim/"; dirp = opendir(file); } if (!dirp) { /* Try /usr/share/bim */ if (dirpath) free(dirpath); dirpath = strdup("/usr/share/bim"); sprintf(file, "%s/%s", dirpath, dirName); extra = "/"; dirp = opendir(file); } if (!dirp) { /* Try one last fallback */ if (dirpath) free(dirpath); dirpath = strdup("/usr/local/share/bim"); sprintf(file, "%s/%s", dirpath, dirName); extra = "/"; dirp = opendir(file); } if (!dirp) { fprintf(stderr, "Could not find startup files: %s\n", dirName); exit(1); } if (dirpath) { /* get kuroko.module_paths */ krk_push(krk_valueGetAttribute(OBJECT_VAL(vm.system), "module_paths")); krk_push(krk_valueGetAttribute(krk_peek(0), "insert")); krk_push(INTEGER_VAL(0)); /* calculate dirpath + extra */ krk_push(OBJECT_VAL(krk_copyString(dirpath,strlen(dirpath)))); krk_push(OBJECT_VAL(krk_copyString(extra,strlen(extra)))); krk_addObjects(); krk_callStack(2); /* result value is popped */ krk_pop(); /* should just be the list */ } if (dirpath) free(dirpath); struct dirent * ent = readdir(dirp); while (ent) { if (str_ends_with(ent->d_name,".krk")) { /* put "dir.file" onto the stack */ krk_push(OBJECT_VAL(krk_copyString(dirName,strlen(dirName)))); if (!str_ends_with(ent->d_name,"__init__.krk")) { krk_push(OBJECT_VAL(S("."))); krk_addObjects(); krk_push(OBJECT_VAL(krk_copyString(ent->d_name,strlen(ent->d_name)-4))); krk_addObjects(); } /* import that */ krk_doRecursiveModuleLoad(AS_STRING(krk_peek(0))); if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) { krk_dumpTraceback(); render_error("The above exception was encountered while loading '%s/%s'.", dirName, ent->d_name); if (global_config.has_terminal) { /* Prompt to continue */ render_commandline_message("Continue loading modules? (y/N) "); int key; while ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); if (key != 'y') { krk_resetStack(); break; } } else { render_error("Press ENTER to continue loading."); int c; while ((c = bim_getch(), c != ENTER_KEY && c != LINE_FEED)); } } /* reset the stack */ krk_resetStack(); } ent = readdir(dirp); } closedir(dirp); } static void findBim(char * argv[]) { /* Try asking /proc */ char * binpath = realpath("/proc/self/exe", NULL); if (!binpath || (access(binpath, X_OK) != 0)) { if (strchr(argv[0], '/')) { binpath = realpath(argv[0], NULL); } else { /* Search PATH for argv[0] */ char * _path = strdup(getenv("PATH")); char * path = _path; while (path) { char * next = strchr(path,':'); if (next) *next++ = '\0'; char tmp[4096]; sprintf(tmp, "%s/%s", path, argv[0]); if (access(tmp, X_OK)) { binpath = strdup(tmp); break; } path = next; } free(_path); } } if (binpath) { vm.binpath = binpath; } /* Else, give up at this point and just don't attach it at all. */ } static void do_kuroko_imports(void) { krk_resetStack(); krk_startModule(""); import_directory("site"); krk_startModule(""); import_directory("syntax"); krk_startModule(""); import_directory("themes"); krk_startModule(""); load_bimrc(); krk_resetStack(); } BIM_COMMAND(reload,"reload","Reloads all the Kuroko stuff.") { /* Unload everything syntax-y */ KrkValue result = krk_interpret( "if True:\n" " import kuroko\n" " for mod in kuroko.modules():\n" " if mod.startswith('syntax.') or mod.startswith('themes.'):\n" " kuroko.unload(mod)\n", ""); if (IS_NONE(result) && (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { krk_dumpTraceback(); return 1; } /* Reload everything */ do_kuroko_imports(); return 0; } KRK_Function(getDocumentText) { struct StringBuilder sb = {0}; int i, j; for (i = 0; i < env->line_count; ++i) { line_t * line = env->lines[i]; for (j = 0; j < line->actual; j++) { char_t c = line->text[j]; if (c.codepoint == 0) { pushStringBuilder(&sb, 0); } else { char tmp[8] = {0}; int len = to_eight(c.codepoint, tmp); pushStringBuilderStr(&sb, tmp, len); } } pushStringBuilder(&sb, '\n'); } return finishStringBuilder(&sb); } KRK_Function(renderError) { char * message = NULL; size_t message_len = 0; if (!krk_parseArgs("|z#",(const char*[]){"message"}, &message, &message_len)) return NONE_VAL(); if (!message || !message_len) redraw_commandline(); else render_error("%s", message); return NONE_VAL(); } KRK_Function(renderMessage) { char * message = NULL; size_t message_len = 0; if (!krk_parseArgs("|z#",(const char*[]){"message"}, &message, &message_len)) return NONE_VAL(); if (!message || !message_len) redraw_commandline(); else render_commandline_message("%s", message); return NONE_VAL(); } KRK_Function(renderStatus) { char * message = NULL; size_t message_len = 0; if (!krk_parseArgs("|z#",(const char*[]){"message"}, &message, &message_len)) return NONE_VAL(); if (!message || !message_len) redraw_commandline(); else render_status_message("%s", message); return NONE_VAL(); } KRK_Function(getDocumentFilename) { if (!env || !env->file_name) return NONE_VAL(); return OBJECT_VAL(krk_copyString(env->file_name,strlen(env->file_name))); } static KrkValue krk_bim_custom_action_dict; KRK_Function(bindkey) { const char * key = NULL; const char * mode = NULL; KrkValue callable; if (!krk_parseArgs("ssV", (const char*[]){"key","mode","callable"}, &key, &mode, &callable)) { return NONE_VAL(); } struct action_map ** mode_map = NULL; for (struct mode_names * m = mode_names; m->name; ++m) { if (!strcmp(m->name, mode)) { mode_map = m->mode; break; } } if (!mode_map) return krk_runtimeError(vm.exceptions->valueError, "invalid mode: %s", mode); enum Key keycode = key_from_name(key); if (keycode == -1) return krk_runtimeError(vm.exceptions->valueError, "invalid key: %s", key); struct action_map * candidate = NULL; for (struct action_map * m = *mode_map; m->key != -1; ++m) { if (m->key == keycode) { candidate = m; break; } } if (!candidate) { /* get size */ int len = 0; for (struct action_map * m = *mode_map; m->key != -1; m++, len++); *mode_map = realloc(*mode_map, sizeof(struct action_map) * (len + 2)); candidate = &(*mode_map)[len]; (*mode_map)[len+1].key = -1; } candidate->key = keycode; candidate->options = opt_krk; candidate->callable = callable; return 0; krk_tableSet(AS_DICT(krk_bim_custom_action_dict), callable, BOOLEAN_VAL(1)); return NONE_VAL(); } KRK_Function(getkey) { int timeout = DEFAULT_KEY_WAIT; if (!krk_parseArgs("|i",(const char*[]){"timeout"},&timeout)) return NONE_VAL(); int key = bim_getkey(timeout); return INTEGER_VAL(key); } KRK_Function(displayWidth) { char * str; if (!krk_parseArgs("s",(const char*[]){"str"},&str)) return NONE_VAL(); return INTEGER_VAL(display_width_of_string(str)); } KRK_Function(pauseForKey) { pause_for_key(); return NONE_VAL(); } KRK_Function(paren_pairs) { KrkString * pairs = NULL; if (!krk_parseArgs("|O!", (const char *[]){"pairs"}, KRK_BASE_CLASS(str), &pairs)) return NONE_VAL(); if (pairs) { krk_unicodeString(pairs); uint32_t * new_pairs = calloc(sizeof(uint32_t), pairs->codesLength + 1); for (size_t i = 0; i < pairs->codesLength; ++i) { uint32_t cp = (uint32_t)KRK_STRING_FAST(pairs,i); new_pairs[i] = cp; } if (global_config.paren_pairs) free(global_config.paren_pairs); global_config.paren_pairs = new_pairs; } if (!global_config.paren_pairs) return NONE_VAL(); /* Somehow we don't have a good API for building a string from a char32 array. */ struct StringBuilder sb = {0}; for (uint32_t * value = global_config.paren_pairs; *value; ++value) { unsigned char bytes[5] = {0}; size_t len = krk_codepointToBytes(*value, bytes); krk_pushStringBuilderStr(&sb, (char*)bytes, len); } return krk_finishStringBuilder(&sb); } /** * Run global initialization tasks */ void initialize(void) { /* Force empty locale */ #ifdef __APPLE__ /* TODO figure out a better way to do this; maybe just LC_CTYPE? */ setlocale(LC_ALL, "en_US.UTF-8"); #else setlocale(LC_ALL, ""); #endif /* Set up default key mappings */ #define CLONE_MAP(map) do { \ int len = 0, i = 0; \ for (struct action_map * m = _ ## map; m->key != -1; m++, len++); \ map = malloc(sizeof(struct action_map) * (len + 1)); \ for (struct action_map * m = _ ## map; m->key != -1; m++, i++) { \ memcpy(&map[i], m, sizeof(struct action_map)); \ } \ map[i].key = -1; \ } while (0) CLONE_MAP(NORMAL_MAP); CLONE_MAP(INSERT_MAP); CLONE_MAP(REPLACE_MAP); CLONE_MAP(LINE_SELECTION_MAP); CLONE_MAP(CHAR_SELECTION_MAP); CLONE_MAP(COL_SELECTION_MAP); CLONE_MAP(COL_INSERT_MAP); CLONE_MAP(NAVIGATION_MAP); CLONE_MAP(ESCAPE_MAP); CLONE_MAP(COMMAND_MAP); CLONE_MAP(SEARCH_MAP); CLONE_MAP(FILESEARCH_MAP); CLONE_MAP(INPUT_BUFFER_MAP); #undef CLONE_MAP /* Simple ASCII defaults, but you could use " " as a config option */ global_config.tab_indicator = strdup(">"); global_config.space_indicator = strdup("-"); uint32_t pairs[] = U"()[]{}<>"; global_config.paren_pairs = malloc(sizeof(pairs)); memcpy(global_config.paren_pairs, pairs, sizeof(pairs)); /* Initialize Kuroko runtime context */ krk_initVM(0); /** * Build the 'bim' module: * @c bindHighlighter Applied to syntax highlighter classes to register them. * @c getCommands Returns a list of bim command objects. * @c themes dict, theme names to theme functions. * @c defineTheme Applied to theme functions to register them. * @c highlighters dict, syntax highlighter names to highlighter classes. * @c getDocumentText Return a string with the full contents of the current buffer. * @c renderError Binding to render_error. */ KrkInstance * bimModule = krk_newInstance(vm.baseClasses->moduleClass); krk_attachNamedObject(&vm.modules, "bim", (KrkObj*)bimModule); krk_attachNamedObject(&bimModule->fields, "__name__", (KrkObj*)S("bim")); BIND_FUNC(bimModule, bindHighlighter); BIND_FUNC(bimModule, getCommands); BIND_FUNC(bimModule, defineTheme); BIND_FUNC(bimModule, getDocumentText); BIND_FUNC(bimModule, renderError); BIND_FUNC(bimModule, renderMessage); BIND_FUNC(bimModule, renderStatus); BIND_FUNC(bimModule, bindkey); BIND_FUNC(bimModule, getDocumentFilename); BIND_FUNC(bimModule, getkey); BIND_FUNC(bimModule, displayWidth); BIND_FUNC(bimModule, pauseForKey); BIND_FUNC(bimModule, paren_pairs); /* Direct access and GC references */ krk_bim_theme_dict = krk_dict_of(0,NULL,0); krk_attachNamedValue(&bimModule->fields, "themes", krk_bim_theme_dict); krk_bim_syntax_dict = krk_dict_of(0,NULL,0); krk_attachNamedValue(&bimModule->fields, "highlighters", krk_bim_syntax_dict); krk_bim_custom_action_dict = krk_dict_of(0,NULL,0); krk_attachNamedValue(&bimModule->fields,"customActions", krk_bim_custom_action_dict); /* Helpful info */ krk_attachNamedObject(&bimModule->fields, "version", (KrkObj*)S(BIM_VERSION)); krk_attachNamedObject(&bimModule->fields, "copyright", (KrkObj*)S(BIM_COPYRIGHT)); krk_attachNamedObject(&bimModule->fields, "builddate", (KrkObj*)S(BIM_BUILD_DATE)); /** * Class representing a BIM_ACTION. * Actions end up in __builtins__, which is dirty, but done for config reasons. * Calling an action executes it. */ KrkClass * Action = krk_makeClass(bimModule, &ActionDef, "Action", vm.baseClasses->objectClass); Action->allocSize = sizeof(struct ActionDef); BIND_METHOD(Action,__call__); krk_finalizeClass(Action); for (struct action_def * a = mappable_actions; mappable_actions && a->name; ++a) { struct ActionDef * actionObj = (void*)krk_newInstance(Action); actionObj->action = a; krk_attachNamedObject(&vm.builtins->fields, a->name, (KrkObj*)actionObj); } /* Class representing a BIM_COMMAND. Works the same as actions. */ KrkClass * Command = krk_makeClass(bimModule, &CommandDef, "Command", vm.baseClasses->objectClass); Command->allocSize = sizeof(struct CommandDef); BIND_METHOD(Command,__call__); krk_finalizeClass(Command); /* For silly legacy config reasons, we have a special 'global' namespace * that we just shove into __builtins__. This contains all of the command * objects that are bound with names starting with "global.", naturally. */ KrkInstance * global = krk_newInstance(vm.baseClasses->objectClass); krk_attachNamedObject(&vm.builtins->fields, "global", (KrkObj*)global); for (struct command_def * c = regular_commands; regular_commands && c->name; ++c) { struct CommandDef * commandObj = (void*)krk_newInstance(CommandDef); commandObj->command = c; if (strstr(c->name,"global.") == c->name) { krk_attachNamedObject(&global->fields, c->name + 7, (KrkObj*)commandObj); } else { krk_attachNamedObject(&vm.builtins->fields, c->name, (KrkObj*)commandObj); } } /** * SyntaxState is the base class for syntax highlighters. * * @class SyntaxState * @e Properties * @b state Read-write access to the underlying state number (used for passing context between lines) * @b i Read access to the offset into the line. * @b lineno Read access to the line number of the line being highlighted. * * @e Methods * @b findKeywords() Takes a list of keywords and highlights with a given flag based on a qualifier. * @b isdigit() Determines if the argument character is a "digit" (0-9) * @b isxdigit() Determines if the argument character is a "hex digit" (0-9, a-f, A-F) * @b paint() Paints a number of character cells a given color and advances the highlighter. * @b paintComment() Paints an end-of-line comment, with buzzword handling. Legacy convenience function. * @b skip() Moves the highlighter forward one character cell without painting. * @b matchAndPaint() Similar to @c findKeywords but only highlights one thing. * @b commentBuzzwords() Detects and automatically highlights common comment buzzwords. Legacy convenience function. * @b rewind() Rewinds the highlighter, moving it back to a previous character cell. * @b __getitem__() Index into character cells of the current line from the highlighter. * Note, negative indexes will reference cells before the 'cursor', but this * does not apply to slicing, which treats the rest of the line (starting at the * cursor) as a single string, thus -1 is the last character of the line. * Indexing returns @c None rather than raising an IndexError if the requested * character is out of range, similar to behavior of the C @c charrel interface. * @e Flags * These flags supply the C FLAG_ constants. Unfortunately, this is kinda slow, and * it would be nice to have some sort of compile-time constant available so that these * don't have to imply attribute lookups at runtime... */ KrkClass * SyntaxState = krk_makeClass(bimModule, &syntaxStateClass, "SyntaxState", vm.baseClasses->objectClass); SyntaxState->allocSize = sizeof(struct SyntaxState); BIND_METHOD(SyntaxState,__init__); BIND_PROP(SyntaxState,state); BIND_PROP(SyntaxState,i); BIND_PROP(SyntaxState,lineno); BIND_METHOD(SyntaxState,findKeywords); BIND_STATICMETHOD(SyntaxState,cKeywordQualifier); BIND_STATICMETHOD(SyntaxState,isdigit); BIND_STATICMETHOD(SyntaxState,isxdigit); BIND_METHOD(SyntaxState,paint); BIND_METHOD(SyntaxState,paintComment); BIND_METHOD(SyntaxState,skip); BIND_METHOD(SyntaxState,matchAndPaint); BIND_METHOD(SyntaxState,commentBuzzwords); BIND_METHOD(SyntaxState,rewind); BIND_METHOD(SyntaxState,__getitem__); BIND_METHOD(SyntaxState,__setitem__); BIND_METHOD(SyntaxState,__contains__); BIND_METHOD(SyntaxState,__mod__); BIND_METHOD(SyntaxState,__lshift__); #define ATTACH_STATE_FLAG(flag) krk_attachNamedValue(&SyntaxState->methods, #flag, INTEGER_VAL(flag)) ATTACH_STATE_FLAG(FLAG_NONE); ATTACH_STATE_FLAG(FLAG_KEYWORD); ATTACH_STATE_FLAG(FLAG_STRING); ATTACH_STATE_FLAG(FLAG_COMMENT); ATTACH_STATE_FLAG(FLAG_TYPE); ATTACH_STATE_FLAG(FLAG_PRAGMA); ATTACH_STATE_FLAG(FLAG_NUMERAL); ATTACH_STATE_FLAG(FLAG_ERROR); ATTACH_STATE_FLAG(FLAG_DIFFPLUS); ATTACH_STATE_FLAG(FLAG_DIFFMINUS); ATTACH_STATE_FLAG(FLAG_NOTICE); ATTACH_STATE_FLAG(FLAG_BOLD); ATTACH_STATE_FLAG(FLAG_LINK); ATTACH_STATE_FLAG(FLAG_ESCAPE); ATTACH_STATE_FLAG(FLAG_EXTRA); ATTACH_STATE_FLAG(FLAG_SPECIAL); ATTACH_STATE_FLAG(FLAG_UNDERLINE); /* This is a dumb cache of characters to avoid recreating them all the time */ _bim_state_chars = krk_newTuple(95); krk_attachNamedObject(&syntaxStateClass->methods, "__chars__", (KrkObj*)_bim_state_chars); for (int c = 0; c < 95; ++c) { char tmp = c + 32; _bim_state_chars->values.values[_bim_state_chars->values.count++] = OBJECT_VAL(krk_copyString(&tmp,1)); } krk_finalizeClass(syntaxStateClass); do_kuroko_imports(); /* Disable default traceback printing */ vm.globalFlags |= KRK_GLOBAL_CLEAN_OUTPUT; /* Initialize space for buffers */ buffers_avail = 4; buffers = malloc(sizeof(buffer_t *) * buffers_avail); } /** * Initialize terminal for editor display. */ void init_terminal(void) { if (!isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) { global_config.tty_in = STDERR_FILENO; } set_alternate_screen(); set_bracketed_paste(); update_screen_size(); get_initial_termios(); set_unbuffered(); mouse_enable(); global_config.has_terminal = 1; signal(SIGWINCH, SIGWINCH_handler); signal(SIGCONT, SIGCONT_handler); signal(SIGTSTP, SIGTSTP_handler); signal(SIGINT, SIGINT_handler); } struct action_def * find_action(uintptr_t action) { if (!action) return NULL; for (int i = 0; i < flex_mappable_actions_count; ++i) { if (action == mappable_actions[i].action) return &mappable_actions[i]; } return NULL; } void dump_mapping(const char * description, struct action_map * map) { printf("## %s\n", description); printf("\n"); printf("| **Key** | **Action** | **Description** |\n"); printf("|---------|------------|-----------------|\n"); struct action_map * m = map; while (m->key != -1) { /* Find the name of this action */ struct action_def * action = find_action(m->method); printf("| `%s` | `%s` | %s |\n", name_from_key(m->key), action ? action->name : "(unbound)", action ? action->description : "(unbound)"); m++; } printf("\n"); } int sort_regular_commands(const void * a, const void * b) { return strcmp(regular_commands[*(int *)a].name, regular_commands[*(int *)b].name); } int sort_prefix_commands(const void * a, const void * b) { return strcmp(prefix_commands[*(int *)a].name, prefix_commands[*(int *)b].name); } void dump_commands(void) { printf("## Regular Commands\n"); printf("\n"); printf("| **Command** | **Description** |\n"); printf("|-------------|-----------------|\n"); int * offsets = malloc(sizeof(int) * flex_regular_commands_count); for (int i = 0; i < flex_regular_commands_count; ++i) { offsets[i] = i; } qsort(offsets, flex_regular_commands_count, sizeof(int), sort_regular_commands); for (int i = 0; i < flex_regular_commands_count; ++i) { printf("| `:%s` | %s |\n", regular_commands[offsets[i]].name, regular_commands[offsets[i]].description); } free(offsets); printf("\n"); printf("## Prefix Commands\n"); printf("\n"); printf("| **Command** | **Description** |\n"); printf("|-------------|-----------------|\n"); offsets = malloc(sizeof(int) * flex_prefix_commands_count); for (int i = 0; i < flex_prefix_commands_count; ++i) offsets[i] = i; qsort(offsets, flex_prefix_commands_count, sizeof(int), sort_prefix_commands); for (int i = 0; i < flex_prefix_commands_count; ++i) { printf("| `:%s...` | %s |\n", !strcmp(prefix_commands[offsets[i]].name, "`") ? "`(backtick)`" : prefix_commands[offsets[i]].name, prefix_commands[offsets[i]].description); } free(offsets); printf("\n"); } BIM_COMMAND(whatis,"whatis","Describe actions bound to a key in different modes.") { int key = 0; if (argc < 2) { render_commandline_message("(press a key)"); while ((key = bim_getkey(DEFAULT_KEY_WAIT)) == KEY_TIMEOUT); } else if (strlen(argv[1]) > 1 && argv[1][0] == '^') { /* See if it's a valid ctrl key */ if (argv[1][2] != '\0') { goto _invalid_key_name; } if ((unsigned char)argv[1][1] < '@' || (unsigned char)argv[1][1] > '@' + '_') { goto _invalid_key_name; } key = argv[1][1] - '@'; } else if (argv[1][1] != '\0') { for (unsigned int i = 0; i < sizeof(KeyNames)/sizeof(KeyNames[0]); ++i) { if (!strcmp(KeyNames[i].name,argv[1])) { key = KeyNames[i].keycode; } } if (!key) goto _invalid_key_name; } else { key = (unsigned char)argv[1][0]; } struct MappingNames { char * name; struct action_map * map; } maps[] = { {"Normal", NORMAL_MAP}, {"Insert", INSERT_MAP}, {"Replace", REPLACE_MAP}, {"Line Selection", LINE_SELECTION_MAP}, {"Char Selection", CHAR_SELECTION_MAP}, {"Col Selection", COL_SELECTION_MAP}, {"Col Insert", COL_INSERT_MAP}, {"Navigation (Select)", NAVIGATION_MAP}, {"Escape (Select, Insert)", ESCAPE_MAP}, {"Command", COMMAND_MAP}, {"Search", SEARCH_MAP}, {"Input (Command, Search)", INPUT_BUFFER_MAP}, {NULL, NULL}, }; render_commandline_message(""); int found_something = 0; for (struct MappingNames * map = maps; map->name; ++map) { /* See if this key is mapped */ struct action_map * m = map->map; while (m->key != -1) { if (m->key == key) { if (m->options & opt_krk) { struct StringBuilder sb = {0}; krk_pushStringBuilderFormat(&sb, "%R", m->callable); krk_pushStringBuilder(&sb, '\0'); render_commandline_message("%s: %s\n", map->name, sb.bytes); krk_discardStringBuilder(&sb); } else { struct action_def * action = find_action(m->method); render_commandline_message("%s: %s\n", map->name, action ? action->description : "(unmapped)"); found_something = 1; } break; } m++; } } if (!found_something) { render_commandline_message("Nothing bound for this key"); } pause_for_key(); return 0; _invalid_key_name: render_error("Invalid key name"); return 1; } BIM_COMMAND(setcolor, "setcolor", "Set colorscheme colors") { #define PRINT_COLOR do { \ render_commandline_message("%20s = ", c->name); \ set_colors(*c->value, *c->value); \ printf(" "); \ set_colors(COLOR_FG, COLOR_BG); \ printf(" %s\n", *c->value); \ } while (0) if (argc < 2) { /* Print colors */ struct ColorName * c = color_names; while (c->name) { PRINT_COLOR; c++; } pause_for_key(); } else { char * colorname = argv[1]; if (argc == 2) { struct ColorName * c = color_names; while (c->name) { if (!strcmp(c->name, colorname)) { PRINT_COLOR; return 0; } c++; } render_error(":setcolor "); return 1; } char * colorvalue = argv[2]; struct ColorName * c = color_names; while (c->name) { if (!strcmp(c->name, colorname)) { *(c->value) = strdup(colorvalue); return 0; } c++; } render_error("Unknown color: %s", colorname); return 1; } return 0; #undef PRINT_COLOR } BIM_COMMAND(checkprop,"checkprop","Check a property value; returns the inverse of the property") { if (argc < 2) { return 1; } if (!strcmp(argv[1],"can_scroll")) return !global_config.can_scroll; else if (!strcmp(argv[1],"can_hideshow")) return !global_config.can_hideshow; else if (!strcmp(argv[1],"can_altscreen")) return !global_config.can_altscreen; else if (!strcmp(argv[1],"can_mouse")) return !global_config.can_mouse; else if (!strcmp(argv[1],"can_unicode")) return !global_config.can_unicode; else if (!strcmp(argv[1],"can_bright")) return !global_config.can_bright; else if (!strcmp(argv[1],"can_title")) return !global_config.can_title; else if (!strcmp(argv[1],"can_bce")) return !global_config.can_bce; else if (!strcmp(argv[1],"can_24bit")) return !global_config.can_24bit; else if (!strcmp(argv[1],"can_256color")) return !global_config.can_256color; else if (!strcmp(argv[1],"can_italic")) return !global_config.can_italic; render_error("Unknown property '%s'", argv[1]); return 1; } BIM_COMMAND(action,"action","Execute a bim action") { if (argc < 2) { render_error("Expected :action [arg [arg [arg...]]]"); return 1; } /* Split argument on spaces */ char * action = argv[1]; char * arg1 = NULL, * arg2 = NULL, * arg3 = NULL; if (argc > 2) arg1 = argv[2]; if (argc > 3) arg2 = argv[3]; if (argc > 4) arg3 = argv[4]; /* Find the action */ for (int i = 0; i < flex_mappable_actions_count; ++i) { if (!strcmp(mappable_actions[i].name, action)) { /* Count arguments */ int args = 0; if (mappable_actions[i].options & ARG_IS_CUSTOM) args++; if (mappable_actions[i].options & ARG_IS_INPUT) args++; if (mappable_actions[i].options & ARG_IS_PROMPT) args++; if (args == 0) { ((action_no_arg)mappable_actions[i].action)(); } else if (args == 1) { if (!arg1) { render_error("Expected one argument"); return 1; } ((action_one_arg)mappable_actions[i].action)(atoi(arg1)); } else if (args == 2) { if (!arg2) { render_error("Expected two arguments"); return 1; } ((action_two_arg)mappable_actions[i].action)(atoi(arg1), atoi(arg2)); } else if (args == 3) { if (!arg3) { render_error("Expected three arguments"); return 1; } ((action_three_arg)mappable_actions[i].action)(atoi(arg1), atoi(arg2), atoi(arg3)); } return 0; } } render_error("Unknown action: %s", action); return 1; } char * describe_options(int options) { static char out[16]; memset(out,0,sizeof(out)); if (options & opt_rep) strcat(out,"r"); /* Repeats */ if (options & opt_arg) strcat(out,"a"); /* takes Argument */ if (options & opt_char) strcat(out,"c"); /* takes Character */ if (options & opt_nav) strcat(out,"n"); /* consumes Nav buffer */ if (options & opt_rw) strcat(out,"w"); /* read-Write */ if (options & opt_norm) strcat(out,"m"); /* changes Mode */ if (options & opt_byte) strcat(out,"b"); /* takes Byte */ return out; } void dump_map_commands(const char * name, struct action_map * map) { struct action_map * m = map; while (m->key != -1) { if (m->options & opt_krk) { fprintf(stdout,"# key %s bound in %s mode to a krk function", name_from_key(m->key), name); } else { struct action_def * action = find_action(m->method); fprintf(stdout,"mapkey %s %s %s", name, name_from_key(m->key), action ? action->name : "none"); if (m->options) { printf(" %s", describe_options(m->options)); if (m->options & opt_arg) { printf(" %d", m->arg); } } } printf("\n"); m++; } } BIM_COMMAND(mapkey,"mapkey","Map a key to an action.") { if (argc < 2) goto _argument_error; char * mode = argv[1]; char * key = strstr(mode," "); if (!key) goto _argument_error; *key = '\0'; key++; char * action = strstr(key," "); if (!action) goto _argument_error; *action = '\0'; action++; /* Options are optional */ char * options = strstr(action, " "); char * arg = NULL; if (options) { *options = '\0'; options++; arg = strstr(options, " "); if (arg) { *arg = '\0'; arg++; } } render_status_message("Going to map key %s in mode %s to action %s with options %s, %s", key, mode, action, options, arg); /* Convert mode to mode name */ struct action_map ** mode_map = NULL; for (struct mode_names * m = mode_names; m->name; ++m) { if (!strcmp(m->name, mode)) { mode_map = m->mode; break; } } if (!mode_map) { render_error("invalid mode: %s", mode); return 1; } enum Key keycode = key_from_name(key); if (keycode == -1) { render_error("invalid key: %s", key); return 1; } struct action_def * action_def = NULL; for (int i = 0; i < flex_mappable_actions_count; ++i) { if (!strcmp(mappable_actions[i].name, action)) { action_def = &mappable_actions[i]; break; } } if (!action_def) { render_error("invalid action: %s", action); return 1; } /* Sanity check required options */ if ((action_def->options & ARG_IS_CUSTOM) && (!options || (!strchr(options,'a') && !strchr(options,'n')))) goto _action_sanity; if ((action_def->options & ARG_IS_PROMPT) && (!options || (!strchr(options,'c') && !strchr(options,'b')))) goto _action_sanity; if ((action_def->options & ACTION_IS_RW) && (!options || !strchr(options,'w'))) goto _action_sanity; int option_map = 0; if (options) { for (char * o = options; *o; ++o) { switch (*o) { case 'r': option_map |= opt_rep; break; case 'a': option_map |= opt_arg; break; case 'c': option_map |= opt_char; break; case 'n': option_map |= opt_nav; break; case 'w': option_map |= opt_rw; break; case 'm': option_map |= opt_norm; break; case 'b': option_map |= opt_byte; break; default: render_error("Invalid option flag: %c", *o); return 1; } } } if ((option_map & opt_arg) && !arg) { render_error("flag 'a' requires an additional argument"); return 1; } int arg_value = (option_map & opt_arg) ? atoi(arg) : 0; /* Make space */ struct action_map * candidate = NULL; for (struct action_map * m = *mode_map; m->key != -1; ++m) { if (m->key == keycode) { candidate = m; break; } } if (!candidate) { /* get size */ int len = 0; for (struct action_map * m = *mode_map; m->key != -1; m++, len++); *mode_map = realloc(*mode_map, sizeof(struct action_map) * (len + 2)); candidate = &(*mode_map)[len]; (*mode_map)[len+1].key = -1; } candidate->key = keycode; candidate->method = action_def->action; candidate->options = option_map; candidate->arg = arg_value; return 0; _action_sanity: render_error("action %s requires missing flag", action); return 1; _argument_error: render_error("usage: mapkey MODE KEY ACTION [OPTIONS [ARG]]"); return 1; } int main(int argc, char * argv[]) { findBim(argv); int opt; while ((opt = getopt(argc, argv, "?c:C:u:q:RS:O:-:")) != -1) { switch (opt) { case 'R': global_config.initial_file_is_read_only = 1; break; case 'q':; initialize(); global_config.use_biminfo = 0; global_config.go_to_line = 0; open_file(optarg); env->loading = 1; for (int i = 0; i < env->line_count; ++i) { recalculate_syntax(env->lines[i], i); } return 0; break; case 'c': case 'C': /* Print file to stdout using our syntax highlighting and color theme */ initialize(); global_config.use_biminfo = 0; global_config.go_to_line = 0; open_file(optarg); for (int i = 0; i < env->line_count; ++i) { recalculate_syntax(env->lines[i], i); if (opt == 'C') { draw_line_number(i); } render_line(env->lines[i], 6 * (env->lines[i]->actual + 1), 0, -1); reset(); fprintf(stdout, "\n"); } return 0; case 'u': global_config.bimrc_path = optarg; break; case 'S': global_config.syntax_fallback = optarg; break; case 'O': /* Set various display options */ if (set_capability(optarg)) { fprintf(stderr, "%s: unrecognized -O option: %s\n", argv[0], optarg); return 1; } break; case '-': if (!strcmp(optarg,"version")) { initialize(); /* Need to load bimrc to get themes */ update_screen_size(); /* Get terminal size if possible */ fprintf(stderr, "\033[1mbim\033[0m %s%s\n%s\n\n", BIM_VERSION, BIM_BUILD_DATE, BIM_COPYRIGHT); #define SECTION(title) do { \ int x, width; \ width = 2 + display_width_of_string(title); \ fprintf(stderr, " \033[1m%s\033[0m:", title); \ x = width; #define ENDSECTION() fprintf(stderr, "\n\n"); } while (0) #define ITEM(str) do { \ int my_width = display_width_of_string(str); \ if (x + my_width + 1 >= global_config.term_width) { \ fprintf(stderr, "\n"); \ for (x = 0; x <= width; ++x) fprintf(stderr, " "); \ fprintf(stderr, "%s", str); \ x += width; \ } else { \ fprintf(stderr, " %s", str); \ x += my_width + 1; \ } } while(0) SECTION("Syntax"); for (struct syntax_definition * s = syntaxes; syntaxes && s->name; ++s) { ITEM(s->name); } ENDSECTION(); SECTION("Themes"); for (struct theme_def * d = themes; themes && d->name; ++d) { ITEM(d->name); } ENDSECTION(); return 0; } else if (!strcmp(optarg,"help")) { show_usage(argv); return 0; } else if (!strcmp(optarg,"dump-mappings")) { initialize(); for (struct mode_names * m = mode_names; m->name; ++m) { dump_mapping(m->description, *m->mode); } return 0; } else if (!strcmp(optarg,"dump-commands")) { initialize(); dump_commands(); return 0; } else if (!strcmp(optarg,"html")) { if (optind >= argc) { show_usage(argv); return 1; } initialize(); global_config.go_to_line = 0; open_file(argv[optind]); for (int i = 0; i < env->line_count; ++i) { recalculate_syntax(env->lines[i], i); } convert_to_html(); /* write to stdout */ output_file(env, stdout); return 0; } else if (!strcmp(optarg,"dump-config")) { initialize(); /* Dump a config file representing the current key mappings */ for (struct mode_names * m = mode_names; m->name; ++m) { dump_map_commands(m->name, *m->mode); } return 0; } else if (strlen(optarg)) { fprintf(stderr, "bim: unrecognized option `%s'\n", optarg); return 1; } /* Else, this is -- to indicate end of arguments */ break; case '?': show_usage(argv); return 0; } } /* Set up terminal */ initialize(); init_terminal(); /* Open file */ if (argc > optind) { while (argc > optind) { open_file(argv[optind]); update_title(); if (global_config.initial_file_is_read_only) { env->readonly = 1; } optind++; } env = buffers[0]; } else { env = buffer_new(); setup_buffer(env); } update_title(); /* Draw the screen once */ redraw_all(); /* Start accepting key commands */ normal_mode(); return 0; } ================================================ FILE: apps/bim.h ================================================ #ifndef _BIM_CORE_H #define _BIM_CORE_H #define _XOPEN_SOURCE 700 #define _DARWIN_C_SOURCE #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __DATE__ # define BIM_BUILD_DATE " built " __DATE__ " at " __TIME__ #else # define BIM_BUILD_DATE DATE "" #endif #ifdef GIT_TAG # define TAG "-" GIT_TAG #else # define TAG "-alpha" #endif #define BLOCK_SIZE 4096 #define ENTER_KEY '\r' #define LINE_FEED '\n' #define BACKSPACE_KEY 0x08 #define DELETE_KEY 0x7F #define DEFAULT_KEY_WAIT (global_config.background_task ? 0 : -1) enum Key { KEY_TIMEOUT = -1, KEY_CTRL_AT = 0, /* Base */ KEY_CTRL_A, KEY_CTRL_B, KEY_CTRL_C, KEY_CTRL_D, KEY_CTRL_E, KEY_CTRL_F, KEY_CTRL_G, KEY_CTRL_H, KEY_CTRL_I, KEY_CTRL_J, KEY_CTRL_K, KEY_CTRL_L, KEY_CTRL_M, KEY_CTRL_N, KEY_CTRL_O, KEY_CTRL_P, KEY_CTRL_Q, KEY_CTRL_R, KEY_CTRL_S, KEY_CTRL_T, KEY_CTRL_U, KEY_CTRL_V, KEY_CTRL_W, KEY_CTRL_X, KEY_CTRL_Y, KEY_CTRL_Z, /* Note we keep ctrl-z mapped in termios as suspend */ KEY_CTRL_OPEN, KEY_CTRL_BACKSLASH, KEY_CTRL_CLOSE, KEY_CTRL_CARAT, KEY_CTRL_UNDERSCORE, /* Space... */ /* Some of these are equivalent to things above */ KEY_BACKSPACE = 0x08, KEY_LINEFEED = '\n', KEY_ENTER = '\r', KEY_TAB = '\t', /* Basic printable characters go here. */ /* Delete is special */ KEY_DELETE = 0x7F, /* Unicode codepoints go here */ KEY_ESCAPE = 0x400000, /* Escape would normally be 27, but is special because reasons */ KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, /* TODO ALT, SHIFT, etc., for F keys */ KEY_MOUSE, /* Must be followed with a 3-byte mouse read */ KEY_MOUSE_SGR, /* Followed by an SGR-style sequence of mouse data */ KEY_HOME, KEY_END, KEY_PAGE_UP, KEY_PAGE_DOWN, KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, KEY_SHIFT_UP, KEY_SHIFT_DOWN, KEY_SHIFT_RIGHT, KEY_SHIFT_LEFT, KEY_CTRL_UP, KEY_CTRL_DOWN, KEY_CTRL_RIGHT, KEY_CTRL_LEFT, KEY_ALT_UP, KEY_ALT_DOWN, KEY_ALT_RIGHT, KEY_ALT_LEFT, KEY_ALT_SHIFT_UP, KEY_ALT_SHIFT_DOWN, KEY_ALT_SHIFT_RIGHT, KEY_ALT_SHIFT_LEFT, KEY_SHIFT_TAB, /* Special signals for paste start, paste end */ KEY_PASTE_BEGIN, KEY_PASTE_END, }; struct key_name_map { enum Key keycode; char * name; }; extern struct key_name_map KeyNames[]; /** * Syntax highlighting flags. */ #define FLAG_NONE 0 #define FLAG_KEYWORD 1 #define FLAG_STRING 2 #define FLAG_COMMENT 3 #define FLAG_TYPE 4 #define FLAG_PRAGMA 5 #define FLAG_NUMERAL 6 #define FLAG_ERROR 7 #define FLAG_DIFFPLUS 8 #define FLAG_DIFFMINUS 9 #define FLAG_NOTICE 10 #define FLAG_BOLD 11 #define FLAG_LINK (12 + (1<<4)) #define FLAG_ESCAPE 13 #define FLAG_EXTRA 14 #define FLAG_SPECIAL 15 #define FLAG_LINK_COLOR 12 #define FLAG_UNDERLINE (1 << 4) #define FLAG_SELECT (1 << 5) #define FLAG_SEARCH (1 << 6) #define FLAG_MASK_COLORS 0x0F #define FLAG_MASK_ATTRIB 0x70 /** * Line buffer definitions * * Lines are essentially resizable vectors of char_t structs, * which represent single codepoints in the file. */ typedef struct { uint32_t display_width:4; uint32_t flags:7; uint32_t codepoint:21; } __attribute__((packed)) char_t; /** * Lines have available and actual lengths, describing * how much space was allocated vs. how much is being * used at the moment. */ typedef struct { int available; int actual; int istate; int is_current; int rev_status; char_t text[]; } line_t; typedef struct background_task { struct _env * env; void (*func)(struct background_task*); struct background_task * next; int _private_i; void * _private_p; } background_task_t; /** * Global configuration state * * At the moment, this is all in a global, but in the future * this should be passed around to various functions. */ typedef struct { /* Terminal size */ int term_width, term_height; int bottom_size; line_t ** yanks; size_t yank_count; int yank_is_full_lines; int tty_in; const char * bimrc_path; const char * syntax_fallback; uint32_t * search; int overlay_mode; line_t * command_buffer; int command_offset, command_col_no; struct syntax_definition * command_syn, * command_syn_back; int history_point; int search_point; int search_direction; int prev_line, prev_col, prev_coffset, prev_offset; unsigned int highlight_on_open:1; unsigned int initial_file_is_read_only:1; unsigned int go_to_line:1; unsigned int break_from_selection:1; unsigned int can_scroll:1; unsigned int can_hideshow:1; unsigned int can_altscreen:1; unsigned int can_mouse:1; unsigned int can_unicode:1; unsigned int can_bright:1; unsigned int can_title:1; unsigned int can_bce:1; unsigned int can_24bit:1; unsigned int can_256color:1; unsigned int can_italic:1; unsigned int can_insert:1; unsigned int can_bracketedpaste:1; unsigned int can_sgrmouse:1; unsigned int history_enabled:1; unsigned int highlight_parens:1; unsigned int smart_case:1; unsigned int highlight_current_line:1; unsigned int shift_scrolling:1; unsigned int check_git:1; unsigned int color_gutter:1; unsigned int relative_lines:1; unsigned int numbers:1; unsigned int horizontal_shift_scrolling:1; unsigned int hide_statusbar:1; unsigned int tabs_visible:1; unsigned int autohide_tabs:1; unsigned int smart_complete:1; unsigned int has_terminal:1; unsigned int search_wraps:1; unsigned int had_error:1; unsigned int use_biminfo:1; int cursor_padding; int split_percent; int scroll_amount; int tab_offset; char * tab_indicator; char * space_indicator; background_task_t * background_task; background_task_t * tail_task; uint32_t * paren_pairs; } global_config_t; #define OVERLAY_MODE_NONE 0 #define OVERLAY_MODE_READ_ONE 1 #define OVERLAY_MODE_COMMAND 2 #define OVERLAY_MODE_SEARCH 3 #define OVERLAY_MODE_COMPLETE 4 #define OVERLAY_MODE_FILESEARCH 5 #define HISTORY_SENTINEL 0 #define HISTORY_INSERT 1 #define HISTORY_DELETE 2 #define HISTORY_REPLACE 3 #define HISTORY_REMOVE_LINE 4 #define HISTORY_ADD_LINE 5 #define HISTORY_REPLACE_LINE 6 #define HISTORY_MERGE_LINES 7 #define HISTORY_SPLIT_LINE 8 #define HISTORY_BREAK 10 typedef struct history { struct history * previous; struct history * next; int type; int line; int col; union { struct { int lineno; int offset; int codepoint; int old_codepoint; } insert_delete_replace; struct { int lineno; line_t * contents; line_t * old_contents; } remove_replace_line; struct { int lineno; int split; } add_merge_split_lines; } contents; } history_t; /** * Buffer data * * A buffer describes a file, and stores * its name as well as the editor state * (cursor offsets, etc.) and the actual * line buffers. */ typedef struct _env { unsigned int loading:1; unsigned int tabs:1; unsigned int modified:1; unsigned int readonly:1; unsigned int indent:1; unsigned int checkgitstatusonwrite:1; unsigned int crnl:1; unsigned int numbers:1; unsigned int gutter:1; unsigned int slowop:1; int highlighting_paren; int maxcolumn; short mode; short tabstop; char * file_name; int offset; int coffset; int line_no; int line_count; int line_avail; int col_no; int preferred_column; struct syntax_definition * syntax; line_t ** lines; history_t * history; history_t * last_save_history; int width; int left; int start_line; int sel_col; int start_col; int prev_line; } buffer_t; struct theme_def { const char * name; void * callable; }; extern struct theme_def * themes; extern void add_colorscheme(struct theme_def theme); struct syntax_state { buffer_t * env; line_t * line; int line_no; int state; int i; }; struct completion_match { char * string; char * file; char * search; }; struct syntax_definition { char * name; char ** ext; int (*calculate)(struct syntax_state *); int prefers_spaces; int (*completion_qualifier)(int c); int (*completion_matcher)(uint32_t * comp, struct completion_match ** matches, int * matches_count, int complete_match, int * matches_len, buffer_t * env); void * krkFunc; void * krkClass; }; extern struct syntax_definition * syntaxes; /** * Editor mode states */ #define MODE_NORMAL 0 #define MODE_INSERT 1 #define MODE_LINE_SELECTION 2 #define MODE_REPLACE 3 #define MODE_CHAR_SELECTION 4 #define MODE_COL_SELECTION 5 #define MODE_COL_INSERT 6 #define MODE_DIRECTORY_BROWSE 7 struct action_def { char * name; uintptr_t action; int options; const char * description; }; extern struct action_def * mappable_actions; #define ARG_IS_INPUT 0x01 /* Takes the key that triggered it as the first argument */ #define ARG_IS_CUSTOM 0x02 /* Takes a custom argument which is specific to the method */ #define ARG_IS_PROMPT 0x04 /* Prompts for an argument. */ #define ACTION_IS_RW 0x08 /* Needs to be able to write. */ #define BIM_ACTION(name, options, description, ...) \ extern void name(__VA_ARGS__); /* Define the action with unknown arguments */ \ void __attribute__((constructor)) _install_ ## name (void) { \ add_action((struct action_def){#name, (uintptr_t)name, options, description}); \ } \ void name(__VA_ARGS__) struct command_def { char * name; int (*command)(char *, int, char * arg[]); const char * description; }; #define BIM_COMMAND(cmd_name, cmd_str, description) \ int bim_command_ ## cmd_name (char * cmd, int argc, char * argv[]); \ void __attribute__((constructor)) _install_cmd_ ## cmd_name (void) { \ add_command((struct command_def){cmd_str, bim_command_ ## cmd_name, description}); \ } \ int bim_command_ ## cmd_name (char * cmd __attribute__((unused)), int argc __attribute__((unused)), char * argv[] __attribute__((unused))) #define BIM_ALIAS(alias, alias_name, cmd_name) \ void __attribute__((constructor)) _install_alias_ ## alias_name (void) { \ add_command((struct command_def){alias, bim_command_ ## cmd_name, "Alias for " #cmd_name}); \ } #define BIM_PREFIX_COMMAND(cmd_name, cmd_prefix, description) \ int bim_command_ ## cmd_name (char * cmd, int argc, char * argv[]); \ void __attribute__((constructor)) _install_cmd_ ## cmd_name (void) { \ add_prefix_command((struct command_def){cmd_prefix, bim_command_ ## cmd_name, description}); \ } \ int bim_command_ ## cmd_name (char * cmd __attribute__((unused)), int argc __attribute__((unused)), char * argv[] __attribute__((unused))) extern const char * flag_to_color(int _flag); extern void redraw_line(int x); extern int git_examine(char * filename); extern void search_next(void); extern void set_preferred_column(void); extern void quit(const char * message); extern void close_buffer(void); extern void set_syntax_by_name(const char * name); extern void rehighlight_search(line_t * line); extern void try_to_center(void); extern int read_one_character(char * message); extern void bim_unget(int c); #define bim_getch() bim_getch_timeout(200) extern int bim_getch_timeout(int timeout); extern buffer_t * buffer_new(void); extern FILE * open_biminfo(void); extern int fetch_from_biminfo(buffer_t * buf); extern int update_biminfo(buffer_t * buf, int is_open); extern buffer_t * buffer_close(buffer_t * buf); extern int to_eight(uint32_t codepoint, char * out); extern char * name_from_key(enum Key keycode); extern void add_action(struct action_def action); extern void open_file(char * file); extern void recalculate_selected_lines(void); extern void add_command(struct command_def command); extern void add_prefix_command(struct command_def command); extern void render_command_input_buffer(void); extern void unhighlight_matching_paren(void); extern void add_syntax(struct syntax_definition def); struct ColorName { const char * name; const char ** value; }; extern struct ColorName color_names[]; struct bim_function { char * command; struct bim_function * next; }; extern struct bim_function ** user_functions; extern int run_function(char * name); extern int has_function(char * name); extern void find_matching_paren(int * out_line, int * out_col, int in_col); extern void render_error(char * message, ...); extern void render_commandline_message(char * message, ...); extern void pause_for_key(void); #define add_match(match_string, match_file, match_search) do { \ if (*matches_count == *matches_len) { \ (*matches_len) *= 2; \ *matches = realloc(*matches, sizeof(struct completion_match) * (*matches_len)); \ } \ (*matches)[*matches_count].string = strdup(match_string); \ (*matches)[*matches_count].file = strdup(match_file); \ (*matches)[*matches_count].search = strdup(match_search); \ (*matches_count)++; \ } while (0) struct action_map { int key; int options; union { struct { uintptr_t method; int arg; }; KrkValue callable; }; }; #define opt_rep 0x1 /* This action will be repeated */ #define opt_arg 0x2 /* This action will take a specified argument */ #define opt_char 0x4 /* This action will read a character to pass as an argument */ #define opt_nav 0x8 /* This action will consume the nav buffer as its argument */ #define opt_rw 0x10 /* Must not be read-only */ #define opt_norm 0x20 /* Returns to normal mode */ #define opt_byte 0x40 /* Same as opt_char but forces a byte */ #define opt_krk 0x80 struct mode_names { const char * description; const char * name; struct action_map ** mode; }; extern struct mode_names mode_names[]; #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 = (state->line->text[state->i].flags & (3 << 5)) | (flag); \ } } while (0) #define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1) #define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1) #define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1) #define skip() (state->i++) #define charrel(x) ((state->i + (x) >= 0 && state->i + (x) < state->line->actual) ? state->line->text[(state->i+(x))].codepoint : -1) static int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)); static int common_comment_buzzwords(struct syntax_state * state); static int paint_comment(struct syntax_state * state); static struct syntax_definition * find_syntax_calculator(const char * name); /* Hacky workaround for isdigit not really accepting Unicode stuff */ static __attribute__((used)) int _isdigit(int c) { if (c > 128) return 0; return isdigit(c); } static __attribute__((used)) int _isxdigit(int c) { if (c > 128) return 0; return isxdigit(c); } #undef isdigit #undef isxdigit #define isdigit(c) _isdigit(c) #define isxdigit(c) _isxdigit(c) #endif /* _BIM_CORE_H */ ================================================ FILE: apps/block-dev-stats.c ================================================ /** * @brief Show block device statistics, where available. * * Shows cache hit/miss/write counts for ATA devices, mostly. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include int main(int argc, char * argv[]) { if (argc < 2) return 1; int fd = open(argv[1],O_RDONLY); if (fd < 0) { fprintf(stderr, "open: %d\n", fd); return 2; } uint64_t stats[4] = {-1}; long res = ioctl(fd, 0x2A01234UL, &stats); if (res < 0) { fprintf(stderr, "ioctl: %ld\n", res); return 3; } fprintf(stderr, "hits:\t%zu\n", stats[0]); fprintf(stderr, "misses:\t%zu\n", stats[1]); fprintf(stderr, "evicts:\t%zu\n", stats[2]); fprintf(stderr, "writes:\t%zu\n", stats[3]); return 0; } ================================================ FILE: apps/cal.c ================================================ /** * @brief cal - print a calendar * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2019 K. Lange */ #include #include #include #include #include const char * month_names[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", }; int days_in_months[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, }; int main(int argc, char * argv[]) { if (argc > 1) { fprintf(stderr, "%s: arguments not currently supported\n", argv[0]); return 1; } struct timeval now; gettimeofday(&now, NULL); struct tm actual; struct tm * timeinfo; timeinfo = localtime((time_t *)&now.tv_sec); memcpy(&actual, timeinfo, sizeof(struct tm)); timeinfo = &actual; char month[20]; sprintf(month, "%s %d", month_names[timeinfo->tm_mon], timeinfo->tm_year + 1900); int len = (20 - strlen(month)) / 2; while (len > 0) { printf(" "); len--; } /* Heading */ printf("%s\n", month); printf("Su Mo Tu We Th Fr Sa\n"); /* Now's the fun part. */ int days_in_month = days_in_months[timeinfo->tm_mon]; if (days_in_month == 0) { /* How many days in February? */ struct tm tmp; memcpy(&tmp, timeinfo, sizeof(struct tm)); tmp.tm_mday = 29; tmp.tm_hour = 12; time_t tmp3 = mktime(&tmp); struct tm * tmp2 = localtime(&tmp3); if (tmp2->tm_mday == 29) { days_in_month = 29; } else { days_in_month = 28; } } int mday = timeinfo->tm_mday; int wday = timeinfo->tm_wday; /* 0 == sunday */ while (mday > 1) { mday--; wday = (wday + 6) % 7; } for (int i = 0; i < wday; ++i) { printf(" "); } while (mday <= days_in_month) { if (mday == timeinfo->tm_mday) { printf("\033[7m%2d\033[0m ", mday); } else { printf("%2d ", mday); } if (wday == 6) { printf("\n"); } mday += 1; wday = (wday + 1) % 7; } if (wday != 0) { printf("\n"); } return 0; } ================================================ FILE: apps/calculator.c ================================================ /** * @file apps/calculator.c * @brief Four-function calculator app. * * This calculator app is intended to be a more straightforward playground * for building out a widget toolkit. The calculator presents buttons in * a grid layout alongside a text input box and a menubar. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include static struct menu_bar menu_bar = {0}; static struct menu_bar_entries menu_entries[] = { {"File", "file"}, {"Help", "help"}, {NULL, NULL}, }; static yutani_t * yctx; static yutani_window_t * window = NULL; static gfx_context_t * ctx = NULL; static int32_t width = 600; static int32_t height = 240; static char * title_str = "Calculator"; static int textInputIsAccumulatorValue = 0; static char accumulator[1024] = {0}; static char textInput[1024] = {0}; struct CalculatorButton { struct TTKButton ttkButton; char * label; void (*onClick)(struct CalculatorButton *); }; static void clear_result(void) { if (textInputIsAccumulatorValue) { textInputIsAccumulatorValue = 0; *textInput = '\0'; *accumulator = '\0'; } } static void calc_numeric(char * text) { clear_result(); strcat(textInput, text); } static void calc_func(char * txt) { clear_result(); strcat(accumulator, textInput); strcat(accumulator, txt); *textInput = '\0'; textInputIsAccumulatorValue = 0; } static void calc_backspace(void) { if (textInputIsAccumulatorValue) { clear_result(); } else if (!*textInput) { size_t l = strlen(accumulator); if (l) { accumulator[l-1] = '\0'; } } else { size_t l = strlen(textInput); if (l) { textInput[l-1] = '\0'; } } } static void btn_numeric(struct CalculatorButton * self) { calc_numeric(self->label); } static void btn_func_div(struct CalculatorButton * self) { calc_func("/"); } static void btn_func_mul(struct CalculatorButton * self) { calc_func("*"); } static void btn_func_sub(struct CalculatorButton * self) { calc_func("-"); } static void btn_func_add(struct CalculatorButton * self) { calc_func("+"); } static void btn_func_pct(struct CalculatorButton * self) { calc_func("%"); } static void btn_func_opr(struct CalculatorButton * self) { calc_func("("); } static void btn_func_cpr(struct CalculatorButton * self) { calc_func(")"); } static void btn_func_clr(struct CalculatorButton * self) { if (!*textInput) { *accumulator = '\0'; } else { *textInput = '\0'; } } static void btn_func_equ(struct CalculatorButton * self) { if (textInputIsAccumulatorValue) return; if (*textInput) { strcat(accumulator, textInput); *textInput = '\0'; } KrkValue result = krk_interpret(accumulator, ""); if (!IS_NONE(result)) { krk_attachNamedValue(&vm.builtins->fields, "_", result); krk_push(result); krk_push(krk_stringFromFormat("%R", result)); krk_swap(1); krk_pop(); if (IS_STRING(krk_peek(0))) { snprintf(textInput, 1024, "%s", AS_CSTRING(krk_peek(0))); } krk_pop(); } else if (krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION) { strcat(textInput, "Error."); } else { strcat(textInput, "*"); } krk_resetStack(); textInputIsAccumulatorValue = 1; } #define N(n) {{0},#n,btn_numeric} #define F(n,func) {{0},n,btn_func_ ## func} #define BTN_ROWS 4 #define BTN_COLS 5 struct CalculatorButton buttons[] = { N(7), N(8), N(9), F("÷",div), F("(",opr), N(4), N(5), N(6), F("×",mul), F(")",cpr), N(1), N(2), N(3), F("-",sub), F("C",clr), N(0), N(.), F("mod",pct), F("+",add), F("=",equ), }; static void redraw(void) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); draw_fill(ctx, rgb(204,204,204)); draw_rectangle_solid(ctx, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT + 4, window->width - bounds.width, 42, rgb(255,255,255)); struct MarkupState * renderer = markup_setup_renderer(ctx, bounds.left_width + 5, bounds.top_height + MENU_BAR_HEIGHT + 14, rgb(0,0,0), 0); markup_set_base_font_size(renderer, 10); markup_set_base_state(renderer, MARKUP_TEXT_STATE_MONO); markup_push_raw_string(renderer, accumulator); if (!textInputIsAccumulatorValue && !textInput[0]) markup_push_raw_string(renderer, "_"); markup_finish_renderer(renderer); renderer = markup_setup_renderer(ctx, bounds.left_width + 5, bounds.top_height + MENU_BAR_HEIGHT + 35, rgb(0,0,0), 0); markup_set_base_font_size(renderer, 16); markup_set_base_state(renderer, (textInputIsAccumulatorValue ? MARKUP_TEXT_STATE_BOLD : 0) | MARKUP_TEXT_STATE_MONO); markup_push_raw_string(renderer, textInput); if (!textInputIsAccumulatorValue && textInput[0]) markup_push_raw_string(renderer, "_"); markup_finish_renderer(renderer); for (int i = 0; i < (BTN_ROWS * BTN_COLS); ++i) { ttk_button_draw(ctx, &buttons[i].ttkButton); } menu_bar_render(&menu_bar, ctx); render_decorations(window, ctx, title_str); flip(ctx); yutani_flip(yctx, window); } static void redraw_window_callback(struct menu_bar * self) { (void)self; redraw(); } int in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) { if (me->new_y >= button->y && me->new_y < button->y + button->height) { if (me->new_x >= button->x && me->new_x < button->x + button->width) { return 1; } } return 0; } #define BASE_TOP 50 void setup_buttons(void) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); menu_bar.x = bounds.left_width; menu_bar.y = bounds.top_height; menu_bar.width = ctx->width - bounds.width; menu_bar.window = window; size_t ind = 0; int aWidth = ctx->width - bounds.width - 10; int baseWidth = aWidth / BTN_COLS; int extraWidth = aWidth - baseWidth * BTN_COLS; int aHeight = ctx->height - bounds.height - 10 - MENU_BAR_HEIGHT - BASE_TOP; int baseHeight = aHeight / BTN_ROWS; int extraHeight = aHeight - baseHeight * BTN_ROWS; for (int row = 0; row < BTN_ROWS; ++row) { for (int col = 0; col < BTN_COLS; ++col, ++ind) { buttons[ind].ttkButton.title = buttons[ind].label; buttons[ind].ttkButton.width = ((col + 1 < BTN_COLS) ? baseWidth : (baseWidth + extraWidth)) - 5; buttons[ind].ttkButton.height = ((row + 1 < BTN_ROWS) ? baseHeight : (baseHeight + extraHeight)) - 5; buttons[ind].ttkButton.x = 5 + bounds.left_width + baseWidth * col; buttons[ind].ttkButton.y = MENU_BAR_HEIGHT + BASE_TOP + 5 + bounds.top_height + baseHeight * row; } } } void resize_finish(int w, int h) { if (w < 300 || h < 240) { yutani_window_resize_offer(yctx, window, w < 300 ? 300 : w, h < 240 ? 240 : h); return; } yutani_window_resize_accept(yctx, window, w, h); reinit_graphics_yutani(ctx, window); width = w; height = h; setup_buttons(); redraw(); yutani_window_resize_done(yctx, window); } static void clear_highlights(int *changed) { for (int i = 0; i < BTN_ROWS * BTN_COLS; ++i) { if (buttons[i].ttkButton.hilight) { *changed = 1; buttons[i].ttkButton.hilight = 0; } } } void set_hilight(struct TTKButton * button, int hilight) { int changed = 0; if (!button) { clear_highlights(&changed); } else if (button && (button->hilight != hilight)) { changed = 1; clear_highlights(&changed); button->hilight = hilight; } if (changed) redraw(); } static void update_buttons(struct yutani_msg_window_mouse_event * me, int hilight) { struct TTKButton * inButton = NULL; for (int i = 0; i < BTN_ROWS * BTN_COLS; ++i) { if (in_button(&buttons[i].ttkButton, me)) { inButton = &buttons[i].ttkButton; break; } } set_hilight(inButton, inButton ? hilight : 0); } static void _menu_action_exit(struct MenuEntry * entry) { exit(0); } static void _menu_action_help(struct MenuEntry * entry) { system("help-browser calculator.trt &"); redraw(); } static void _menu_action_about(struct MenuEntry * entry) { /* Show About dialog */ char about_cmd[1024] = "\0"; strcat(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\" "); char coords[100]; sprintf(coords, "%d %d &", (int)window->x + (int)window->width / 2, (int)window->y + (int)window->height / 2); strcat(about_cmd, coords); system(about_cmd); redraw(); } 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(); markup_text_init(); 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; 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, "calculator"); ctx = init_graphics_yutani_double_buffer(window); menu_bar.entries = menu_entries; menu_bar.redraw_callback = redraw_window_callback; menu_bar.set = menu_set_create(); struct MenuList * m = menu_create(); /* File */ menu_insert(m, menu_create_normal("exit",NULL,"Exit", _menu_action_exit)); menu_set_insert(menu_bar.set, "file", m); m = menu_create(); menu_insert(m, menu_create_normal("help",NULL,"Contents",_menu_action_help)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal("star",NULL,"About Calculator",_menu_action_about)); menu_set_insert(menu_bar.set, "help", m); setup_buttons(); redraw(); struct TTKButton * _down_button = NULL; vm.binpath = strdup("/bin/calculator"); /* Just assume this so we can get module imports */ krk_initVM(KRK_GLOBAL_CLEAN_OUTPUT); krk_startModule("__main__"); 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; yutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)ke->wid); if (win == window) { if (ke->event.action == KEY_ACTION_DOWN) { if (ke->event.key == '\n') btn_func_equ(NULL); else if ((ke->event.key >= '0' && ke->event.key <= '9') || ke->event.key == '.') { char tmp[2] = {ke->event.key, '\0'}; calc_numeric(tmp); } else if ((ke->event.key == KEY_BACKSPACE)) { calc_backspace(); } else if ((ke->event.key)) { char tmp[2] = {ke->event.key, '\0'}; calc_func(tmp); } redraw(); } } } 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*)(uintptr_t)wf->wid); if (win) { win->focused = wf->focused; redraw(); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; if (me->wid == window->wid) { 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; } menu_bar_mouse_event(yctx, window, &menu_bar, me, me->new_x, me->new_y); struct decor_bounds bounds; decor_get_bounds(window, &bounds); if (me->new_y > bounds.top_height) { if (me->command == YUTANI_MOUSE_EVENT_DOWN) { for (int i = 0; i < BTN_ROWS * BTN_COLS; ++i) { if (in_button(&buttons[i].ttkButton, me)) { set_hilight(&buttons[i].ttkButton, 2); _down_button = &buttons[i].ttkButton; } } } else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) { if (_down_button) { if (in_button(_down_button, me)) { ((struct CalculatorButton*)_down_button)->onClick((struct CalculatorButton*)_down_button); _down_button->hilight = 0; } } _down_button = NULL; } if (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { update_buttons(me, 1); } else if (_down_button) { if (in_button(_down_button, me)) { set_hilight(_down_button, 2); } else { set_hilight(NULL, 0); } } } } } 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; } ================================================ FILE: apps/cat-img.c ================================================ /** * @brief Display images in a Toaru terminal. * * @copyright * 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 #include #include #include #include #include #include #include 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; int ret = 0; while (optind < argc) { sprite_t * image = calloc(sizeof(sprite_t),1); if (load_sprite(image, argv[optind])) { free(image); ret |= 1; optind++; continue; } 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 ret; } ================================================ FILE: apps/cat.c ================================================ /** * @brief 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. * * @copyright * 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 */ #include #include #include #include #include #include #define CHUNK_SIZE 4096 static char * _argv_0; static char * _file; 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; if (r < 0) { fprintf(stderr, "%s: %s: %s\n", _argv_0, _file, strerror(errno)); return; } write(STDOUT_FILENO, buf, r); } } int main(int argc, char ** argv) { int ret = 0; _argv_0 = argv[0]; if (argc == 1) { _file = "stdin"; doit(0); } for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i],"-")) { _file = "stdin"; doit(0); continue; } _file = argv[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; } ================================================ FILE: apps/chmod.c ================================================ /** * @brief chmod - change file permissions * * 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. * * @copyright * 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 */ #include #include #include #include #include #include #include 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' ... '7': 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; default: fprintf(stderr, "%s: invalid mode '%s'\n", argv[0], argv[1]); return 1; } } int out = 0; for (int i = 2; i < argc; ++i) { int actual_mode = 0; struct stat _stat; if (stat(argv[i], &_stat) < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); out |= 1; continue; } switch (mode_set) { case MODE_SET: actual_mode = mode; break; case MODE_ADD: actual_mode = (_stat.st_mode & 07777) | mode; break; case MODE_REMOVE: actual_mode = (_stat.st_mode & 07777) & ~(mode); break; } if (chmod(argv[i], actual_mode) < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); out |= 1; continue; } } return out; } ================================================ FILE: apps/chown.c ================================================ /** * @brief chown - bad implementation thereof * * @copyright * 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 */ #include #include #include #include #include #include static int usage(char * argv[]) { fprintf(stderr, "usage: %s [OWNER][:[GROUP]] FILE...\n", argv[0]); return 1; } static int invalid(char * argv[], char c) { fprintf(stderr, "%s: %c: unrecognized option\n", argv[0], c); return 1; } static int parse_user_group(char * argv[], char * arg, uid_t * user, gid_t * group) { /* Does this look like a number? */ if (*arg >= '0' && *arg <= '9') { /* Try to extract */ char * endptr; unsigned long int number = strtoul(arg, &endptr, 10); if (*endptr != ':' && *endptr != '\0') { fprintf(stderr, "%s: %s: Invalid user/group specification\n", argv[0], arg); } *user = number; arg = endptr; } else if (*arg == ':') { *user = -1; arg++; } else { char * colon = strstr(arg, ":"); if (colon) { *colon = '\0'; } /* Check name */ struct passwd * userEnt = getpwnam(arg); if (!userEnt) { fprintf(stderr, "%s: %s: Invalid user\n", argv[0], arg); return 1; } *user = userEnt->pw_uid; if (colon) { arg = colon + 1; } else { arg = NULL; } } if (arg && *arg) { if (*arg >= '0' && *arg <= '9') { char * endptr; unsigned long int number = strtoul(arg, &endptr, 10); if (*endptr != '\0') { fprintf(stderr, "%s: %s: Invalid group specification\n", argv[0], arg); } *group = number; arg = endptr; } else { struct passwd * userEnt = getpwnam(arg); if (!userEnt) { fprintf(stderr, "%s: %s: Invalid group\n", argv[0], arg); return 1; } *group = userEnt->pw_uid; } } return 0; } int main(int argc, char * argv[]) { int i = 1; for (; i < argc; i++) { if (argv[i][0] != '-') break; switch (argv[i][0]) { case 'h': return usage(argv); default: return invalid(argv,argv[i][0]); } } if (i + 1 >= argc) return usage(argv); uid_t user = -1; uid_t group = -1; if (parse_user_group(argv, argv[i++], &user, &group)) return 1; if (user == -1 && group == -1) return 0; int retval = 0; for (; i < argc; i++) { if (chown(argv[i], user, group)) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); retval = 1; } } return retval; } ================================================ FILE: apps/clear.c ================================================ /** * @brief 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. * * @copyright * 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 */ #include int main(int argc, char ** argv) { printf("\033[H\033[2J"); fflush(stdout); return 0; } ================================================ FILE: apps/cmp.c ================================================ /** * @brief Compare files * * Standard POSIX utility. * * XXX Only use this with normal files; errors in special files * will not be handled well with our current libc. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2025 K. Lange */ #include #include #include #include #include static int usage(char * argv[]) { fprintf(stderr, "usage: %s [-l | -s] file1 file2\n", argv[0]); return 2; } int main(int argc, char * argv[]) { int c; int format = 0; int retval = 0; while ((c = getopt(argc, argv, "ls")) != -1) { switch (c) { case 'l': format = 'l'; break; case 's': format = 's'; break; default: return usage(argv); } } if (optind + 1 >= argc) return usage(argv); const char * file_a = argv[optind]; const char * file_b = argv[optind+1]; FILE * a; FILE * b; if (!strcmp(file_a, "-")) { a = stdin; file_a = "stdin"; } else { a = fopen(file_a, "r"); if (!a) { fprintf(stderr, "%s: %s: %s\n", argv[0], file_a, strerror(errno)); return 2; } } if (!strcmp(file_b, "-")) { b = stdin; file_b = "stdin"; } else { b = fopen(file_b, "r"); if (!b) { fprintf(stderr, "%s: %s: %s\n", argv[0], file_b, strerror(errno)); fclose(a); return 2; } } if (a == stdin && b == stdin) { fprintf(stderr, "stdin may only be specified for one argument\n"); return 2; } size_t count = 1; size_t line = 1; while (!feof(a) && !feof(b)) { int _a = fgetc(a); if (_a < 0 && ferror(a)) { fprintf(stderr, "%s: %s: %s\n", argv[0], file_a, strerror(errno)); retval = 2; goto finish; } int _b = fgetc(b); if (_b < 0 && ferror(b)) { fprintf(stderr, "%s: %s: %s\n", argv[0], file_b, strerror(errno)); retval = 2; goto finish; } if (_a != _b) { if (_a == EOF || _b == EOF) { retval = 1; if (format != 's') fprintf(stderr, "%s: EOF on %s\n", argv[0], _a == EOF ? file_a : file_b); goto finish; } switch (format) { case 0: fprintf(stdout, "%s %s differ: char %zu, line %zu\n", file_a, file_b, count, line); /* fallthrough */ case 's': retval = 1; goto finish; case 'l': fprintf(stdout, "%zu %o %o\n", count, _a, _b); retval = 1; break; } } count += 1; if (_a == '\n') line += 1; } finish: if (a != stdin) fclose(a); if (b != stdin) fclose(b); return retval; } ================================================ FILE: apps/color-picker.c ================================================ /** * @file apps/color-picker.c * @brief Color picker * * Color Picker widget demo, eventually maybe a paint app again... * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #define dist(a,b,c,d) sqrt((double)(((a) - (c)) * ((a) - (c)) + ((b) - (d)) * ((b) - (d)))) static yutani_t * yctx; static yutani_window_t * wina; static int should_exit = 0; uint16_t win_width; uint16_t win_height; uint16_t off_x; uint16_t off_y; static int needs_redraw = 0; gfx_context_t * ctx; static struct TT_Font * tt_font_thin = NULL; void redraw_borders() { render_decorations(wina, ctx, "Color Picker"); } double fmin(double a, double b) { return a < b ? a : b; } double fmax(double a, double b) { return a > b ? a : b; } uint32_t hsv_to_rgb(float h, float s, float v) { float c = v * s; float hp = fmod(h, 2 * M_PI); float x = c * (1.0 - fabs(fmod(hp / 1.0472, 2) - 1.0)); float m = v - c; float rp, gp, bp; if (hp <= 1.0472) { rp = c; gp = x; bp = 0; } else if (hp <= 2.0944) { rp = x; gp = c; bp = 0; } else if (hp <= 3.1416) { rp = 0; gp = c; bp = x; } else if (hp <= 4.1888) { rp = 0; gp = x; bp = c; } else if (hp <= 5.2360) { rp = x; gp = 0; bp = c; } else { rp = c; gp = 0; bp = x; } return rgb((rp + m) * 255, (gp + m) * 255, (bp + m) * 255); } void rgb_to_hsv(uint32_t c, double *h, double *s, double *v) { float r = _RED(c) / 255.0; float g = _GRE(c) / 255.0; float b = _BLU(c) / 255.0; float c_max = fmax(r,fmax(g,b)); float c_min = fmin(r,fmin(g,b)); float delta = c_max - c_min; if (!delta) { *h = 0; } else if (c_max == r) { *h = 1.0471975512 * fmod((g - b) / delta, 6.0); } else if (c_max == g) { *h = 1.0471975512 * ((b - r) / delta + 2.0); } else { *h = 1.0471975512 * ((r - g) / delta + 4.0); } if (c_max == 0) { *s = 0; } else { *s = delta / c_max; } *v = c_max; } struct Picker { int x; int y; int radius; struct gfx_point red; struct gfx_point white; struct gfx_point black; double dp; double hue; }; static double pt_sign(const struct gfx_point *p1, const struct gfx_point *p2, const struct gfx_point *p3) { return (p1->x - p3->x) * (p2->y - p3->y) - (p2->x - p3->x) * (p1->y - p3->y); } static 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) { double d1 = pt_sign(pt,v1,v2); double d2 = pt_sign(pt,v2,v3); double d3 = pt_sign(pt,v3,v1); int neg = (d1 < 0) || (d2 < 0) || (d3 < 0); int pos = (d1 > 0) || (d2 > 0) || (d3 > 0); *proximity = 1.0; *proximity = fmin(*proximity, gfx_line_distance(pt, v1, v2)); *proximity = fmin(*proximity, gfx_line_distance(pt, v2, v3)); *proximity = fmin(*proximity, gfx_line_distance(pt, v3, v1)); return !(neg && pos); } static uint32_t gfx_fill_magic(int32_t x, int32_t y, double alpha, void * extra) { if (alpha > 1.0) alpha = 1.0; if (alpha < 0.0) alpha = 0.0; struct Picker * picker = extra; /* Picker center */ int _x = picker->x + picker->radius; int _y = picker->y + picker->radius; /* hole in the middle */ double r = dist(x,y,_x,_y); int inner_radius = picker->radius * 0.8; uint32_t c; if (r < inner_radius) { struct gfx_point p = {(float)x,(float)y}; /* Are we in the triangle? */ double proximity; if (!in_triangle(&p,&picker->red,&picker->white,&picker->black,&proximity)) { return rgba(0,0,0,0); } alpha = proximity; double h = picker->hue; double v = 1.0 - (gfx_line_distance(&p, &picker->red, &picker->white) / picker->dp); double _l = gfx_line_distance(&p, &picker->black, &picker->white); double _h = gfx_line_distance(&p, &picker->black, &picker->red); double s = _l + _h > 0.0 ? (_l / (_l+_h)) : 1.0; c = hsv_to_rgb(h,s,v); } else { double angle = atan2(y-_y,_x-x) + M_PI; if (r < inner_radius + 1) { alpha *= r - inner_radius; } c = hsv_to_rgb(angle, 1.0, 1.0); } return premultiply(rgba(_RED(c),_GRE(c),_BLU(c),(int)(255 * alpha))); } static void fill_picker(struct Picker *picker) { /* Triangle stuff */ picker->red.x = 0.8 * picker->radius * cos(-picker->hue) + picker->x + picker->radius; picker->red.y = 0.8 * picker->radius * sin(-picker->hue) + picker->y + picker->radius; picker->white.x = 0.8 * picker->radius * cos(-picker->hue + 2.09439510239) + picker->x + picker->radius; picker->white.y = 0.8 * picker->radius * sin(-picker->hue + 2.09439510239) + picker->y + picker->radius; picker->black.x = 0.8 * picker->radius * cos(-picker->hue + 4.18879020479) + picker->x + picker->radius; picker->black.y = 0.8 * picker->radius * sin(-picker->hue + 4.18879020479) + picker->y + picker->radius; /* Midpoint */ struct gfx_point midpoint = {(picker->white.x + picker->black.x) / 2.0, (picker->white.y + picker->black.y) / 2.0}; picker->dp = gfx_point_distance(&picker->red, &midpoint); } static double _hue = 0; static double _sat = 1.0; static double _val = 1.0; static uint32_t my_color = 0xFFFF0000; static void draw_ring(gfx_context_t * ctx, double x, double y, double radius, double thickness, uint32_t c) { struct gfx_point p = {x,y}; for (int _y = y - radius - thickness; _y <= y + radius + thickness; ++_y) { if (_y < 0) continue; if (_y >= ctx->height) break; for (int _x = x - radius - thickness; _x <= x + radius + thickness; ++_x) { if (_x < 0) continue; if (_x >= ctx->width) break; struct gfx_point pt = {_x,_y}; double dist = gfx_point_distance(&p,&pt); if (dist > radius - thickness && dist < radius + thickness) { double alpha = fmin(1.0,thickness - fabs(radius - dist)); GFX(ctx,_x,_y) = alpha_blend_rgba(GFX(ctx,_x,_y), premultiply(rgba(_RED(c),_GRE(c),_BLU(c),_ALP(c)*alpha))); } } } } static void redraw_everything(void) { draw_fill(ctx, rgb(200,200,200)); struct Picker picker = {off_x, off_y, win_width / 2, {0,0}, {0,0}, {0,0}, 0, _hue}; fill_picker(&picker); draw_rounded_rectangle_pattern(ctx,picker.x,picker.y,picker.radius*2,picker.radius*2,picker.radius,gfx_fill_magic, &picker); /* Now figure out where the s + v goes */ double x = picker.white.x * (1.0 - _sat) + picker.red.x * (_sat); double y = picker.white.y * (1.0 - _sat) + picker.red.y * (_sat); x = x * (_val) + picker.black.x * (1.0 - _val); y = y * (_val) + picker.black.y * (1.0 - _val); draw_ring(ctx, x, y, 5, 1.5, _val < 0.5 ? rgb(255,255,255) : rgb(0,0,0)); draw_rounded_rectangle(ctx,off_x + 5, off_y + picker.radius * 2 + 5, 15, 15, 5, my_color); char colorName[11]; sprintf(colorName,"#%02x%02x%02x", _RED(my_color), _GRE(my_color), _BLU(my_color)); tt_set_size(tt_font_thin, 13); tt_draw_string(ctx, tt_font_thin, off_x + 25, off_y + picker.radius * 2 + 18, colorName, rgb(0,0,0)); redraw_borders(); flip(ctx); yutani_flip(yctx, wina); } static double clamp_to_line(struct gfx_point *p, const struct gfx_point *v, const struct gfx_point *w, struct gfx_point *v_t) { float lengthlength = gfx_point_distance_squared(v,w); struct gfx_point p_v = gfx_point_sub(p,v); struct gfx_point w_v = gfx_point_sub(w,v); float tmp = gfx_point_dot(&p_v,&w_v) / lengthlength; tmp = fmin(1.0,tmp); float t = fmax(0.0, tmp); w_v.x *= t; w_v.y *= t; *v_t= gfx_point_add(v, &w_v); return gfx_point_distance(p, v_t); } static int inside_circle = -1; static void handle_mouse(struct yutani_msg_window_mouse_event * me) { if (me->command != YUTANI_MOUSE_EVENT_DOWN && me->command != YUTANI_MOUSE_EVENT_DRAG) return; /* Can we figure out a hue? */ int32_t x = me->new_x; int32_t y = me->new_y; struct Picker _picker = {off_x, off_y, win_width / 2, {0,0}, {0,0}, {0,0}, 0, _hue}; struct Picker * picker = &_picker; fill_picker(&_picker); /* Picker center */ int _x = picker->x + picker->radius; int _y = picker->y + picker->radius; /* hole in the middle */ double r = dist(x,y,_x,_y); int inner_radius = picker->radius * 0.8; struct gfx_point p = {(float)x,(float)y}; if (me->command == YUTANI_MOUSE_EVENT_DOWN) { if (r > picker->radius) { inside_circle = -1; return; } inside_circle = r < inner_radius; if (inside_circle) { double proximity; if (!in_triangle(&p,&picker->red,&picker->white,&picker->black,&proximity)) { inside_circle = -1; return; } } } if (inside_circle == 1) { double proximity; if (!in_triangle(&p,&picker->red,&picker->white,&picker->black,&proximity)) { struct gfx_point a; struct gfx_point b; struct gfx_point c; double a_d = clamp_to_line(&p,&picker->red,&picker->white,&a); double b_d = clamp_to_line(&p,&picker->black,&picker->white,&b); double c_d = clamp_to_line(&p,&picker->black,&picker->red,&c); if (a_d <= b_d && a_d <= c_d) { p = a; } else if (b_d <= a_d && b_d <= c_d) { p = b; } else if (c_d <= a_d && c_d <= b_d) { p = c; } } double v = 1.0 - (gfx_line_distance(&p, &picker->red, &picker->white) / picker->dp); double _l = gfx_line_distance(&p, &picker->black, &picker->white); double _h = gfx_line_distance(&p, &picker->black, &picker->red); double s = _l + _h > 0.0 ? (_l / (_l+_h)) : 1.0; _sat = s; _val = v; } else if (inside_circle == 0) { _hue = atan2(y-_y,_x-x) + M_PI; } else { return; } my_color = hsv_to_rgb(_hue,_sat,_val); needs_redraw = 1; } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, wina, w, h); reinit_graphics_yutani(ctx, wina); struct decor_bounds bounds; decor_get_bounds(wina, &bounds); win_width = w - bounds.width; win_height = h - bounds.height; off_x = bounds.left_width; off_y = bounds.top_height; redraw_everything(); yutani_window_resize_done(yctx, wina); } static uint32_t parseColor(const char * c) { if (*c != '#' || strlen(c) != 7) return rgba(0,0,0,255); char r[3] = {c[1],c[2],'\0'}; char g[3] = {c[3],c[4],'\0'}; char b[3] = {c[5],c[6],'\0'}; return rgba(strtoul(r,NULL,16),strtoul(g,NULL,16),strtoul(b,NULL,16),255); } int main (int argc, char ** argv) { yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]); return 1; } if (argc > 1) { my_color = parseColor(argv[1]); rgb_to_hsv(my_color, &_hue, &_sat, &_val); } win_width = 160; win_height = 200; tt_font_thin = tt_font_from_shm("sans-serif"); init_decorations(); struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); /* Do something with a window */ wina = yutani_window_create(yctx, win_width + bounds.width, win_height + bounds.height); yutani_window_move(yctx, wina, 300, 300); decor_get_bounds(wina, &bounds); off_x = bounds.left_width; off_y = bounds.top_height; win_width = wina->width - bounds.width; win_height = wina->height - bounds.height; ctx = init_graphics_yutani_double_buffer(wina); redraw_everything(); yutani_window_advertise_icon(yctx, wina, "Color Picker", "art"); while (!should_exit) { yutani_msg_t * m = yutani_poll(yctx); while (m) { menu_process_event(yctx, 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; } } 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*)(uintptr_t)wf->wid); if (win && win == wina) { win->focused = wf->focused; needs_redraw = 1; } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: should_exit = 1; break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; if (wr->wid == wina->wid) { resize_finish(wr->width, wr->height); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; switch (decor_handle_event(yctx, m)) { case DECOR_CLOSE: should_exit = 1; break; case DECOR_RIGHT: decor_show_default_menu(wina, wina->x + me->new_x, wina->y + me->new_y); break; } if (me->wid == wina->wid) { handle_mouse(me); } } break; default: break; } free(m); m = yutani_poll_async(yctx); } if (needs_redraw) { redraw_everything(); needs_redraw = 0; } } wait(NULL); yutani_close(yctx, wina); return 0; } ================================================ FILE: apps/compositor.c ================================================ /** * @brief Yutani - The ToaruOS Window Compositor. * * Yutani is a canvas-based window compositor and manager. * It employs shared memory to provide clients access to * canvases in which they may render, while using a packet-based * socket interface to communicate actions between the server * and client such as keyboard activity, mouse movement, responses * to client events, etc., as well as to communicate requests from * the client to the server, such as creation of new windows, * movement, resizing, and display updates. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define _DEBUG_YUTANI #ifdef _DEBUG_YUTANI #include #define TRACE_APP_NAME "yutani" #else #define TRACE(msg,...) #endif /* Early definitions */ static void mark_window(yutani_globals_t * yg, yutani_server_window_t * window); static void window_actually_close(yutani_globals_t * yg, yutani_server_window_t * w); static void notify_subscribers(yutani_globals_t * yg); static void mouse_stop_drag(yutani_globals_t * yg); static void window_move(yutani_globals_t * yg, yutani_server_window_t * window, int x, int y); static yutani_server_window_t * top_at(yutani_globals_t * yg, uint16_t x, uint16_t y); static void window_unminimize(yutani_globals_t * yg, yutani_server_window_t * window); static void window_finish_minimize(yutani_globals_t * yg, yutani_server_window_t * w); #define ENABLE_BLUR_BEHIND #ifdef ENABLE_BLUR_BEHIND #define BLUR_CLIP_MAX 20 #define BLUR_KERNEL 10 static char * blur_texture = NULL; static gfx_context_t * blur_ctx = NULL; static gfx_context_t * clip_ctx = NULL; #endif /** * Print usage information. */ static int usage(char * argv[]) { fprintf(stderr, "Yutani - Window Compositor\n" "\n" "usage: %s [-n [-g WxH]] [-h]\n" "\n" " -n --nested \033[3mRun in a window.\033[0m\n" " -h --help \033[3mShow this help message.\033[0m\n" " -g --geometry \033[3mSet the size of the server framebuffer.\033[0m\n" "\n" " Yutani is the standard system compositor.\n" "\n", argv[0]); return 1; } /** * Parse arguments */ static int parse_args(int argc, char * argv[], int * out) { static struct option long_opts[] = { {"nested", no_argument, 0, 'n'}, {"geometry", required_argument, 0, 'g'}, {"help", no_argument, 0, 'h'}, {0,0,0,0} }; int index, c; while ((c = getopt_long(argc, argv, "hg:n", long_opts, &index)) != -1) { if (!c) { if (long_opts[index].flag == 0) { c = long_opts[index].val; } } switch (c) { case 'h': return usage(argv); case 'n': yutani_options.nested = 1; break; case 'g': { char * c = strstr(optarg, "x"); if (c) { *c = '\0'; c++; yutani_options.nest_width = atoi(optarg); yutani_options.nest_height = atoi(c); } } break; default: fprintf(stderr, "Unrecognized option: %c\n", c); break; } } *out = optind; return 0; } static int32_t min(int32_t a, int32_t b) { return (a < b) ? a : b; } static int32_t max(int32_t a, int32_t b) { return (a > b) ? a : b; } static int next_buf_id(void) { static int _next = 1; return _next++; } static int next_wid(void) { static int _next = 1; return _next++; } uint64_t yutani_current_time(yutani_globals_t * yg) { struct timeval t; gettimeofday(&t, NULL); time_t sec_diff = t.tv_sec - yg->start_time; suseconds_t usec_diff = t.tv_usec - yg->start_subtime; if (t.tv_usec < yg->start_subtime) { sec_diff -= 1; usec_diff = (1000000 + t.tv_usec) - yg->start_subtime; } return (uint64_t)(sec_diff * 1000 + usec_diff / 1000); } uint64_t yutani_time_since(yutani_globals_t * yg, uint64_t start_time) { uint64_t now = yutani_current_time(yg); uint64_t diff = now - start_time; /* Milliseconds */ return diff; } /** * Translate and transform coordinate from screen-relative to window-relative. */ void yutani_device_to_window(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y) { if (!window) { *out_x = 0; *out_y = 0; return; } *out_x = x - window->x; *out_y = y - window->y; if (!window->rotation) return; double t_x = (double)*out_x - ((double)window->width / 2); double t_y = (double)*out_y - ((double)window->height / 2); double s = sin(-M_PI * (window->rotation/ 180.0)); double c = cos(-M_PI * (window->rotation/ 180.0)); double n_x = t_x * c - t_y * s; double n_y = t_x * s + t_y * c; *out_x = (int32_t)(n_x + ((double)window->width / 2)); *out_y = (int32_t)(n_y + ((double)window->height / 2)); } /** * Translate and transform coordinate from window-relative to screen-relative. */ void yutani_window_to_device(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y) { if (!window->rotation) { *out_x = window->x + x; *out_y = window->y + y; return; } double t_x = (double)x - ((double)window->width / 2); double t_y = (double)y - ((double)window->height / 2); double s = sin((double)window->rotation * M_PI / 180.0); double c = cos((double)window->rotation * M_PI / 180.0); double n_x = t_x * c - t_y * s; double n_y = t_x * s + t_y * c; *out_x = (int32_t)(n_x + ((double)window->width / 2) + (double)window->x); *out_y = (int32_t)(n_y + ((double)window->height / 2) + (double)window->y); } static list_t * window_zorder_owner(yutani_globals_t * yg, unsigned short index) { switch (index) { case YUTANI_ZORDER_BOTTOM: case YUTANI_ZORDER_TOP: return NULL; case YUTANI_ZORDER_MENU: return yg->menu_zs; case YUTANI_ZORDER_OVERLAY: return yg->overlay_zs; default: return yg->mid_zs; } } /** * Remove a window from the z stack. */ static void unorder_window(yutani_globals_t * yg, yutani_server_window_t * w) { unsigned short index = w->z; w->z = -1; if (index == YUTANI_ZORDER_BOTTOM && yg->bottom_z == w) { yg->bottom_z = NULL; return; } if (index == YUTANI_ZORDER_TOP && yg->top_z == w) { yg->top_z = NULL; return; } list_t * zorder_owner = window_zorder_owner(yg, index); if (!zorder_owner) return; node_t * n = list_find(zorder_owner, w); if (!n) return; list_delete(zorder_owner, n); free(n); } /** * Move a window to a new stack order. */ static void reorder_window(yutani_globals_t * yg, yutani_server_window_t * window, uint16_t new_zed) { if (!window) { return; } unorder_window(yg, window); window->z = new_zed; list_t * zorder_owner = window_zorder_owner(yg, new_zed); if (zorder_owner) { list_insert(zorder_owner, window); return; } if (new_zed == YUTANI_ZORDER_TOP) { if (yg->top_z) { unorder_window(yg, yg->top_z); } yg->top_z = window; return; } if (new_zed == YUTANI_ZORDER_BOTTOM) { if (yg->bottom_z) { unorder_window(yg, yg->bottom_z); } yg->bottom_z = window; return; } } /** * Move a window to the top of if its z stack. */ static void make_top(yutani_globals_t * yg, yutani_server_window_t * w) { unsigned short index = w->z; list_t * zorder_owner = window_zorder_owner(yg, index); if (!zorder_owner) return; node_t * n = list_find(zorder_owner, w); if (!n) return; /* wat */ list_delete(zorder_owner, n); list_append(zorder_owner, n); } /** * Set a window as the focused window. * * Currently, we only support one focused window. * In the future, we should support multiple windows as "focused" to account * for multiple "seats" on a single display. */ static void set_focused_window(yutani_globals_t * yg, yutani_server_window_t * w) { if (w == yg->focused_window) { return; /* Already focused */ } if (w && w->minimized) { window_unminimize(yg,w); } if (yg->focused_window) { /* Send focus change to old focused window */ yutani_msg_buildx_window_focus_change_alloc(response); yutani_msg_buildx_window_focus_change(response, yg->focused_window->wid, 0); pex_send(yg->server, yg->focused_window->owner, response->size, (char *)response); } if (!w) w = yg->bottom_z; yg->focused_window = w; if (w) { /* Send focus change to new focused window */ yutani_msg_buildx_window_focus_change_alloc(response); yutani_msg_buildx_window_focus_change(response, w->wid, 1); pex_send(yg->server, w->owner, response->size, (char *)response); make_top(yg, w); mark_window(yg, w); } /* Notify all subscribers of window changes */ notify_subscribers(yg); } /** * Get the focused window. * * In case there is no focused window, we return the bottom window. */ static yutani_server_window_t * get_focused(yutani_globals_t * yg) { if (yg->focused_window) return yg->focused_window; if (yg->bottom_z) set_focused_window(yg, yg->bottom_z); return yg->bottom_z; } static int yutani_pick_animation(uint32_t flags, int direction) { if (flags & YUTANI_WINDOW_FLAG_DIALOG_ANIMATION) { return (direction == 0) ? YUTANI_EFFECT_SQUEEZE_IN : YUTANI_EFFECT_SQUEEZE_OUT; } if (flags & YUTANI_WINDOW_FLAG_NO_ANIMATION) { return (direction == 0) ? YUTANI_EFFECT_NONE : YUTANI_EFFECT_DISAPPEAR; } return (direction == 0) ? YUTANI_EFFECT_FADE_IN : YUTANI_EFFECT_FADE_OUT; } /** * Create a server window object. * * Initializes a window of the particular size for a given client. */ static yutani_server_window_t * server_window_create(yutani_globals_t * yg, int width, int height, uintptr_t owner, uint32_t flags) { yutani_server_window_t * win = calloc(sizeof(yutani_server_window_t),1); win->wid = next_wid(); win->owner = owner; list_insert(yg->windows, win); hashmap_set(yg->wids_to_windows, (void*)(uintptr_t)win->wid, win); list_t * client_list = hashmap_get(yg->clients_to_windows, (void *)owner); list_insert(client_list, win); win->x = 0; win->y = 0; win->z = 1; win->width = width; win->height = height; win->bufid = next_buf_id(); win->rotation = 0; win->newbufid = 0; win->client_flags = 0; win->client_icon = 0; win->client_length = 0; win->client_strings = NULL; win->anim_mode = 0; win->anim_start = 0; win->alpha_threshold = 0; win->show_mouse = 1; win->tiled = 0; win->untiled_width = 0; win->untiled_height = 0; win->default_mouse = 1; win->server_flags = flags; win->opacity = 255; win->hidden = 1; win->minimized = 0; char key[1024]; YUTANI_SHMKEY(yg->server_ident, key, 1024, win); size_t size = (width * height * 4); win->buffer = shm_obtain(key, &size); memset(win->buffer, 0, size); list_insert(yg->mid_zs, win); return win; } /** * Update the shape threshold for a window. * * A shaping threshold is a byte representing the minimum * required alpha for a window to be considered "solid". * Eg., a value of 0 says all windows are solid, while a value of * 1 requires a window to have at least some opacity to it, * and a value of 255 requires fully opaque pixels. * * Not actually stored as a byte, so a value over 255 can be used. * This results in a window that passes through all clicks. */ static void server_window_update_shape(yutani_globals_t * yg, yutani_server_window_t * window, int set) { window->alpha_threshold = set; } /** * Start resizing a window. * * Resizing a multi-stage process. * The client and server agree on a size and the server prepares a buffer. * The client then needs to accept the resize, fill the buffer, and then * inform the server that it is ready, at which point we'll swap the * buffer we are rendering from. */ static uint32_t server_window_resize(yutani_globals_t * yg, yutani_server_window_t * win, int width, int height) { /* A client has accepted our offer, let's make a buffer for them */ if (win->newbufid) { /* Already in the middle of an accept/done, bail */ return win->newbufid; } win->newbufid = next_buf_id(); { char key[1024]; YUTANI_SHMKEY_EXP(yg->server_ident, key, 1024, win->newbufid); size_t size = (width * height * 4); win->newbuffer = shm_obtain(key, &size); } return win->newbufid; } /** * Finish the resize process. * * We delete the unlink the old buffer and then swap the pointers * for the new buffer. */ static void server_window_resize_finish(yutani_globals_t * yg, yutani_server_window_t * win, int width, int height) { if (!win->newbufid) { return; } int oldbufid = win->bufid; mark_window(yg, win); if (yg->resizing_window == win) { yg->resize_release_time = 0; if (yg->mouse_state == YUTANI_MOUSE_STATE_NORMAL) { int32_t x, y; if (yg->resizing_window->rotation) { /* 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. */ /* The center always moves by one-half the resize dimensions */ int32_t center_x, center_y; yutani_server_window_t fake_window = { .width = yg->resizing_init_w, .height = yg->resizing_init_h, .x = yg->resizing_window->x, .y = yg->resizing_window->y, .rotation = yg->resizing_window->rotation, }; yutani_window_to_device(&fake_window, yg->resizing_offset_x + yg->resizing_w / 2, yg->resizing_offset_y + yg->resizing_h / 2, ¢er_x, ¢er_y); x = center_x - yg->resizing_w / 2; y = center_y - yg->resizing_h / 2; } else { yutani_window_to_device(yg->resizing_window, yg->resizing_offset_x, yg->resizing_offset_y, &x, &y); } TRACE("resize complete, now %d x %d", yg->resizing_w, yg->resizing_h); window_move(yg, yg->resizing_window, x,y); yg->resizing_window = NULL; yg->mouse_window = NULL; } } win->width = width; win->height = height; win->bufid = win->newbufid; win->buffer = win->newbuffer; win->newbuffer = NULL; win->newbufid = 0; { char key[1024]; YUTANI_SHMKEY_EXP(yg->server_ident, key, 1024, oldbufid); shm_release(key); } mark_window(yg, win); } /** * Mark a screen region as damaged. */ static void mark_screen(yutani_globals_t * yg, int32_t x, int32_t y, int32_t width, int32_t height) { yutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t)); rect->x = x; rect->y = y; rect->width = width; rect->height = height; list_insert(yg->update_list, rect); } /** * Draw the cursor sprite. */ static void draw_cursor(yutani_globals_t * yg, int x, int y, int cursor) { sprite_t * sprite = &yg->mouse_sprite; static sprite_t * previous = NULL; if (yg->resizing_window) { switch (yg->resizing_direction) { case SCALE_UP: case SCALE_DOWN: sprite = &yg->mouse_sprite_resize_v; break; case SCALE_LEFT: case SCALE_RIGHT: sprite = &yg->mouse_sprite_resize_h; break; case SCALE_DOWN_RIGHT: case SCALE_UP_LEFT: sprite = &yg->mouse_sprite_resize_da; break; case SCALE_DOWN_LEFT: case SCALE_UP_RIGHT: sprite = &yg->mouse_sprite_resize_db; break; default: break; } } else if (yg->mouse_state == YUTANI_MOUSE_STATE_MOVING) { sprite = &yg->mouse_sprite_drag; } else { switch (cursor) { case YUTANI_CURSOR_TYPE_DRAG: sprite = &yg->mouse_sprite_drag; break; case YUTANI_CURSOR_TYPE_RESIZE_VERTICAL: sprite = &yg->mouse_sprite_resize_v; break; case YUTANI_CURSOR_TYPE_RESIZE_HORIZONTAL: sprite = &yg->mouse_sprite_resize_h; break; case YUTANI_CURSOR_TYPE_RESIZE_UP_DOWN: sprite = &yg->mouse_sprite_resize_da; break; case YUTANI_CURSOR_TYPE_RESIZE_DOWN_UP: sprite = &yg->mouse_sprite_resize_db; break; case YUTANI_CURSOR_TYPE_POINT: sprite = &yg->mouse_sprite_point; break; case YUTANI_CURSOR_TYPE_IBEAM: sprite = &yg->mouse_sprite_ibeam; break; } } if (sprite != previous) { mark_screen(yg, x / MOUSE_SCALE - MOUSE_OFFSET_X, y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT); previous = sprite; } if (yg->vbox_pointer > 0) { if (write(yg->vbox_pointer, sprite->bitmap, 48*48*4) > 0) { /* if that was successful, we don't need to draw the cursor */ return; } } yutani_server_window_t * cursor_window = yg->resizing_window ? yg->resizing_window : top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); int16_t rotation = cursor_window ? cursor_window->rotation : 0; if (rotation) { draw_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); } else { draw_sprite(yg->backend_ctx, sprite, x / MOUSE_SCALE - MOUSE_OFFSET_X, y / MOUSE_SCALE - MOUSE_OFFSET_Y); } } /** * Determine if a window has a solid pixel at a given screen-space coordinate. * * This is where we evaluate alpha thresholds. We only do this underneath * the cursor, and only when we move the cursor. It's reasonably fast * in those circumstances, but shouldn't be used for large regions. We * do have one debug method that indicates the top window in a box * around the cursor, but it is relatively slow. */ static yutani_server_window_t * check_top_at(yutani_globals_t * yg, yutani_server_window_t * w, uint16_t x, uint16_t y){ if (!w || w->hidden || w->minimized) return NULL; int32_t _x = -1, _y = -1; yutani_device_to_window(w, x, y, &_x, &_y); if (_x < 0 || _x >= w->width || _y < 0 || _y >= w->height) return NULL; uint32_t c = ((uint32_t *)w->buffer)[(w->width * _y + _x)]; uint8_t a = _ALP(c); if (a >= w->alpha_threshold) { return w; } return NULL; } /** * Find the window that is at the top at a particular screen-space coordinate. * * This walks through each window from top to bottom (foreachr - reverse foreach) * until it finds one with a pixel at this coordinate. Again, we only call this * at the cursor coordinates, and it is not particularly fast, so don't use it * anywhere that needs to hit a lot of coordinates. */ static yutani_server_window_t * top_at(yutani_globals_t * yg, uint16_t x, uint16_t y) { if (check_top_at(yg, yg->top_z, x, y)) return yg->top_z; foreachr(node, yg->menu_zs) { yutani_server_window_t * w = node->value; if (check_top_at(yg, w, x, y)) return w; } foreachr(node, yg->overlay_zs) { yutani_server_window_t * w = node->value; if (check_top_at(yg, w, x, y)) return w; } foreachr(node, yg->mid_zs) { yutani_server_window_t * w = node->value; if (check_top_at(yg, w, x, y)) return w; } if (check_top_at(yg, yg->bottom_z, x, y)) return yg->bottom_z; return NULL; } /** * Get the window at a coordinate and focus it. * * See the docs for the proceeding functions, but added to this * focusing windows is also not particular fast as the reshuffle * is complicated. */ static void set_focused_at(yutani_globals_t * yg, int x, int y) { yutani_server_window_t * n_focused = top_at(yg, x, y); set_focused_window(yg, n_focused); } /* * Convenience functions for checking if a window is in the top/bottom stack. * * In the future, these single-item "stacks" will be replaced with dedicated stacks * so we can have multiple background windows and multiple panels / always-top windows. */ int yutani_window_is_top(yutani_globals_t * yg, yutani_server_window_t * window) { /* For now, just use simple z-order */ return window->z == YUTANI_ZORDER_TOP; } int yutani_window_is_bottom(yutani_globals_t * yg, yutani_server_window_t * window) { /* For now, just use simple z-order */ return window->z == YUTANI_ZORDER_BOTTOM; } /** * Get a color for a wid for debugging. * * Makes a pretty rainbow pattern. */ uint32_t yutani_color_for_wid(yutani_wid_t wid) { static uint32_t colors[] = { 0xFF19aeff, 0xFFff4141, 0xFFffff3e, 0xFFff6600, 0xFF9ade00, 0xFFd76cff, 0xFF364e59, 0xFF0084c8, 0xFFdc0000, 0xFFff9900, 0xFF009100, 0xFFba00ff, 0xFFb88100, 0xFF9eabb0 }; int i = wid % (sizeof(colors) / sizeof(uint32_t)); return colors[i]; } /** * Determine if a matrix has an identity transformation for its linear component. */ static inline int matrix_is_translation(gfx_matrix_t m) { return (m[0][0] == 1.0 && m[0][1] == 0.0 && m[1][0] == 0.0 && m[1][1] == 1.0); } static void apply_rotation(yutani_globals_t * yg, yutani_server_window_t * window, gfx_matrix_t m, int16_t r) { if (window == yg->resizing_window) { if (r) { gfx_matrix_translate(m, yg->resizing_init_w / 2, yg->resizing_init_h / 2); gfx_matrix_rotate(m, (double)r * M_PI / 180.0); gfx_matrix_translate(m, -yg->resizing_init_w / 2, -yg->resizing_init_h / 2); } double x_scale = (double)yg->resizing_w / (double)yg->resizing_window->width; double y_scale = (double)yg->resizing_h / (double)yg->resizing_window->height; if (x_scale < 0.00001) { x_scale = 0.00001; } if (y_scale < 0.00001) { y_scale = 0.00001; } gfx_matrix_translate(m, (int)yg->resizing_offset_x, (int)yg->resizing_offset_y); gfx_matrix_scale(m, x_scale, y_scale); } else if (r) { gfx_matrix_translate(m, window->width / 2, window->height / 2); gfx_matrix_rotate(m, (double)r * M_PI / 180.0); gfx_matrix_translate(m, -window->width / 2, -window->height / 2); } } /** * Blit a window to the framebuffer. * * Applies transformations (rotation, animations) and then renders * the window through alpha blitting. */ static int yutani_blit_window(yutani_globals_t * yg, yutani_server_window_t * window, int x, int y) { if (window->hidden || window->minimized) { return 0; } sprite_t _win_sprite; _win_sprite.width = window->width; _win_sprite.height = window->height; _win_sprite.bitmap = (uint32_t *)window->buffer; _win_sprite.masks = NULL; _win_sprite.blank = 0; _win_sprite.alpha = ALPHA_EMBEDDED; double opacity = (double)(window->opacity) / 255.0; if (window->rotation || window == yg->resizing_window || window->anim_mode || (window->server_flags & YUTANI_WINDOW_FLAG_BLUR_BEHIND)) { double m[2][3]; gfx_matrix_identity(m); gfx_matrix_translate(m,x,y); if (window->anim_mode) { int frame = yutani_time_since(yg, window->anim_start); if (frame >= yutani_animation_lengths[window->anim_mode]) { /* XXX handle animation-end things like cleanup of closing windows */ if (yutani_is_closing_animation[window->anim_mode]) { list_insert(yg->windows_to_remove, window); return 0; } if (yutani_is_minimizing_animation[window->anim_mode]) { list_insert(yg->windows_to_minimize, window); return 0; } window->anim_mode = 0; window->anim_start = 0; apply_rotation(yg, window, m, window->rotation); } else { switch (window->anim_mode) { case YUTANI_EFFECT_SQUEEZE_OUT: case YUTANI_EFFECT_FADE_OUT: { frame = yutani_animation_lengths[window->anim_mode] - frame; } /* fallthrough */ case YUTANI_EFFECT_SQUEEZE_IN: case YUTANI_EFFECT_FADE_IN: { double time_diff = ((double)frame / (float)yutani_animation_lengths[window->anim_mode]); apply_rotation(yg, window, m, window->rotation); if (window->server_flags & YUTANI_WINDOW_FLAG_DIALOG_ANIMATION) { double x = time_diff; int t_y = (window->height * (1.0 -x)) / 2; gfx_matrix_translate(m, 0, t_y); gfx_matrix_scale(m, 1.0, x); } else { double x = 0.75 + time_diff * 0.25; opacity *= time_diff; if (!(window->server_flags & YUTANI_WINDOW_FLAG_ALT_ANIMATION)) { int t_x = (window->width * (1.0 - x)) / 2; int t_y = (window->height * (1.0 - x)) / 2; gfx_matrix_translate(m, t_x, t_y); gfx_matrix_scale(m, x, x); } } } break; case YUTANI_EFFECT_MINIMIZE: { frame = yutani_animation_lengths[window->anim_mode] - frame; } /* fallthrough */ case YUTANI_EFFECT_UNMINIMIZE: { double time_diff = ((double)frame / (float)yutani_animation_lengths[window->anim_mode]); opacity *= time_diff; double t_x = -(window->x - window->icon_x) * (1.0 - time_diff); double t_y = -(window->y - window->icon_y) * (1.0 - time_diff); double s_x = 1.0 + (((float)window->icon_w / (float)(window->width ?: 1.0)) - 1.0) * (1.0 - time_diff); double s_y = 1.0 + (((float)window->icon_h / (float)(window->height ?: 1.0)) - 1.0) * (1.0 - time_diff); gfx_matrix_translate(m, t_x, t_y); gfx_matrix_scale(m, s_x, s_y); apply_rotation(yg, window, m, window->rotation * time_diff); } break; default: apply_rotation(yg, window, m, window->rotation); break; } } } else { apply_rotation(yg, window, m, window->rotation); } #ifdef ENABLE_BLUR_BEHIND if (window->server_flags & YUTANI_WINDOW_FLAG_BLUR_BEHIND) { extern 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); draw_sprite_transform_blur(yg->backend_ctx, blur_ctx, &_win_sprite, m, opacity, window->alpha_threshold); } else #endif if (matrix_is_translation(m)) { draw_sprite_alpha(yg->backend_ctx, &_win_sprite, m[0][2], m[1][2], opacity); } else { draw_sprite_transform(yg->backend_ctx, &_win_sprite, m, opacity); } } else if (window->opacity != 255) { draw_sprite_alpha(yg->backend_ctx, &_win_sprite, window->x, window->y, opacity); } else { draw_sprite(yg->backend_ctx, &_win_sprite, window->x, window->y); } #if YUTANI_DEBUG_WINDOW_BOUNDS if (yg->debug_bounds) { int32_t t_x, t_y; int32_t s_x, s_y; int32_t r_x, r_y; int32_t q_x, q_y; yutani_window_to_device(window, 0, 0, &t_x, &t_y); yutani_window_to_device(window, window->width, window->height, &s_x, &s_y); yutani_window_to_device(window, 0, window->height, &r_x, &r_y); yutani_window_to_device(window, window->width, 0, &q_x, &q_y); uint32_t x = alpha_blend(rgba(0,0,0,0), yutani_color_for_wid(window->wid), rgb(178,0,0)); struct TT_Contour * contour = tt_contour_start(t_x,t_y); contour = tt_contour_line_to(contour, r_x,r_y); contour = tt_contour_line_to(contour, s_x,s_y); contour = tt_contour_line_to(contour, q_x,q_y); struct TT_Shape * shape = tt_contour_finish(contour); free(contour); tt_path_paint(yg->backend_ctx, shape, x); free(shape); } #endif return 0; } /** * VirtualBox Seamless desktop driver. * * Sends rectangles describing all the non-background windows * to the VirtualBox Guest Additions driver for use with the * seamless desktop mode. */ static void yutani_post_vbox_rects(yutani_globals_t * yg) { if (yg->vbox_rects <= 0) return; char tmp[4096]; uint32_t * count = (uint32_t *)tmp; *count = 0; struct Rect { int32_t x; int32_t y; int32_t xe; int32_t ye; } __attribute__((packed)); struct Rect * rects = (struct Rect *)(tmp+sizeof(int32_t)); #define DO_WINDOW(win) if (win && !win->hidden && !win->minimized && *count < 255 ) { \ rects->x = (win)->x; \ rects->y = (win)->y; \ rects->xe = (win)->x + (win)->width; \ rects->ye = (win)->y + (win)->height; \ rects++; \ (*count)++; \ } /* Add top window if it exists */ DO_WINDOW(yg->top_z); /* Add regular windows */ foreach (node, yg->mid_zs) { yutani_server_window_t * w = node->value; DO_WINDOW(w); } /* Add overlay windows */ foreach (node, yg->overlay_zs) { yutani_server_window_t * w = node->value; DO_WINDOW(w); } /* Add menu windows */ foreach (node, yg->menu_zs) { yutani_server_window_t * w = node->value; DO_WINDOW(w); } /* * If there were no windows, show the whole desktop * so we can see, eg., the login screen. */ if (*count == 0) { *count = 1; rects->x = 0; rects->y = 0; rects->xe = yg->width; rects->ye = yg->height; } /* Post rectangle data to driver */ write(yg->vbox_rects, tmp, sizeof(tmp)); } /** * Blit all windows into the given context. * * This is called for rendering and for screenshots. */ static void yutani_blit_windows(yutani_globals_t * yg) { if (!yg->bottom_z || yg->bottom_z->anim_mode) { draw_fill(yg->backend_ctx, rgb(0,0,0)); } if (yg->bottom_z) yutani_blit_window(yg, yg->bottom_z, yg->bottom_z->x, yg->bottom_z->y); foreach (node, yg->mid_zs) { yutani_server_window_t * w = node->value; if (w) yutani_blit_window(yg, w, w->x, w->y); } foreach (node, yg->overlay_zs) { yutani_server_window_t * w = node->value; if (w) yutani_blit_window(yg, w, w->x, w->y); } foreach (node, yg->menu_zs) { yutani_server_window_t * w = node->value; if (w) yutani_blit_window(yg, w, w->x, w->y); } if (yg->top_z) yutani_blit_window(yg, yg->top_z, yg->top_z->x, yg->top_z->y); } /** * Take a screenshot */ static void yutani_screenshot(yutani_globals_t * yg) { int task = yg->screenshot_frame; yg->screenshot_frame = 0; /* raw screenshots */ char fname[1024]; struct tm * timeinfo; struct timeval now; gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); strftime(fname,1024,"/tmp/screenshot_%F_%H_%M_%S.tga",timeinfo); FILE * f = fopen(fname, "w"); if (!f) { TRACE("Error opening output file for screenshot."); return; } uint32_t * buffer = NULL; int width, height; int alpha; if (task == YUTANI_SCREENSHOT_FULL) { buffer = (void *)yg->backend_ctx->backbuffer; width = yg->width; height = yg->height; alpha = 0; } else if (task == YUTANI_SCREENSHOT_WINDOW) { yutani_server_window_t * window = yg->focused_window; buffer = (void *)window->buffer; width = window->width; height = window->height; alpha = 1; } if (buffer) { struct { uint8_t id_length; uint8_t color_map_type; uint8_t image_type; uint16_t color_map_first_entry; uint16_t color_map_length; uint8_t color_map_entry_size; uint16_t x_origin; uint16_t y_origin; uint16_t width; uint16_t height; uint8_t depth; uint8_t descriptor; } __attribute__((packed)) header = { 0, /* No image ID field */ 0, /* No color map */ 2, /* Uncompressed truecolor */ 0, 0, 0, /* No color map */ 0, 0, /* Don't care about origin */ width, height, alpha ? 32 : 24, alpha ? 8 : 0, }; fwrite(&header, 1, sizeof(header), f); for (int y = height-1; y>=0; y--) { for (int x = 0; x < width; ++x) { uint8_t buf[4] = { _BLU(buffer[y * width + x]), _GRE(buffer[y * width + x]), _RED(buffer[y * width + x]), _ALP(buffer[y * width + x]), }; fwrite(buf, 1, alpha ? 4 : 3, f); } } } fclose(f); FILE * toast = fopen("/dev/pex/toast", "w"); fprintf(toast, "{\"icon\": \"%s\", \"body\": \"Screenshot taken.\"}", fname); fclose(toast); /* Blorp */ system("play /usr/share/ttk/blorp.wav &"); } static gfx_context_t * init_graphics_with_store(gfx_context_t * base, char * store) { gfx_context_t * out = malloc(sizeof(gfx_context_t)); out->clips = NULL; out->width = base->width; out->height = base->height; out->stride = base->stride; out->depth = base->depth; out->size = base->size; out->buffer = store; out->backbuffer = out->buffer; return out; } static void resize_display(yutani_globals_t * yg) { TRACE("Resizing display."); if (!yutani_options.nested) { reinit_graphics_fullscreen(yg->backend_ctx); } else { reinit_graphics_yutani(yg->backend_ctx, yg->host_window); yutani_window_resize_done(yg->host_context, yg->host_window); } #ifdef ENABLE_BLUR_BEHIND free(blur_ctx); blur_texture = realloc(blur_texture, yg->backend_ctx->stride * yg->backend_ctx->height); blur_ctx = init_graphics_with_store(yg->backend_ctx, blur_texture); clip_ctx->width = yg->backend_ctx->width; clip_ctx->height = yg->backend_ctx->height; /* reinitialize extended clip context or we won't be drawing enough later... */ if (clip_ctx->clips && clip_ctx->clips_size) { free(clip_ctx->clips); clip_ctx->clips_size = 0; clip_ctx->clips = NULL; } #endif TRACE("graphics context resized..."); yg->width = yg->backend_ctx->width; yg->height = yg->backend_ctx->height; yg->backend_framebuffer = yg->backend_ctx->backbuffer; TRACE("Marking..."); yg->resize_on_next = 0; mark_screen(yg, 0, 0, yg->width, yg->height); TRACE("Sending welcome messages..."); yutani_msg_buildx_welcome_alloc(response); yutani_msg_buildx_welcome(response, yg->width, yg->height); pex_broadcast(yg->server, response->size, (char *)response); TRACE("Done."); } /** * Redraw all windows, as well as the mouse cursor. * * This is the main redraw function. */ static void redraw_windows(yutani_globals_t * yg) { int has_updates = 0; /* We keep our own temporary mouse coordinates as they may change while we're drawing. */ int tmp_mouse_x = yg->mouse_x; int tmp_mouse_y = yg->mouse_y; if (yg->resize_on_next) { resize_display(yg); } if (yg->resizing_window && yg->mouse_state == YUTANI_MOUSE_STATE_NORMAL && yg->resize_release_time && yutani_time_since(yg, yg->resize_release_time) >= 500) { yutani_server_window_t * resizing = yg->resizing_window; mark_window(yg, resizing); yg->resize_release_time = 0; yg->resizing_window = NULL; yg->mouse_window = NULL; mark_window(yg, resizing); } gfx_clear_clip(yg->backend_ctx); #ifdef ENABLE_BLUR_BEHIND gfx_clear_clip(clip_ctx); #endif /* If the mouse has moved, that counts as two damage regions */ if ((yg->last_mouse_x != tmp_mouse_x) || (yg->last_mouse_y != tmp_mouse_y)) { has_updates = 2; gfx_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); gfx_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); #ifdef ENABLE_BLUR_BEHIND gfx_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); gfx_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); #endif } yg->last_mouse_x = tmp_mouse_x; yg->last_mouse_y = tmp_mouse_y; if (yg->bottom_z && yg->bottom_z->anim_mode) mark_window(yg, yg->bottom_z); if (yg->top_z && yg->top_z->anim_mode) mark_window(yg, yg->top_z); foreach (node, yg->mid_zs) { yutani_server_window_t * w = node->value; if (w && w->anim_mode) { if (w->anim_mode == YUTANI_EFFECT_MINIMIZE || w->anim_mode == YUTANI_EFFECT_UNMINIMIZE) { yutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t)); rect->x = 0; rect->y = 0; rect->width = yg->width; rect->height = yg->height; list_insert(yg->update_list, rect); } else { mark_window(yg, w); } } } foreach (node, yg->overlay_zs) { yutani_server_window_t * w = node->value; if (w && w->anim_mode) mark_window(yg, w); } foreach (node, yg->menu_zs) { yutani_server_window_t * w = node->value; if (w && w->anim_mode) mark_window(yg, w); } /* Calculate damage regions from currently queued updates */ while (yg->update_list->length) { node_t * win = list_dequeue(yg->update_list); yutani_damage_rect_t * rect = (void *)win->value; /* We add a clip region for each window in the update queue */ has_updates = 1; gfx_add_clip(yg->backend_ctx, rect->x, rect->y, rect->width, rect->height); #ifdef ENABLE_BLUR_BEHIND gfx_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); #endif free(rect); free(win); } /* Render */ if (has_updates) { #ifdef ENABLE_BLUR_BEHIND /* Extend clips */ char * oclip = yg->backend_ctx->clips; yg->backend_ctx->clips = clip_ctx->clips; #endif /* * In theory, we should restrict this to windows within the clip region, * but calculating that may be more trouble than it's worth; * we also need to render windows in stacking order... */ yutani_blit_windows(yg); #ifdef ENABLE_BLUR_BEHIND /* Restore clip context */ yg->backend_ctx->clips = oclip; #endif /* Send VirtualBox rects */ yutani_post_vbox_rects(yg); #if YUTANI_DEBUG_WINDOW_SHAPES #define WINDOW_SHAPE_VIEWER_SIZE 20 /* * Debugging window shapes: draw a box around the mouse cursor * showing which window is at the top and will accept mouse events. */ if (yg->debug_shapes) { int _ly = max(0,tmp_mouse_y/MOUSE_SCALE - WINDOW_SHAPE_VIEWER_SIZE); int _hy = min(yg->height,tmp_mouse_y/MOUSE_SCALE + WINDOW_SHAPE_VIEWER_SIZE); int _lx = max(0,tmp_mouse_x/MOUSE_SCALE - 20); int _hx = min(yg->width,tmp_mouse_x/MOUSE_SCALE + WINDOW_SHAPE_VIEWER_SIZE); for (int y = _ly; y < _hy; ++y) { for (int x = _lx; x < _hx; ++x) { yutani_server_window_t * w = top_at(yg, x, y); if (w) { GFX(yg->backend_ctx, x, y) = yutani_color_for_wid(w->wid); } } } } #endif if (yutani_options.nested) { flip(yg->backend_ctx); /* * We should be able to flip only the places we need to flip, but * instead we're going to flip the whole thing. * * TODO: Do a better job of this. */ yutani_flip(yg->host_context, yg->host_window); yutani_server_window_t * tmp_window = top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); if (yg->mouse_state == YUTANI_MOUSE_STATE_MOVING) { yutani_window_show_mouse(yg->host_context, yg->host_window, YUTANI_CURSOR_TYPE_DRAG); } else if (!tmp_window || tmp_window->show_mouse) { yutani_window_show_mouse(yg->host_context, yg->host_window, tmp_window ? tmp_window->show_mouse : 1); } } else { /* * Draw the cursor. * We may also want to draw other compositor elements, like effects, but those * can also go in the stack order of the windows. */ yutani_server_window_t * tmp_window = top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); if (!tmp_window || tmp_window->show_mouse) { draw_cursor(yg, tmp_mouse_x, tmp_mouse_y, tmp_window ? tmp_window->show_mouse : 1); } /* * Flip the updated areas. This minimizes writes to video memory, * which is very important on real hardware where these writes are slow. */ if (yg->backend_ctx->size == 0) { extern void gfx_flip_24bit(gfx_context_t * ctx); gfx_flip_24bit(yg->backend_ctx); } else { flip(yg->backend_ctx); } } foreach (node, yg->windows) { yutani_server_window_t * w = node->value; if (w->z == YUTANI_ZORDER_MAX && w != yg->top_z) { if (yutani_is_closing_animation[w->anim_mode]) { list_insert(yg->windows_to_remove, w); } } } /* * If any windows were marked for removal, * then remove them. */ while (yg->windows_to_remove->tail) { node_t * node = list_pop(yg->windows_to_remove); window_actually_close(yg, node->value); free(node); } while (yg->windows_to_minimize->tail) { node_t * node = list_pop(yg->windows_to_minimize); window_finish_minimize(yg, node->value); free(node); } } if (yg->screenshot_frame) { yutani_screenshot(yg); } } /** * Initialize clipping regions. */ void yutani_clip_init(yutani_globals_t * yg) { yg->update_list = list_create(); } /** * Mark a region within a window as damaged. * * If the window is rotated, we calculate the minimum rectangle that covers * the whole region specified and then mark that. */ static 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) { if (window->hidden || window->minimized) return; yutani_damage_rect_t * rect = malloc(sizeof(yutani_damage_rect_t)); yutani_server_window_t fake_window; if (window == yg->resizing_window) { fake_window.width = yg->resizing_init_w; fake_window.height = yg->resizing_init_h; fake_window.x = window->x; fake_window.y = window->y; fake_window.rotation = window->rotation; double x_scale = (double)yg->resizing_w / (double)yg->resizing_window->width; double y_scale = (double)yg->resizing_h / (double)yg->resizing_window->height; x *= x_scale; x += yg->resizing_offset_x - 1; y *= y_scale; y += yg->resizing_offset_y - 1; width *= x_scale; height *= y_scale; width += 2; height += 2; window = &fake_window; } if (window->rotation == 0) { rect->x = window->x + x; rect->y = window->y + y; rect->width = width; rect->height = height; } else { int32_t ul_x, ul_y; int32_t ll_x, ll_y; int32_t ur_x, ur_y; int32_t lr_x, lr_y; yutani_window_to_device(window, x, y, &ul_x, &ul_y); yutani_window_to_device(window, x, y + height, &ll_x, &ll_y); yutani_window_to_device(window, x + width, y, &ur_x, &ur_y); yutani_window_to_device(window, x + width, y + height, &lr_x, &lr_y); /* Calculate bounds */ int32_t left_bound = min(min(ul_x, ll_x), min(ur_x, lr_x)); int32_t top_bound = min(min(ul_y, ll_y), min(ur_y, lr_y)); int32_t right_bound = max(max(ul_x+1, ll_x+1), max(ur_x+1, lr_x+1)); int32_t bottom_bound = max(max(ul_y+1, ll_y+1), max(ur_y+1, lr_y+1)); rect->x = left_bound; rect->y = top_bound; rect->width = right_bound - left_bound; rect->height = bottom_bound - top_bound; } list_insert(yg->update_list, rect); } /** * (Convenience function) Mark a whole a window as damaged. */ static void mark_window(yutani_globals_t * yg, yutani_server_window_t * window) { mark_window_relative(yg, window, 0, 0, window->width, window->height); } /** * Set a window as closed. It will be removed after rendering has completed. */ static void window_mark_for_close(yutani_globals_t * yg, yutani_server_window_t * w) { if (w->hidden || w->minimized) { window_actually_close(yg, w); } else { w->anim_mode = yutani_pick_animation(w->server_flags, 1); w->anim_start = yutani_current_time(yg); } } /** * Remove a window from its owner's child set. */ static void window_remove_from_client(yutani_globals_t * yg, yutani_server_window_t * w) { list_t * client_list = hashmap_get(yg->clients_to_windows, (void *)(uintptr_t)w->owner); if (client_list) { node_t * n = list_find(client_list, w); if (n) { list_delete(client_list, n); free(n); } } } static void window_finish_minimize(yutani_globals_t * yg, yutani_server_window_t * w) { if (w->minimized) return; w->minimized = 1; w->anim_mode = 0; w->anim_start = 0; unorder_window(yg,w); if (w == yg->focused_window) { yg->focused_window = NULL; if (yg->menu_zs->tail && yg->menu_zs->tail->value) { set_focused_window(yg, yg->menu_zs->tail->value); } else if (yg->mid_zs->tail && yg->mid_zs->tail->value) { set_focused_window(yg, yg->mid_zs->tail->value); } else { set_focused_window(yg, yg->bottom_z); } } list_insert(yg->minimized_zs, w); notify_subscribers(yg); } /** * Actually remove a window and free the associated resources. */ static void window_actually_close(yutani_globals_t * yg, yutani_server_window_t * w) { /* Remove from the wid -> window mapping */ hashmap_remove(yg->wids_to_windows, (void *)(uintptr_t)w->wid); /* Remove from the general list of windows. */ list_remove(yg->windows, list_index_of(yg->windows, w)); if (w->minimized) { node_t * n = list_find(yg->minimized_zs, w); if (n) { list_delete(yg->minimized_zs, n); free(n); } } /* Unstack the window */ unorder_window(yg, w); /* Mark the region where the window was */ mark_window(yg, w); /* And if it was focused, unfocus it. */ if (w == yg->focused_window) { /* find the top z-ordered window */ yg->focused_window = NULL; if (yg->menu_zs->tail && yg->menu_zs->tail->value) { set_focused_window(yg, yg->menu_zs->tail->value); } else if (yg->mid_zs->tail && yg->mid_zs->tail->value) { set_focused_window(yg, yg->mid_zs->tail->value); } else { set_focused_window(yg, yg->bottom_z); } } { char key[1024]; YUTANI_SHMKEY_EXP(yg->server_ident, key, 1024, w->bufid); /* * Normally we would acquire a lock before doing this, but the render * thread holds that lock already and we are only called from the * render thread, so we don't bother. */ shm_release(key); } /* Notify subscribers that there are changes to windows */ notify_subscribers(yg); } /** * Generate flags for client advertisements. * * Currently, we only have one flag (focused). */ static uint32_t ad_flags(yutani_globals_t * yg, yutani_server_window_t * win) { uint32_t flags = win->client_flags; if (win == yg->focused_window || (yg->focused_window && yg->focused_window->parent == win->wid)) { flags |= 1; } if (win->minimized) { flags |= 2; } return flags; } /** * Send a result for a window query. */ static void yutani_query_result(yutani_globals_t * yg, uintptr_t dest, yutani_server_window_t * win) { if (win && win->client_length) { yutani_msg_buildx_window_advertise_alloc(response, win->client_length); yutani_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); pex_send(yg->server, dest, response->size, (char *)response); } } /** * Send a notice to all subscribed clients that windows have updated. */ static void notify_subscribers(yutani_globals_t * yg) { yutani_msg_buildx_notify_alloc(response); yutani_msg_buildx_notify(response); list_t * remove = NULL; foreach(node, yg->window_subscribers) { uintptr_t subscriber = (uintptr_t)node->value; if (!hashmap_has(yg->clients_to_windows, (void *)subscriber)) { if (!remove) { remove = list_create(); } list_insert(remove, node); } else { pex_send(yg->server, subscriber, response->size, (char *)response); } } if (remove) { while (remove->length) { node_t * n = list_pop(remove); list_delete(yg->window_subscribers, n->value); free(n); } free(remove); } } static void window_move(yutani_globals_t * yg, yutani_server_window_t * window, int x, int y) { mark_window(yg, window); window->x = x; window->y = y; mark_window(yg, window); yutani_msg_buildx_window_move_alloc(response); yutani_msg_buildx_window_move(response, window->wid, x, y); pex_send(yg->server, window->owner, response->size, (char *)response); } /** * Move and resize a window to fit a particular tiling pattern. * * x and y are 0-based * width_div and height_div are the number of cells in each dimension */ static void window_tile(yutani_globals_t * yg, yutani_server_window_t * window, int width_div, int height_div, int x, int y) { int panel_h = 0; yutani_server_window_t * panel = yg->top_z; if (panel) { panel_h = panel->height; if (panel->y < 1) { panel_h += panel->y; /* We can move the panel up to "hide" it. */ } } if (!window->tiled) { window->untiled_width = window->width; window->untiled_height = window->height; window->untiled_left = window->x; window->untiled_top = window->y; window->tiled = 1; } int w = yg->width / width_div; int h = (yg->height - panel_h) / height_div; int _x = w * x; int _y = panel_h + h * y; if (x == width_div - 1) { w = yg->width - w * x; } if (y == height_div - 1) { h = (yg->height - panel_h) - h * y; } int tile = YUTANI_RESIZE_TILED; /* If not left most */ if (x > 0) { _x -= 1; w++; tile &= ~YUTANI_RESIZE_TILE_LEFT; } /* If not right most */ if (x < width_div-1) { tile &= ~YUTANI_RESIZE_TILE_RIGHT; } /* If not top most */ if (y > 0) { _y -= 1; h++; tile &= ~YUTANI_RESIZE_TILE_UP; } /* If not bottom most */ if (y < height_div-1) { tile &= ~YUTANI_RESIZE_TILE_DOWN; } window_move(yg, window, _x, _y); yutani_msg_buildx_window_resize_alloc(response); yutani_msg_buildx_window_resize(response, YUTANI_MSG_RESIZE_OFFER, window->wid, w, h, 0, tile); pex_send(yg->server, window->owner, response->size, (char *)response); } /** * Take a previously tiled window and "untile" it, eg. restore its original size. */ static void window_untile(yutani_globals_t * yg, yutani_server_window_t * window) { window->tiled = 0; yutani_msg_buildx_window_resize_alloc(response); yutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_OFFER, window->wid, window->untiled_width, window->untiled_height, 0, 0); pex_send(yg->server, window->owner, response->size, (char *)response); } static void window_reveal(yutani_globals_t * yg, yutani_server_window_t * window) { if (!window->hidden) return; window->hidden = 0; window->anim_mode = yutani_pick_animation(window->server_flags, 0); window->anim_start = yutani_current_time(yg); } static void window_unminimize(yutani_globals_t * yg, yutani_server_window_t * window) { if (!window->minimized) return; node_t * n = list_find(yg->minimized_zs, window); if (n) { list_delete(yg->minimized_zs, n); free(n); } list_insert(yg->mid_zs, window); window->z = 1; window->minimized = 0; window->anim_mode = YUTANI_EFFECT_UNMINIMIZE; window->anim_start = yutani_current_time(yg); } static void window_minimize(yutani_globals_t * yg, yutani_server_window_t * window) { if (!window->client_length) return; /* Windows must be advertised to be minimized. */ window->anim_mode = YUTANI_EFFECT_MINIMIZE; window->anim_start = yutani_current_time(yg); } /** * Process a key event. * * These are mostly compositor shortcuts and bindings. * We also process key bindings for other applications. */ static void handle_key_event(yutani_globals_t * yg, struct yutani_msg_key_event * ke) { yg->active_modifiers = ke->event.modifiers; yutani_server_window_t * focused = get_focused(yg); if (focused) { #if 1 if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_SUPER) && (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) && (ke->event.keycode == 'z')) { mark_window(yg,focused); focused->rotation -= 5; mark_window(yg,focused); return; } if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_SUPER) && (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) && (ke->event.keycode == 'x')) { mark_window(yg,focused); focused->rotation += 5; mark_window(yg,focused); return; } if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_SUPER) && (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) && (ke->event.keycode == 'c')) { mark_window(yg,focused); focused->rotation = 0; mark_window(yg,focused); return; } if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_SUPER) && (ke->event.keycode == 'a')) { window_minimize(yg,focused); return; } #endif if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == KEY_F10)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { if (focused->tiled) { window_untile(yg, focused); window_move(yg, focused, focused->untiled_left, focused->untiled_top); } else { window_tile(yg, focused, 1, 1, 0, 0); } return; } } if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == KEY_F4)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { yutani_msg_buildx_window_close_alloc(response); yutani_msg_buildx_window_close(response, focused->wid); pex_send(yg->server, focused->owner, response->size, (char *)response); return; } } #if YUTANI_DEBUG_WINDOW_SHAPES if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_SUPER) && (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) && (ke->event.keycode == 'n')) { yg->debug_shapes = (1-yg->debug_shapes); return; } #endif #if YUTANI_DEBUG_WINDOW_BOUNDS if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_SUPER) && (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) && (ke->event.keycode == 'b')) { yg->debug_bounds = (1-yg->debug_bounds); return; } #endif #ifdef ENABLE_BLUR_BEHIND if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_SUPER) && (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) && (ke->event.keycode == 'v')) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { focused->server_flags ^= YUTANI_WINDOW_FLAG_BLUR_BEHIND; mark_window(yg, focused); } return; } #endif /* Screenshot key */ if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.keycode == KEY_PRINT_SCREEN)) { if (ke->event.modifiers & (KEY_MOD_LEFT_SHIFT | KEY_MOD_RIGHT_SHIFT)) { yg->screenshot_frame = YUTANI_SCREENSHOT_WINDOW; } else { yg->screenshot_frame = YUTANI_SCREENSHOT_FULL; } } if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.keycode == KEY_ESCAPE) && (yg->mouse_state == YUTANI_MOUSE_STATE_MOVING)) { mouse_stop_drag(yg); return; } /* * Tiling hooks. * These are based on the compiz grid plugin. */ if ((ke->event.action == KEY_ACTION_DOWN) && (ke->event.modifiers & KEY_MOD_LEFT_SUPER)) { if ((ke->event.modifiers & KEY_MOD_LEFT_SHIFT) && (ke->event.keycode == KEY_ARROW_LEFT)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { window_tile(yg, focused, 2, 2, 0, 0); return; } } if ((ke->event.modifiers & KEY_MOD_LEFT_SHIFT) && (ke->event.keycode == KEY_ARROW_RIGHT)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { window_tile(yg, focused, 2, 2, 1, 0); return; } } if ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) && (ke->event.keycode == KEY_ARROW_LEFT)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { window_tile(yg, focused, 2, 2, 0, 1); return; } } if ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) && (ke->event.keycode == KEY_ARROW_RIGHT)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { window_tile(yg, focused, 2, 2, 1, 1); return; } } if ((ke->event.keycode == KEY_ARROW_LEFT)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { window_tile(yg, focused, 2, 1, 0, 0); return; } } if ((ke->event.keycode == KEY_ARROW_RIGHT)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { window_tile(yg, focused, 2, 1, 1, 0); return; } } if ((ke->event.keycode == KEY_ARROW_UP)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { window_tile(yg, focused, 1, 2, 0, 0); return; } } if ((ke->event.keycode == KEY_ARROW_DOWN)) { if (focused->z != YUTANI_ZORDER_BOTTOM && focused->z != YUTANI_ZORDER_TOP) { window_tile(yg, focused, 1, 2, 0, 1); return; } } } } /* * External bindings registered by clients. */ uint32_t key_code = ((ke->event.modifiers << 24) | (ke->event.keycode)); if (hashmap_has(yg->key_binds, (void*)(uintptr_t)key_code)) { struct key_bind * bind = hashmap_get(yg->key_binds, (void*)(uintptr_t)key_code); yutani_msg_buildx_key_event_alloc(response); yutani_msg_buildx_key_event(response,focused ? focused->wid : UINT32_MAX, &ke->event, &ke->state); pex_send(yg->server, bind->owner, response->size, (char *)response); if (bind->response == YUTANI_BIND_STEAL) { /* If this keybinding was registered as "steal", we'll stop here. */ return; } } /* Finally, send the key to the focused client. */ if (focused) { yutani_msg_buildx_key_event_alloc(response); yutani_msg_buildx_key_event(response,focused->wid, &ke->event, &ke->state); pex_send(yg->server, focused->owner, response->size, (char *)response); } } /** * Register a new keybinding. * * req - bind message * owner - client to assign the binding to */ static void add_key_bind(yutani_globals_t * yg, struct yutani_msg_key_bind * req, uintptr_t owner) { uint32_t key_code = (((uint8_t)req->modifiers << 24) | ((uint32_t)req->key & 0xFFFFFF)); struct key_bind * bind = hashmap_get(yg->key_binds, (void*)(uintptr_t)key_code); if (!bind) { bind = malloc(sizeof(struct key_bind)); bind->owner = owner; bind->response = req->response; hashmap_set(yg->key_binds, (void*)(uintptr_t)key_code, bind); } else { bind->owner = owner; bind->response = req->response; } } static void adjust_window_opacity(yutani_globals_t * yg, int direction) { yutani_server_window_t * window = top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); if (window && window->z != YUTANI_ZORDER_BOTTOM) { window->opacity += direction; if (window->opacity < 0) { window->opacity = 0; } if (window->opacity > 255) { window->opacity = 255; } mark_window(yg, window); } } static void mouse_stop_drag(yutani_globals_t * yg) { yg->mouse_window = NULL; yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL; mark_screen(yg, yg->mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT); } static void mouse_start_drag(yutani_globals_t * yg, yutani_server_window_t * w) { if (yg->mouse_state == YUTANI_MOUSE_STATE_RESIZING || yg->mouse_state == YUTANI_MOUSE_STATE_ROTATING) return; /* Refuse */ set_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); if (!w) { yg->mouse_window = get_focused(yg); } else { yg->mouse_window = w; } if (yg->mouse_window) { if (yg->mouse_window->z == YUTANI_ZORDER_BOTTOM || yg->mouse_window->z == YUTANI_ZORDER_TOP || yg->mouse_window->server_flags & YUTANI_WINDOW_FLAG_DISALLOW_DRAG) { yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL; yg->mouse_window = NULL; } else { yg->mouse_state = YUTANI_MOUSE_STATE_MOVING; yg->mouse_init_x = yg->mouse_x; yg->mouse_init_y = yg->mouse_y; yg->mouse_win_x = yg->mouse_window->x; yg->mouse_win_y = yg->mouse_window->y; yg->mouse_drag_button = yg->last_mouse_buttons; mark_screen(yg, yg->mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT); make_top(yg, yg->mouse_window); } } } static void mouse_start_rotate(yutani_globals_t * yg) { set_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); yg->mouse_window = get_focused(yg); if (yg->mouse_window) { if (yg->mouse_window->z == YUTANI_ZORDER_BOTTOM || yg->mouse_window->z == YUTANI_ZORDER_TOP) { /* Prevent rotating panel and wallpaper */ yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL; yg->mouse_window = NULL; return; } yg->mouse_state = YUTANI_MOUSE_STATE_ROTATING; yg->mouse_init_x = yg->mouse_x; yg->mouse_init_y = yg->mouse_y; int32_t x_diff = yg->mouse_x / MOUSE_SCALE - (yg->mouse_window->x + yg->mouse_window->width / 2); int32_t y_diff = yg->mouse_y / MOUSE_SCALE - (yg->mouse_window->y + yg->mouse_window->height / 2); int new_r = atan2(x_diff, y_diff) * 180.0 / (-M_PI); yg->mouse_init_r = yg->mouse_window->rotation - new_r; make_top(yg, yg->mouse_window); } } static void mouse_start_resize(yutani_globals_t * yg, yutani_scale_direction_t direction) { set_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); yg->mouse_window = get_focused(yg); if (yg->mouse_window) { if (yg->mouse_window->z == YUTANI_ZORDER_BOTTOM || yg->mouse_window->z == YUTANI_ZORDER_TOP || yg->mouse_window->server_flags & YUTANI_WINDOW_FLAG_DISALLOW_RESIZE) { /* Prevent resizing panel and wallpaper */ yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL; yg->mouse_window = NULL; yg->resizing_window = NULL; } else { TRACE("resize starting for wid=%d", yg->mouse_window -> wid); yg->mouse_state = YUTANI_MOUSE_STATE_RESIZING; yg->mouse_init_x = yg->mouse_x; yg->mouse_init_y = yg->mouse_y; yg->mouse_win_x = yg->mouse_window->x; yg->mouse_win_y = yg->mouse_window->y; yg->resizing_window = yg->mouse_window; yg->resizing_w = yg->mouse_window->width; yg->resizing_h = yg->mouse_window->height; yg->resizing_offset_x = 0; yg->resizing_offset_y = 0; yg->resizing_init_w = yg->mouse_window->width; yg->resizing_init_h = yg->mouse_window->height; if (direction == SCALE_AUTO) { /* Determine the best direction to scale in based on simple 9-cell system. */ int32_t x, y; yutani_device_to_window(yg->resizing_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y); int h_d = 0; int v_d = 0; if (y <= yg->resizing_h / 3) { v_d = -1; } else if (y >= (yg->resizing_h / 3) * 2) { v_d = 1; } if (x <= yg->resizing_w / 3) { h_d = -1; } else if (x >= (yg->resizing_w / 3) * 2) { h_d = 1; } /* Fall back */ if (h_d == 0 && v_d == 0) direction = SCALE_DOWN_RIGHT; else if (h_d == 1 && v_d == 1) direction = SCALE_DOWN_RIGHT; else if (h_d == 1 && v_d == -1) direction = SCALE_UP_RIGHT; else if (h_d == -1 && v_d == 1) direction = SCALE_DOWN_LEFT; else if (h_d == -1 && v_d == -1) direction = SCALE_UP_LEFT; else if (h_d == 1 && v_d == 0) direction = SCALE_RIGHT; else if (h_d == -1 && v_d == 0) direction = SCALE_LEFT; else if (h_d == 0 && v_d == 1) direction = SCALE_DOWN; else if (h_d == 0 && v_d == -1) direction = SCALE_UP; } yg->resizing_direction = direction; make_top(yg, yg->mouse_window); mark_window(yg, yg->resizing_window); } } } static void handle_mouse_event(yutani_globals_t * yg, struct yutani_msg_mouse_event * me) { if (me->type == YUTANI_MOUSE_EVENT_TYPE_RELATIVE) { yg->mouse_x += me->event.x_difference YUTANI_INCOMING_MOUSE_SCALE; yg->mouse_y -= me->event.y_difference YUTANI_INCOMING_MOUSE_SCALE; } else if (me->type == YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE) { yg->mouse_x = me->event.x_difference * MOUSE_SCALE; yg->mouse_y = me->event.y_difference * MOUSE_SCALE; } if (yg->mouse_x < 0) yg->mouse_x = 0; if (yg->mouse_y < 0) yg->mouse_y = 0; if (yg->mouse_x > (int)(yg->width) * MOUSE_SCALE) yg->mouse_x = (yg->width) * MOUSE_SCALE; if (yg->mouse_y > (int)(yg->height) * MOUSE_SCALE) yg->mouse_y = (yg->height) * MOUSE_SCALE; switch (yg->mouse_state) { case YUTANI_MOUSE_STATE_NORMAL: { if ((me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT) && (yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) { mouse_start_drag(yg, NULL); } else if ((me->event.buttons & YUTANI_MOUSE_SCROLL_UP) && (yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) { adjust_window_opacity(yg, 8); } else if ((me->event.buttons & YUTANI_MOUSE_SCROLL_DOWN) && (yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) { adjust_window_opacity(yg, -8); } else if ((me->event.buttons & YUTANI_MOUSE_BUTTON_RIGHT) && (yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) { mouse_start_rotate(yg); } else if ((me->event.buttons & YUTANI_MOUSE_BUTTON_MIDDLE) && (yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) { yg->resizing_button = YUTANI_MOUSE_BUTTON_MIDDLE; mouse_start_resize(yg, SCALE_AUTO); } else if ((me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT) && !(yg->active_modifiers & YUTANI_KEY_MODIFIER_ALT)) { yg->mouse_state = YUTANI_MOUSE_STATE_DRAGGING; set_focused_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); yg->mouse_window = get_focused(yg); yg->mouse_moved = 0; yg->mouse_drag_button = YUTANI_MOUSE_BUTTON_LEFT; if (yg->mouse_window) { yutani_device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y); yutani_msg_buildx_window_mouse_event_alloc(response); yutani_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); yg->mouse_click_x_orig = yg->mouse_click_x; yg->mouse_click_y_orig = yg->mouse_click_y; pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response); } } else { yg->mouse_window = get_focused(yg); yutani_server_window_t * tmp_window = top_at(yg, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE); if (yg->mouse_window && !(me->event.buttons & YUTANI_MOUSE_BUTTON_RIGHT)) { int32_t x, y; yutani_device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y); yutani_msg_buildx_window_mouse_event_alloc(response); yutani_msg_buildx_window_mouse_event(response,yg->mouse_window->wid, x, y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_MOVE, yg->active_modifiers); pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response); } if (tmp_window) { int32_t x, y; yutani_msg_buildx_window_mouse_event_alloc(response); if (tmp_window != yg->old_hover_window) { yutani_device_to_window(tmp_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y); yutani_msg_buildx_window_mouse_event(response, tmp_window->wid, x, y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_ENTER, yg->active_modifiers); pex_send(yg->server, tmp_window->owner, response->size, (char *)response); if (yg->old_hover_window) { yutani_device_to_window(yg->old_hover_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y); yutani_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); pex_send(yg->server, yg->old_hover_window->owner, response->size, (char *)response); } yg->old_hover_window = tmp_window; } if (tmp_window != yg->mouse_window || (me->event.buttons & YUTANI_MOUSE_BUTTON_RIGHT)) { yutani_device_to_window(tmp_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &x, &y); yutani_msg_buildx_window_mouse_event(response, tmp_window->wid, x, y, -1, -1, me->event.buttons, YUTANI_MOUSE_EVENT_MOVE, yg->active_modifiers); pex_send(yg->server, tmp_window->owner, response->size, (char *)response); } } } } break; case YUTANI_MOUSE_STATE_MOVING: { int button_down = (me->event.buttons & YUTANI_MOUSE_BUTTON_LEFT); int drag_stop = yg->mouse_drag_button != 0 ? (!button_down) : (button_down); if (drag_stop) { mouse_stop_drag(yg); } else { if (yg->mouse_y / MOUSE_SCALE < 10) { if (!yg->mouse_window->tiled) { window_tile(yg, yg->mouse_window, 1, 1, 0, 0); } break; } if (yg->mouse_x / MOUSE_SCALE < 10) { if (!yg->mouse_window->tiled) { window_tile(yg, yg->mouse_window, 2, 1, 0, 0); } break; } else if (yg->mouse_x / MOUSE_SCALE >= ((int)yg->width - 10)) { if (!yg->mouse_window->tiled) { window_tile(yg, yg->mouse_window, 2, 1, 1, 0); } break; } if (yg->mouse_window->tiled) { if ((abs(yg->mouse_x - yg->mouse_init_x) > UNTILE_SENSITIVITY) || (abs(yg->mouse_y - yg->mouse_init_y) > UNTILE_SENSITIVITY)) { /* Untile it */ window_untile(yg,yg->mouse_window); /* Position the window such that it's representative of where it was, percentage-wise, in the untiled window */ float percent_x = (float)(yg->mouse_x / MOUSE_SCALE - yg->mouse_window->x) / (float)yg->mouse_window->width; float percent_y = (float)(yg->mouse_y / MOUSE_SCALE - yg->mouse_window->y) / (float)yg->mouse_window->height; window_move(yg, yg->mouse_window, yg->mouse_x / MOUSE_SCALE - yg->mouse_window->untiled_width * percent_x, yg->mouse_y / MOUSE_SCALE - yg->mouse_window->untiled_height * percent_y); /* reset init_x / init_y */ yg->mouse_init_x = yg->mouse_x; yg->mouse_init_y = yg->mouse_y; yg->mouse_win_x = yg->mouse_window->x; yg->mouse_win_y = yg->mouse_window->y; } } else { int x, y; x = yg->mouse_win_x + (yg->mouse_x - yg->mouse_init_x) / MOUSE_SCALE; y = yg->mouse_win_y + (yg->mouse_y - yg->mouse_init_y) / MOUSE_SCALE; window_move(yg, yg->mouse_window, x, y); } } } break; case YUTANI_MOUSE_STATE_ROTATING: { if (!(me->event.buttons & YUTANI_MOUSE_BUTTON_RIGHT)) { yg->mouse_window = NULL; yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL; mark_screen(yg, yg->mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT); } else if (yg->mouse_window) { /* Calculate rotation and make relative to initial rotation */ int32_t x_diff = yg->mouse_x / MOUSE_SCALE - (yg->mouse_window->x + yg->mouse_window->width / 2); int32_t y_diff = yg->mouse_y / MOUSE_SCALE - (yg->mouse_window->y + yg->mouse_window->height / 2); int new_r = atan2(x_diff, y_diff) * 180.0 / (-M_PI); mark_window(yg, yg->mouse_window); /* Normalize to -179~180 range */ int nr = (new_r + yg->mouse_init_r + 360) % 360; yg->mouse_window->rotation = nr > 180 ? nr - 360 : nr; mark_window(yg, yg->mouse_window); } } break; case YUTANI_MOUSE_STATE_DRAGGING: { if (!(me->event.buttons & yg->mouse_drag_button)) { /* Mouse released */ yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL; int32_t old_x = yg->mouse_click_x_orig; int32_t old_y = yg->mouse_click_y_orig; if (yg->mouse_window) { yutani_device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y); if (!yg->mouse_moved) { yutani_msg_buildx_window_mouse_event_alloc(response); yutani_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); pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response); } else { yutani_msg_buildx_window_mouse_event_alloc(response); yutani_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); pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response); } } } else { yg->mouse_state = YUTANI_MOUSE_STATE_DRAGGING; yg->mouse_moved = 1; int32_t old_x = yg->mouse_click_x; int32_t old_y = yg->mouse_click_y; if (yg->mouse_window) { yutani_device_to_window(yg->mouse_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &yg->mouse_click_x, &yg->mouse_click_y); if (old_x != yg->mouse_click_x || old_y != yg->mouse_click_y) { yutani_msg_buildx_window_mouse_event_alloc(response); yutani_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); pex_send(yg->server, yg->mouse_window->owner, response->size, (char *)response); } } } } break; case YUTANI_MOUSE_STATE_RESIZING: { int32_t relative_x, relative_y; int32_t relative_init_x, relative_init_y; yutani_server_window_t fake_window = { .width = yg->resizing_init_w, .height = yg->resizing_init_h, .x = yg->resizing_window->x, .y = yg->resizing_window->y, .rotation = yg->resizing_window->rotation, }; yutani_device_to_window(&fake_window, yg->mouse_init_x / MOUSE_SCALE, yg->mouse_init_y / MOUSE_SCALE, &relative_init_x, &relative_init_y); yutani_device_to_window(&fake_window, yg->mouse_x / MOUSE_SCALE, yg->mouse_y / MOUSE_SCALE, &relative_x, &relative_y); int width_diff = (relative_x - relative_init_x); int height_diff = (relative_y - relative_init_y); mark_window(yg, yg->resizing_window); if (yg->resizing_direction == SCALE_UP || yg->resizing_direction == SCALE_DOWN) { width_diff = 0; yg->resizing_offset_x = 0; } if (yg->resizing_direction == SCALE_LEFT || yg->resizing_direction == SCALE_RIGHT) { height_diff = 0; yg->resizing_offset_y = 0; } if (yg->resizing_direction == SCALE_LEFT || yg->resizing_direction == SCALE_UP_LEFT || yg->resizing_direction == SCALE_DOWN_LEFT) { yg->resizing_offset_x = width_diff; width_diff = -width_diff; } else if (yg->resizing_direction == SCALE_RIGHT || yg->resizing_direction == SCALE_UP_RIGHT || yg->resizing_direction == SCALE_DOWN_RIGHT) { yg->resizing_offset_x = 0; } if (yg->resizing_direction == SCALE_UP || yg->resizing_direction == SCALE_UP_LEFT || yg->resizing_direction == SCALE_UP_RIGHT) { yg->resizing_offset_y = height_diff; height_diff = -height_diff; } else if (yg->resizing_direction == SCALE_DOWN || yg->resizing_direction == SCALE_DOWN_LEFT || yg->resizing_direction == SCALE_DOWN_RIGHT) { yg->resizing_offset_y = 0; } yg->resizing_w = yg->resizing_init_w + width_diff; yg->resizing_h = yg->resizing_init_h + height_diff; /* Enforce logical boundaries */ if (yg->resizing_w < 1) { yg->resizing_w = 1; } if (yg->resizing_h < 1) { yg->resizing_h = 1; } if (yg->resizing_offset_x > yg->resizing_init_w) { yg->resizing_offset_x = yg->resizing_init_w; } if (yg->resizing_offset_y > yg->resizing_init_h) { yg->resizing_offset_y = yg->resizing_init_h; } mark_window(yg, yg->resizing_window); if (!yg->resize_release_time || !(me->event.buttons & yg->resizing_button) || (yg->resize_release_time && yutani_time_since(yg, yg->resize_release_time) >= 100)) { yg->resize_release_time = yutani_current_time(yg); yutani_msg_buildx_window_resize_alloc(response); yutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_OFFER, yg->resizing_window->wid, yg->resizing_w, yg->resizing_h, 0, yg->resizing_window->tiled); pex_send(yg->server, yg->resizing_window->owner, response->size, (char *)response); } if (!(me->event.buttons & yg->resizing_button)) { yg->mouse_state = YUTANI_MOUSE_STATE_NORMAL; } } break; default: /* XXX ? */ break; } } static yutani_globals_t * _static_yg; static void yutani_display_resize_handle(int signum) { (void)signum; TRACE("Display change request, one moment."); _static_yg->resize_on_next = 1; signal(SIGWINEVENT, yutani_display_resize_handle); } #define FONT_PATH "/usr/share/fonts/" #define FONT(a,b) {a, FONT_PATH b} struct font_def { char * identifier; char * path; }; /** * TODO: This should be configurable... */ static struct font_def fonts[] = { FONT("sans-serif", "truetype/dejavu/DejaVuSans.ttf"), FONT("sans-serif.bold", "truetype/dejavu/DejaVuSans-Bold.ttf"), FONT("sans-serif.italic", "truetype/dejavu/DejaVuSans-Oblique.ttf"), FONT("sans-serif.bolditalic", "truetype/dejavu/DejaVuSans-BoldOblique.ttf"), FONT("monospace", "truetype/dejavu/DejaVuSansMono.ttf"), FONT("monospace.bold", "truetype/dejavu/DejaVuSansMono-Bold.ttf"), FONT("monospace.italic", "truetype/dejavu/DejaVuSansMono-Oblique.ttf"), FONT("monospace.bolditalic", "truetype/dejavu/DejaVuSansMono-BoldOblique.ttf"), {NULL, NULL} }; static char * precache_shmfont(char * ident, char * name) { FILE * f = fopen(name, "r"); if (!f) return NULL; size_t s = 0; fseek(f, 0, SEEK_END); s = ftell(f); fseek(f, 0, SEEK_SET); size_t shm_size = s; char * font = shm_obtain(ident, &shm_size); assert((shm_size >= s) && "shm_obtain returned too little memory to load a font into!"); fread(font, s, 1, f); fclose(f); return font; } static void load_fonts(yutani_globals_t * yg) { int i = 0; while (fonts[i].identifier) { char tmp[100]; sprintf(tmp, "sys.%s.fonts.%s", yg->server_ident, fonts[i].identifier); TRACE("Loading font %s -> %s", fonts[i].path, tmp); if (!precache_shmfont(tmp, fonts[i].path)) { TRACE(" ... failed."); } ++i; } } /** * main */ int main(int argc, char * argv[]) { int argx = 0; int results = parse_args(argc, argv, &argx); if (results) return results; yutani_globals_t * yg = malloc(sizeof(yutani_globals_t)); memset(yg, 0x00, sizeof(yutani_globals_t)); if (yutani_options.nested) { yg->host_context = yutani_init(); yg->host_window = yutani_window_create(yg->host_context, yutani_options.nest_width, yutani_options.nest_height); yutani_window_move(yg->host_context, yg->host_window, 50, 50); yutani_window_advertise_icon(yg->host_context, yg->host_window, "Compositor", "compositor"); yg->backend_ctx = init_graphics_yutani_double_buffer(yg->host_window); } else { char * d = getenv("DISPLAY"); if (d && *d) { fprintf(stderr, "DISPLAY is already set but not running nested. This is probably wrong.\n"); return 1; } _static_yg = yg; signal(SIGWINEVENT, yutani_display_resize_handle); yg->backend_ctx = init_graphics_fullscreen_double_buffer(); } #ifdef ENABLE_BLUR_BEHIND blur_texture = realloc(blur_texture, yg->backend_ctx->stride * yg->backend_ctx->height); blur_ctx = init_graphics_with_store(yg->backend_ctx, blur_texture); clip_ctx = calloc(1, sizeof(gfx_context_t)); clip_ctx->width = yg->backend_ctx->width; clip_ctx->height = yg->backend_ctx->height; #endif if (!yg->backend_ctx) { free(yg); TRACE("Failed to open framebuffer, bailing."); return 1; } { struct timeval t; gettimeofday(&t, NULL); yg->start_time = t.tv_sec; yg->start_subtime = t.tv_usec; } yg->width = yg->backend_ctx->width; yg->height = yg->backend_ctx->height; draw_fill(yg->backend_ctx, rgb(0,0,0)); flip(yg->backend_ctx); yg->backend_framebuffer = yg->backend_ctx->backbuffer; if (yutani_options.nested) { char * name = malloc(sizeof(char) * 64); sprintf(name, "compositor-nest-%d", getpid()); yg->server_ident = name; } else { /* XXX check if this already exists? */ yg->server_ident = "compositor"; } setenv("DISPLAY", yg->server_ident, 1); FILE * server = pex_bind(yg->server_ident); TRACE("pex bound? %d", server); yg->server = server; load_fonts(yg); TRACE("Loading sprites..."); #define MOUSE_DIR "/usr/share/cursor/" load_sprite(&yg->mouse_sprite, MOUSE_DIR "normal.png"); load_sprite(&yg->mouse_sprite_drag, MOUSE_DIR "grab.png"); load_sprite(&yg->mouse_sprite_resize_v, MOUSE_DIR "resize-vertical.png"); load_sprite(&yg->mouse_sprite_resize_h, MOUSE_DIR "resize-horizontal.png"); load_sprite(&yg->mouse_sprite_resize_da, MOUSE_DIR "resize-uldr.png"); load_sprite(&yg->mouse_sprite_resize_db, MOUSE_DIR "resize-dlur.png"); load_sprite(&yg->mouse_sprite_point, MOUSE_DIR "point.png"); load_sprite(&yg->mouse_sprite_ibeam, MOUSE_DIR "ibeam.png"); TRACE("Done."); TRACE("Initializing variables..."); yg->last_mouse_x = 0; yg->last_mouse_y = 0; yg->mouse_x = yg->width * MOUSE_SCALE / 2; yg->mouse_y = yg->height * MOUSE_SCALE / 2; yg->windows = list_create(); yg->wids_to_windows = hashmap_create_int(10); yg->key_binds = hashmap_create_int(10); yg->clients_to_windows = hashmap_create_int(10); yg->mid_zs = list_create(); yg->menu_zs = list_create(); yg->overlay_zs = list_create(); yg->windows_to_remove = list_create(); yg->windows_to_minimize = list_create(); yg->minimized_zs = list_create(); yg->window_subscribers = list_create(); yg->last_mouse_buttons = 0; yg->resize_release_time = 0; TRACE("Done."); yutani_clip_init(yg); if (!fork()) { if (argx < argc) { TRACE("Starting alternate startup app: %s", argv[argx]); execvp(argv[argx], &argv[argx]); } else { TRACE("Starting application"); char * args[] = {"/bin/glogin", NULL}; execvp(args[0], args); TRACE("Failed to start app?"); } } int fds[4]; int mfd = -1; int kfd = -1; int amfd = -1; int vmmouse = 0; mouse_device_packet_t packet; key_event_t event; key_event_state_t state = {0}; fds[0] = fileno(server); if (yutani_options.nested) { fds[1] = fileno(yg->host_context->sock); } else { mfd = open("/dev/mouse", O_RDONLY); kfd = open("/dev/kbd", O_RDONLY); amfd = open("/dev/absmouse", O_RDONLY); if (amfd < 0) { amfd = open("/dev/vmmouse", O_RDONLY); vmmouse = 1; } yg->vbox_rects = open("/dev/vboxrects", O_WRONLY); yg->vbox_pointer = open("/dev/vboxpointer", O_WRONLY); fds[1] = mfd; fds[2] = kfd; fds[3] = amfd; } uint64_t last_redraw = 0; while (1) { unsigned long frameTime = yutani_time_since(yg, last_redraw); if (frameTime > 15) { redraw_windows(yg); last_redraw = yutani_current_time(yg); frameTime = 0; } if (yutani_options.nested) { int index = fswait2(2, fds, 16 - frameTime); if (index == 1) { yutani_msg_t * m = yutani_poll(yg->host_context); if (m) { switch (m->type) { case YUTANI_MSG_KEY_EVENT: { struct yutani_msg_key_event * ke = (void*)m->data; yutani_msg_buildx_key_event_alloc(m_); yutani_msg_buildx_key_event(m_, 0, &ke->event, &ke->state); handle_key_event(yg, (struct yutani_msg_key_event *)m_->data); } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; mouse_device_packet_t packet; packet.buttons = me->buttons; packet.x_difference = me->new_x; packet.y_difference = me->new_y; yg->last_mouse_buttons = packet.buttons; yutani_msg_buildx_mouse_event_alloc(m_); yutani_msg_buildx_mouse_event(m_, 0, &packet, YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE); handle_mouse_event(yg, (struct yutani_msg_mouse_event *)m_->data); } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; TRACE("Resize request from host compositor for size %dx%d", wr->width, wr->height); yutani_window_resize_accept(yg->host_context, yg->host_window, wr->width, wr->height); yg->resize_on_next = 1; } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: { TRACE("Host session ended. Should exit."); yutani_msg_buildx_session_end_alloc(response); yutani_msg_buildx_session_end(response); pex_broadcast(server, response->size, (char *)response); yg->server = NULL; exit(0); } break; default: break; } } free(m); continue; } else if (index > 0) { continue; } } else { int index = fswait2(amfd == -1 ? 3 : 4, fds, 16 - frameTime); if (index == 2) { unsigned char buf[1]; int r = read(kfd, buf, 1); if (r > 0) { if (kbd_scancode(&state, buf[0], &event)) { yutani_msg_buildx_key_event_alloc(m); yutani_msg_buildx_key_event(m,0, &event, &state); handle_key_event(yg, (struct yutani_msg_key_event *)m->data); } } continue; } else if (index == 1) { int r = read(mfd, (char *)&packet, sizeof(mouse_device_packet_t)); if (r > 0) { yg->last_mouse_buttons = packet.buttons; yutani_msg_buildx_mouse_event_alloc(m); yutani_msg_buildx_mouse_event(m,0, &packet, YUTANI_MOUSE_EVENT_TYPE_RELATIVE); handle_mouse_event(yg, (struct yutani_msg_mouse_event *)m->data); } continue; } else if (amfd != -1 && index == 3) { int r = read(amfd, (char *)&packet, sizeof(mouse_device_packet_t)); if (r > 0) { if (!vmmouse) { packet.buttons = yg->last_mouse_buttons & 0xF; } else { yg->last_mouse_buttons = packet.buttons; } yutani_msg_buildx_mouse_event_alloc(m); yutani_msg_buildx_mouse_event(m,0, &packet, YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE); handle_mouse_event(yg, (struct yutani_msg_mouse_event *)m->data); } continue; } else if (index > 0) { continue; } } pex_packet_t * p = calloc(PACKET_SIZE, 1); pex_listen(server, p); yutani_msg_t * m = (yutani_msg_t *)p->data; if (p->size == 0) { /* Connection closed for client */ TRACE("Connection closed for client %x", p->source); list_t * client_list = hashmap_get(yg->clients_to_windows, (void *)p->source); if (client_list) { foreach(node, client_list) { yutani_server_window_t * win = node->value; TRACE("Killing window %d", win->wid); window_mark_for_close(yg, win); } hashmap_remove(yg->clients_to_windows, (void *)p->source); list_free(client_list); free(client_list); } if (hashmap_is_empty(yg->clients_to_windows)) { TRACE("Last compositor client disconnected, exiting."); yg->server = NULL; exit(0); } free(p); continue; } if (m->magic != YUTANI_MSG__MAGIC) { TRACE("Message has bad magic. (Should eject client, but will instead skip this message.) 0x%x", m->magic); free(p); continue; } switch(m->type) { case YUTANI_MSG_HELLO: { TRACE("And hello to you, %p!", p->source); list_t * client_list = hashmap_get(yg->clients_to_windows, (void *)p->source); if (!client_list) { TRACE("Client is new: %p", p->source); client_list = list_create(); hashmap_set(yg->clients_to_windows, (void *)p->source, client_list); } yutani_msg_buildx_welcome_alloc(response); yutani_msg_buildx_welcome(response,yg->width, yg->height); pex_send(server, p->source, response->size, (char *)response); } break; case YUTANI_MSG_WINDOW_NEW: case YUTANI_MSG_WINDOW_NEW_FLAGS: { struct yutani_msg_window_new_flags * wn = (void *)m->data; TRACE("Client %p requested a new window (%dx%d).", p->source, wn->width, wn->height); yutani_server_window_t * w = server_window_create(yg, wn->width, wn->height, p->source, m->type != YUTANI_MSG_WINDOW_NEW ? wn->flags : 0); if (w->server_flags & YUTANI_WINDOW_FLAG_PARENT_WID) { TRACE("Set parent wid of %d to %d\n", w->wid, wn->parent_wid); w->parent = wn->parent_wid; } yutani_msg_buildx_window_init_alloc(response); yutani_msg_buildx_window_init(response,w->wid, w->width, w->height, w->bufid); pex_send(server, p->source, response->size, (char *)response); if (!(w->server_flags & YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS)) { set_focused_window(yg, w); } notify_subscribers(yg); } break; case YUTANI_MSG_FLIP: { struct yutani_msg_flip * wf = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wf->wid); if (w) { window_reveal(yg, w); mark_window(yg, w); } } break; case YUTANI_MSG_FLIP_REGION: { struct yutani_msg_flip_region * wf = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wf->wid); if (w) { window_reveal(yg, w); mark_window_relative(yg, w, wf->x, wf->y, wf->width, wf->height); } } break; case YUTANI_MSG_KEY_EVENT: { /* XXX Verify this is from a valid device client */ struct yutani_msg_key_event * ke = (void *)m->data; handle_key_event(yg, ke); } break; case YUTANI_MSG_MOUSE_EVENT: { /* XXX Verify this is from a valid device client */ struct yutani_msg_mouse_event * me = (void *)m->data; handle_mouse_event(yg, me); } break; case YUTANI_MSG_WINDOW_MOVE: { struct yutani_msg_window_move * wm = (void *)m->data; //TRACE("%08x wanted to move window %d to %d, %d", p->source, wm->wid, (int)wm->x, (int)wm->y); if (wm->x > (int)yg->width + 100 || wm->x < -(int)yg->width || wm->y > (int)yg->height + 100 || wm->y < -(int)yg->height) { TRACE("Refusing to move window to these coordinates."); break; } yutani_server_window_t * win = hashmap_get(yg->wids_to_windows, (void*)(uintptr_t)wm->wid); if (win) { window_move(yg, win, wm->x, wm->y); } else { TRACE("%08x wanted to move window %d, but I can't find it?", p->source, wm->wid); } } break; case YUTANI_MSG_WINDOW_MOVE_RELATIVE: { struct yutani_msg_window_move_relative * wm = (void *)m->data; yutani_server_window_t * movee = hashmap_get(yg->wids_to_windows, (void*)(uintptr_t)wm->wid_to_move); yutani_server_window_t * base = hashmap_get(yg->wids_to_windows, (void*)(uintptr_t)wm->wid_base); if (!movee || !base) break; /* Map coordinate to new origin location */ int32_t nx, ny; yutani_window_to_device(base, wm->x + movee->width / 2, wm->y + movee->height / 2, &nx, &ny); window_move(yg, movee, nx - movee->width / 2, ny - movee->height / 2); /* Match window rotation to base window */ movee->rotation = base->rotation; } break; case YUTANI_MSG_WINDOW_SET_PARENT: { struct yutani_msg_window_set_parent * wsp = (void *)m->data; yutani_server_window_t * movee = hashmap_get(yg->wids_to_windows, (void*)(uintptr_t)wsp->wid); if (!movee) break; /* XXX Should we disallow parenting to a window owned by another client? * Validate that the window even exists? * For now, just accept any wid, whatever. */ movee->parent = wsp->parent_wid; notify_subscribers(yg); } break; case YUTANI_MSG_WINDOW_CLOSE: { struct yutani_msg_window_close * wc = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wc->wid); if (w) { window_mark_for_close(yg, w); window_remove_from_client(yg, w); } } break; case YUTANI_MSG_WINDOW_STACK: { struct yutani_msg_window_stack * ws = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)ws->wid); if (w) { reorder_window(yg, w, ws->z); } } break; case YUTANI_MSG_RESIZE_REQUEST: { struct yutani_msg_window_resize * wr = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wr->wid); if (w) { yutani_msg_buildx_window_resize_alloc(response); yutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_OFFER, w->wid, wr->width, wr->height, 0, w->tiled); pex_send(server, p->source, response->size, (char *)response); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wr->wid); if (w) { yutani_msg_buildx_window_resize_alloc(response); yutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_OFFER, w->wid, wr->width, wr->height, 0, w->tiled); pex_send(server, p->source, response->size, (char *)response); } } break; case YUTANI_MSG_RESIZE_ACCEPT: { struct yutani_msg_window_resize * wr = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wr->wid); if (w) { uint32_t newbufid = server_window_resize(yg, w, wr->width, wr->height); yutani_msg_buildx_window_resize_alloc(response); yutani_msg_buildx_window_resize(response,YUTANI_MSG_RESIZE_BUFID, w->wid, wr->width, wr->height, newbufid, 0); pex_send(server, p->source, response->size, (char *)response); } } break; case YUTANI_MSG_RESIZE_DONE: { struct yutani_msg_window_resize * wr = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wr->wid); if (w) { server_window_resize_finish(yg, w, wr->width, wr->height); notify_subscribers(yg); } } break; case YUTANI_MSG_QUERY_WINDOWS: { foreach (node, yg->minimized_zs) { yutani_query_result(yg, p->source, node->value); } yutani_query_result(yg, p->source, yg->bottom_z); foreach (node, yg->mid_zs) { yutani_query_result(yg, p->source, node->value); } /* Exclude menus, overlay windows, top and bottom. */ yutani_query_result(yg, p->source, yg->top_z); yutani_msg_buildx_window_advertise_alloc(response, 0); yutani_msg_buildx_window_advertise(response,0, 0, 0, 0, 0, 0, 0, NULL); pex_send(server, p->source, response->size, (char *)response); } break; case YUTANI_MSG_SUBSCRIBE: { foreach(node, yg->window_subscribers) { if ((uintptr_t)node->value == p->source) { break; } } list_insert(yg->window_subscribers, (void*)p->source); } break; case YUTANI_MSG_UNSUBSCRIBE: { node_t * node = list_find(yg->window_subscribers, (void*)p->source); if (node) { list_delete(yg->window_subscribers, node); } } break; case YUTANI_MSG_WINDOW_ADVERTISE: { struct yutani_msg_window_advertise * wa = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid); if (w) { if (w->client_strings) free(w->client_strings); w->client_icon = wa->icon; w->client_flags = wa->flags; w->client_length = wa->size; w->client_strings = malloc(wa->size); memcpy(w->client_strings, wa->strings, wa->size); notify_subscribers(yg); } } break; case YUTANI_MSG_SESSION_END: { yutani_msg_buildx_session_end_alloc(response); yutani_msg_buildx_session_end(response); pex_broadcast(server, response->size, (char *)response); } break; case YUTANI_MSG_WINDOW_FOCUS: { struct yutani_msg_window_focus * wa = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid); if (w) { set_focused_window(yg, w); } } break; case YUTANI_MSG_KEY_BIND: { struct yutani_msg_key_bind * wa = (void *)m->data; add_key_bind(yg, wa, p->source); } break; case YUTANI_MSG_WINDOW_DRAG_START: { struct yutani_msg_window_drag_start * wa = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid); if (w) { /* Start dragging */ mouse_start_drag(yg, w); } } break; case YUTANI_MSG_WINDOW_UPDATE_SHAPE: { struct yutani_msg_window_update_shape * wa = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid); if (w) { /* Set shape parameter */ server_window_update_shape(yg, w, wa->set_shape); } } break; case YUTANI_MSG_WINDOW_WARP_MOUSE: { struct yutani_msg_window_warp_mouse * wa = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid); if (w) { if (yg->focused_window == w) { int32_t x, y; yutani_window_to_device(w, wa->x, wa->y, &x, &y); struct yutani_msg_mouse_event me; me.event.x_difference = x; me.event.y_difference = y; me.event.buttons = yg->last_mouse_buttons; me.type = YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE; me.wid = wa->wid; handle_mouse_event(yg, &me); } } } break; case YUTANI_MSG_WINDOW_SHOW_MOUSE: { struct yutani_msg_window_show_mouse * wa = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid); if (w) { if (wa->show_mouse == -1) { w->show_mouse = w->default_mouse; } else if (wa->show_mouse < 2) { w->default_mouse = wa->show_mouse; w->show_mouse = wa->show_mouse; } else { w->show_mouse = wa->show_mouse; } if (yg->focused_window == w) { mark_screen(yg, yg->mouse_x / MOUSE_SCALE - MOUSE_OFFSET_X, yg->mouse_y / MOUSE_SCALE - MOUSE_OFFSET_Y, MOUSE_WIDTH, MOUSE_HEIGHT); } } } break; case YUTANI_MSG_WINDOW_RESIZE_START: { struct yutani_msg_window_resize_start * wa = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)wa->wid); if (w) { if (yg->focused_window == w && !yg->resizing_window) { yg->resizing_window = w; yg->resizing_button = YUTANI_MOUSE_BUTTON_LEFT; /* XXX Uh, what if we used something else */ mouse_start_resize(yg, wa->direction); } } } break; case YUTANI_MSG_SPECIAL_REQUEST: { struct yutani_msg_special_request * sr = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)sr->wid); switch (sr->request) { case YUTANI_SPECIAL_REQUEST_MAXIMIZE: if (w) { if (w->tiled) { window_untile(yg,w); window_move(yg,w,w->untiled_left,w->untiled_top); } else { window_tile(yg, w, 1, 1, 0, 0); } } break; case YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE: if (w) { yutani_msg_buildx_window_close_alloc(response); yutani_msg_buildx_window_close(response, w->wid); pex_send(yg->server, w->owner, response->size, (char *)response); } break; case YUTANI_SPECIAL_REQUEST_MINIMIZE: if (w) { window_minimize(yg,w); } break; case YUTANI_SPECIAL_REQUEST_CLIPBOARD: { yutani_msg_buildx_clipboard_alloc(response, yg->clipboard_size); yutani_msg_buildx_clipboard(response, yg->clipboard); pex_send(server, p->source, response->size, (char *)response); } break; default: TRACE("Unknown special request type: 0x%x", sr->request); break; } } break; case YUTANI_MSG_CLIPBOARD: { struct yutani_msg_clipboard * cb = (void *)m->data; yg->clipboard_size = min(cb->size, 511); memcpy(yg->clipboard, cb->content, yg->clipboard_size); yg->clipboard[yg->clipboard_size] = '\0'; TRACE("Copied text to clipbard (size=%d)", yg->clipboard_size); } break; case YUTANI_MSG_WINDOW_PANEL_SIZE: { struct yutani_msg_window_panel_size * ps = (void *)m->data; yutani_server_window_t * w = hashmap_get(yg->wids_to_windows, (void *)(uintptr_t)ps->wid); if (w) { w->icon_x = ps->x; w->icon_y = ps->y; w->icon_w = ps->w; w->icon_h = ps->h; } } break; default: { TRACE("Unknown type: 0x%8x", m->type); } break; } free(p); } return 0; } ================================================ FILE: apps/cp.c ================================================ /** * @brief cp - Copy files * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #define CHUNK_SIZE 4096 #ifndef IS_MV #define APP_NAME "cp" static int recursive = 0; #endif static int symlinks = 0; static int copy_thing(char * tmp, char * tmp2); static int copy_link(char * source, char * dest, int mode, int uid, int gid) { //fprintf(stderr, "need to copy link %s to %s\n", source, dest); char tmp[1024]; readlink(source, tmp, 1024); symlink(tmp, dest); chmod(dest, mode); chown(dest, uid, gid); return 0; } static int copy_file(char * source, char * dest, int mode,int uid, int gid) { //fprintf(stderr, "need to copy file %s to %s %x\n", source, dest, mode); int s_fd = open(source, O_RDONLY); if (s_fd < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", source, strerror(errno)); return 1; } int d_fd = open(dest, O_WRONLY | O_CREAT, mode); if (d_fd < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", dest, strerror(errno)); return 1; } ssize_t length; length = lseek(s_fd, 0, SEEK_END); if (length < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", source, strerror(errno)); return 1; } lseek(s_fd, 0, SEEK_SET); //fprintf(stderr, "%d bytes to copy\n", length); char buf[CHUNK_SIZE]; while (length > 0) { ssize_t r = read(s_fd, buf, length < CHUNK_SIZE ? length : CHUNK_SIZE); if (r < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", source, strerror(errno)); return 1; } //fprintf(stderr, "copying %d bytes from %s to %s\n", r, source, dest); ssize_t w = write(d_fd, buf, r); if (w < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", dest, strerror(errno)); return 1; } length -= r; /* Actually should be -w, but let's not get into that now... this should probably use stdio anyway */ //fprintf(stderr, "%d bytes remaining\n", length); } close(s_fd); close(d_fd); if (chown(dest, uid, gid) < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", dest, strerror(errno)); return 1; } return 0; } static int copy_directory(char * source, char * dest, int mode, int uid, int gid) { DIR * dirp = opendir(source); if (dirp == NULL) { fprintf(stderr, "Failed to copy directory %s\n", source); return 1; } //fprintf(stderr, "Creating %s\n", dest); if (!strcmp(dest, "/")) { dest = ""; } else { mkdir(dest, mode); } int ret = 0; struct dirent * ent = readdir(dirp); while (ent != NULL) { if (!strcmp(ent->d_name,".") || !strcmp(ent->d_name,"..")) { //fprintf(stderr, "Skipping %s\n", ent->d_name); ent = readdir(dirp); continue; } //fprintf(stderr, "not skipping %s/%s → %s/%s\n", source, ent->d_name, dest, ent->d_name); char tmp[strlen(source)+strlen(ent->d_name)+2]; sprintf(tmp, "%s/%s", source, ent->d_name); char tmp2[strlen(dest)+strlen(ent->d_name)+2]; sprintf(tmp2, "%s/%s", dest, ent->d_name); //fprintf(stderr,"%s → %s\n", tmp, tmp2); ret |= copy_thing(tmp,tmp2); ent = readdir(dirp); } closedir(dirp); chown(dest, uid, gid); return ret; } static int copy_thing(char * tmp, char * tmp2) { struct stat statbuf; int ret = symlinks ? lstat(tmp, &statbuf) : stat(tmp, &statbuf); if (ret < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", tmp, strerror(errno)); return 1; } if (S_ISLNK(statbuf.st_mode)) { return copy_link(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid); } else if (S_ISDIR(statbuf.st_mode)) { if (!recursive) { fprintf(stderr, APP_NAME ": %s: omitting directory\n", tmp); return 1; } return copy_directory(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid); } else if (S_ISREG(statbuf.st_mode)) { return copy_file(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid); } else { fprintf(stderr, APP_NAME ": %s is not any of the required file types?\n", tmp); return 1; } } #ifndef IS_MV static int copy_top_level(char **argv, int argc, int optind) { char * destination = argv[argc-1]; destination = *destination ? destination : "."; int ret = 0; struct stat statbuf; stat((destination), &statbuf); if (S_ISDIR(statbuf.st_mode)) { while (optind < argc - 1) { char * source = strrchr(argv[optind], '/'); if (!source) source = argv[optind]; char output[4096]; sprintf(output, "%s/%s", destination, source); ret |= copy_thing(argv[optind], output); optind++; } } else { if (optind < argc - 2) { fprintf(stderr, APP_NAME ": target '%s' is not a directory\n", destination); return 1; } ret |= copy_thing(argv[optind], destination); } return ret; } int main(int argc, char ** argv) { int opt; while ((opt = getopt(argc, argv, "RrP")) != -1) { switch (opt) { case 'R': case 'r': recursive = 1; symlinks = 1; break; case 'P': symlinks = 0; break; default: fprintf(stderr, "cp: unrecognized option '%c'\n", opt); break; } } if (optind < argc - 1) { return copy_top_level(argv, argc, optind); } else { fprintf(stderr, "cp: not enough arguments\n"); return 1; } return 0; } #endif ================================================ FILE: apps/cpu-name.krk ================================================ #!/bin/kuroko import fileio let lines let cpus = {0: {}} let current = 0 with fileio.open('/proc/cpuinfo','r') as f: lines = f.readlines() for line in lines: if line == '\n': current++ cpus[current] = {} continue line = line.strip() if ': ' in line: let key, value = line.split(': ') cpus[current][key] = value if cpus and 'Model name' in cpus[0]: print(cpus[0]['Model name']) else if cpus and 'PartNum' in cpus[0]: # ARM let manuf = { 0x00: 'Masked', 0x41: 'ARM', 0x61: 'Apple', } let parts = { 0x000: 'by hypervisor', 0xD02: 'Cortex-A34', 0xD03: 'Cortex-A53', 0xD04: 'Cortex-A35', 0xD05: 'Cortex-A55', 0xD07: 'Cortex-A57', 0xD08: 'Cortex-A72', 0xD09: 'Cortex-A73', # Apple stuff 0x022: 'M1', # Icestorm core 0x023: 'M1', # Firestorm core } print( manuf.get(int(cpus[0]['Implementer'],0),cpus[0]['Implementer']), parts.get(int(cpus[0]['PartNum'],0),cpus[0]['PartNum']) ) ================================================ FILE: apps/cpuwidget.c ================================================ /** * @brief System monitor tool * * Displays CPU usage, memory usage, and network usage, with nice * curvy anti-aliased graphs that scroll smoothly. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int left, top, width, height; static struct menu_bar menu_bar = {0}; static struct menu_bar_entries menu_entries[] = { {"File", "file"}, {"Help", "help"}, {NULL, NULL}, }; static yutani_t * yctx; static yutani_window_t * wina; static gfx_context_t * ctx_base; static gfx_context_t * ctx_cpu; static gfx_context_t * ctx_mem; static gfx_context_t * ctx_net; static int left_pad = 0; static int h_pad = 0; static int top_pad = 19; static int bottom_pad = 34; static int graph_height; static struct TT_Font * tt_thin = NULL; static struct TT_Font * tt_bold = NULL; uint32_t hsv_to_rgb(float h, float s, float v) { float c = v * s; float hp = fmod(h, 2 * M_PI); float x = c * (1.0 - fabs(fmod(hp / 1.0472, 2) - 1.0)); float m = v - c; float rp, gp, bp; if (hp <= 1.0472) { rp = c; gp = x; bp = 0; } else if (hp <= 2.0944) { rp = x; gp = c; bp = 0; } else if (hp <= 3.1416) { rp = 0; gp = c; bp = x; } else if (hp <= 4.1888) { rp = 0; gp = x; bp = c; } else if (hp <= 5.2360) { rp = x; gp = 0; bp = c; } else { rp = c; gp = 0; bp = x; } return rgb((rp + m) * 255, (gp + m) * 255, (bp + m) * 255); } static int should_exit = 0; static clock_t last_redraw = 0; static int cpu_count = 1; static void get_cpu_info(int cpus[]) { FILE * f = fopen("/proc/idle","r"); char buf[4096]; fread(buf, 4096, 1, f); char * buffer = buf; for (int i = 0; i < cpu_count; ++i) { /* pid */ char * a = strchr(buffer, ':'); a++; cpus[i] = strtoul(a, &a, 10); cpus[i] += strtoul(a, &a, 10); cpus[i] += strtoul(a, &a, 10); cpus[i] += strtoul(a, &a, 10); cpus[i] /= 4; if (cpus[i] < 0) cpus[i] = 0; if (cpus[i] > 1000) cpus[i] = 1000; buffer = strchr(a, '\n'); } fclose(f); } static uint32_t colors[32]; static uint32_t if_colors[32]; #define EASE_WIDTH 8 static void plot_graph(gfx_context_t * ctx, size_t scale, long samples[100], uint32_t color, float shift) { float unit_width = (float)ctx->width / 99.0; float factor[EASE_WIDTH]; for (int k = 0; k < EASE_WIDTH; ++k) { factor[k] = (cos(M_PI * ((float)k / (float)(EASE_WIDTH-1))) + 1.0) / 2.0; } struct TT_Contour * contour = NULL; size_t first = 1; for (int j = 1; j < 100; ++j) { if (samples[j-1] == -1) { first++; continue; } float start = (float)ctx->width * (float)(j - 1) / 99.0 + shift; size_t old = samples[j-1]; size_t new = samples[j]; if (old > scale) old = scale; if (new > scale) new = scale; float nsamples[EASE_WIDTH]; for (int k = 0; k < EASE_WIDTH; ++k) { float value = old * factor[k] + new * (1.0 - factor[k]); nsamples[k] = ((scale - value) * ((float)ctx->height - 1) / (float)scale); } if (!contour) { contour = tt_contour_start(start, nsamples[0]); } for (int k = 1; k < EASE_WIDTH; ++k) { contour = tt_contour_line_to(contour, start + unit_width * ((float)k / (float)(EASE_WIDTH-1)), nsamples[k]); } } if (!contour) return; struct TT_Shape * stroke = tt_contour_stroke_shape(contour, 0.5); tt_path_paint(ctx, stroke, color); free(stroke); contour = tt_contour_line_to(contour, ctx->width + shift, ctx->height); contour = tt_contour_line_to(contour, (float)ctx->width * (float)(first - 1) / 99.0 + shift, ctx->height); struct TT_Shape * shape = tt_contour_finish(contour); uint32_t c = premultiply(rgba(_RED(color),_GRE(color),_BLU(color),_ALP(color) * 0.25)); tt_path_paint(ctx, shape, c); free(shape); free(contour); } static long cpu_samples[32][100]; static void draw_lines(gfx_context_t * ctx) { float unit_width = (float)ctx->width / 99.0; for (int i = 1; i < 10; i++) { struct TT_Contour * line = tt_contour_start((int)(unit_width * 10.0 * i) + 0.5, 0); line = tt_contour_line_to(line, (int)(unit_width * 10.0 * i) + 0.5, ctx->height); struct TT_Shape * shape = tt_contour_stroke_shape(line, 0.5); free(line); tt_path_paint(ctx, shape, rgb(150,150,150)); free(shape); } } static void draw_cpu_graphs(gfx_context_t * ctx, float shift) { draw_fill(ctx, rgb(0xF8,0xF8,0xF8)); draw_lines(ctx); for (int i = 0; i < cpu_count; ++i) { plot_graph(ctx, 1000, cpu_samples[i], colors[i], shift); } } static void next_cpu(gfx_context_t * ctx) { int cpus_new[32]; get_cpu_info(cpus_new); for (int i = 0; i < cpu_count; ++i) { memmove(&cpu_samples[i][0], &cpu_samples[i][1], 99 * sizeof(long)); cpu_samples[i][99] = 1000-cpus_new[i]; } draw_cpu_graphs(ctx, 0.0); } static void get_mem_info(int * total, int * used) { FILE * f = fopen("/proc/meminfo", "r"); if (!f) return; int free; char buf[1024] = {0}; fgets(buf, 1024, f); char * a, * b; a = strchr(buf, ' '); a++; b = strchr(a, '\n'); *b = '\0'; *total = atoi(a); fgets(buf, 1024, f); a = strchr(buf, ' '); a++; b = strchr(a, '\n'); *b = '\0'; free = atoi(a); *used = *total - free; fclose(f); } static long mem_samples[100]; static long mem_total; static void draw_mem_graphs(gfx_context_t * ctx, float shift) { draw_fill(ctx, rgb(0xF8,0xF8,0xF8)); draw_lines(ctx); plot_graph(ctx, mem_total, mem_samples, rgb(250,110,240), shift); } static void next_mem(gfx_context_t * ctx) { static int total = 0; static int old_use = 0; int mem_use = 0; get_mem_info(&total, &mem_use); if (!old_use) { old_use = mem_use; return; } memmove(&mem_samples[0], &mem_samples[1], 99 * sizeof(long)); mem_total = total; mem_samples[99] = mem_use; draw_mem_graphs(ctx, 0.0); old_use = mem_use; } static char ifnames[32][256]; static int count_interfaces(void) { int count = 0; DIR * d = opendir("/dev/net"); if (!d) { return 0; } struct dirent * ent; while ((ent = readdir(d))) { if (ent->d_name[0] == '.') continue; snprintf(ifnames[count>>1], 255, ent->d_name); count += 2; } closedir(d); return count; } static void refresh_interfaces(size_t ifs[32]) { int ind = 0; DIR * d = opendir("/dev/net"); if (!d) return; struct dirent * ent; while ((ent = readdir(d))) { if (ent->d_name[0] == '.') continue; char if_path[1024]; snprintf(if_path, 1023, "/dev/net/%s", ent->d_name); int netdev = open(if_path, O_RDONLY); if (netdev < 0) { continue; } netif_counters_t counts; if (!ioctl(netdev, SIOCGIFCOUNTS, &counts)) { ifs[ind + 0] = counts.rx_bytes; ifs[ind + 1] = counts.tx_bytes; ind += 2; } close(netdev); } closedir(d); } static long net_samples[32][100]; static size_t net_scale = 300 * 1024; static void redraw_net_scale(void); static int if_count = -1; static void draw_net_graphs(gfx_context_t * ctx, float shift) { draw_fill(ctx, rgb(0xF8,0xF8,0xF8)); draw_lines(ctx); for (int i = 0; i < if_count; ++i) { plot_graph(ctx, net_scale, net_samples[i], if_colors[i], shift); } } static void next_net(gfx_context_t * ctx) { static size_t old_ifs[32]; static clock_t ticks_last = 0; size_t new_ifs[32]; if (!ticks_last) { ticks_last = times(NULL); refresh_interfaces(old_ifs); return; } clock_t ticks_now = times(NULL); refresh_interfaces(new_ifs); long max = 0; for (int i = 0; i < if_count; ++i) { for (int j = 0; j < 99; ++j) { net_samples[i][j] = net_samples[i][j+1]; if (net_samples[i][j] != -1) { if (net_samples[i][j] > max) { max = net_samples[i][j]; } } } /* Kilobits... */ size_t use = (new_ifs[i] - old_ifs[i]) * 8 / 1024; use *= CLOCKS_PER_SEC; use /= (ticks_now - ticks_last); net_samples[i][99] = use; if ((long)use > max) { max = use; } } size_t scale = max ? max : (300 * 1024); if (scale != net_scale) { net_scale = scale; redraw_net_scale(); } draw_net_graphs(ctx, 0.0); memcpy(old_ifs, new_ifs, sizeof(new_ifs)); ticks_last = ticks_now; } static void draw_legend_element(int which, int count, int index, uint32_t color, char * label) { struct decor_bounds bounds; decor_get_bounds(wina, &bounds); /* Available display width */ int legend_width = ctx_base->width - bounds.width - 40; if (legend_width <= 0) return; /* Calculate graph offset from the usual rule */ int y = MENU_BAR_HEIGHT + bounds.top_height + (which + 1) * (top_pad + graph_height) + which * bottom_pad + 4; /* Space to give to each unit. */ int unit_width = legend_width / count; /* Left offset of this unit */ int unit_x = unit_width * index + bounds.left_width + 10; /* First draw blob */ draw_rounded_rectangle(ctx_base, unit_x, y, 20, 20, 5, color); if (unit_width > 22) { char * label_cropped = tt_ellipsify(label, 12, tt_thin, unit_width - 22, NULL); tt_draw_string(ctx_base, tt_thin, 22 + unit_x, y + 14, label_cropped, rgb(0,0,0)); } } static void draw_legend_cpu(void) { for (int i = 0; i < cpu_count; ++i) { char _cpu_name[] = "CPU "; sprintf(_cpu_name, "CPU %d", i+1); draw_legend_element(0, cpu_count, i, colors[i], _cpu_name); } } static void draw_legend_mem(void) { draw_legend_element(1, 1, 0, rgb(250,110,240), "Memory Usage"); } static void draw_legend_net(void) { for (int i = 0; i < if_count; ++i) { char _net_name[300]; sprintf(_net_name, "%s (%s)", (i & 1) ? "TX" : "RX", ifnames[i>>1]); draw_legend_element(2, if_count, i, if_colors[i], _net_name); } } static int poll_tick = 0; static void redraw_graphs(void) { float shift = -(float)(poll_tick + 1) / (float)(EASE_WIDTH-1) * ctx_cpu->width / 100.0; draw_cpu_graphs(ctx_cpu, shift); draw_mem_graphs(ctx_mem, shift); draw_net_graphs(ctx_net, shift); } static void refresh(clock_t ticks) { if (poll_tick == (EASE_WIDTH-2)) { next_cpu(ctx_cpu); next_mem(ctx_mem); next_net(ctx_net); poll_tick = 0; } else { redraw_graphs(); poll_tick++; } flip(ctx_base); yutani_flip(yctx, wina); last_redraw = ticks; } static void redraw_net_scale(void) { struct decor_bounds bounds; decor_get_bounds(wina, &bounds); tt_set_size(tt_thin, 10); char net_max[100]; snprintf(net_max, 100, "%0.2fmbps", (double)net_scale / 1024.0); int swidth = tt_string_width(tt_thin, net_max) + 2; draw_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)); tt_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)); } void render_base(void) { render_decorations(wina, ctx_base, "System Monitor"); menu_bar_render(&menu_bar, ctx_base); } static void redraw_window_callback(struct menu_bar * self) { (void)self; render_base(); flip(ctx_base); yutani_flip(yctx,wina); } static void initial_stuff(void) { struct decor_bounds bounds; decor_get_bounds(wina, &bounds); graph_height = (height - top_pad * 3 - bottom_pad * 3) / 3; menu_bar.x = bounds.left_width; menu_bar.y = bounds.top_height; menu_bar.width = ctx_base->width - bounds.width; menu_bar.window = wina; draw_fill(ctx_base, rgb(204,204,204)); ctx_cpu = init_graphics_subregion(ctx_base, bounds.left_width + left_pad, MENU_BAR_HEIGHT + bounds.top_height + top_pad, width - h_pad, graph_height); ctx_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); ctx_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); draw_fill(ctx_cpu, rgb(0xF8,0xF8,0xF8)); draw_fill(ctx_mem, rgb(0xF8,0xF8,0xF8)); draw_fill(ctx_net, rgb(0xF8,0xF8,0xF8)); tt_set_size(tt_bold, 13); tt_draw_string(ctx_base, tt_bold, bounds.left_width + 3, MENU_BAR_HEIGHT + bounds.top_height + 14, "CPU", rgb(0,0,0)); tt_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)); tt_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)); tt_set_size(tt_thin, 10); tt_draw_string(ctx_base, tt_thin, bounds.left_width + width - 30, MENU_BAR_HEIGHT + bounds.top_height + 17, "100%", rgb(0,0,0)); tt_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)); char net_max[100]; snprintf(net_max, 100, "%0.2fmbps", (double)net_scale / 1024.0); int swidth = tt_string_width(tt_thin, net_max) + 2; tt_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)); tt_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)); tt_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)); tt_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)); render_base(); draw_legend_cpu(); draw_legend_mem(); draw_legend_net(); } void resize_finish(int w, int h) { if (w < 300) w = 300; if (h < 300) h = 300; free(ctx_cpu); free(ctx_mem); free(ctx_net); yutani_window_resize_accept(yctx, wina, w, h); reinit_graphics_yutani(ctx_base, wina); struct decor_bounds bounds; decor_get_bounds(wina, &bounds); width = w - bounds.left_width - bounds.right_width; height = h - MENU_BAR_HEIGHT - bounds.top_height - bounds.bottom_height; initial_stuff(); redraw_graphs(); flip(ctx_base); yutani_window_resize_done(yctx, wina); } static void _menu_action_exit(struct MenuEntry * entry) { exit(0); } static void _menu_action_help(struct MenuEntry * entry) { system("help-browser systemmonitor.trt &"); render_base(); } static void _menu_action_about(struct MenuEntry * entry) { /* Show About dialog */ char about_cmd[1024] = "\0"; strcat(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\" "); char coords[100]; sprintf(coords, "%d %d &", (int)wina->x + (int)wina->width / 2, (int)wina->y + (int)wina->height / 2); strcat(about_cmd, coords); system(about_cmd); render_base(); } int main (int argc, char ** argv) { left = 100; top = 100; width = 640; height = 480; cpu_count = sysfunc(TOARU_SYS_FUNC_NPROC, NULL); yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]); return 1; } srand(time(NULL)); for (int i = 0; i < cpu_count; ++i) { colors[i] = hsv_to_rgb((float)i / (float)cpu_count * 6.24, 0.9, 0.9); for (int j = 0; j < 100; ++j) { cpu_samples[i][j] = -1; } } init_decorations(); struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); wina = yutani_window_create(yctx, width + bounds.width, height + bounds.height + MENU_BAR_HEIGHT); yutani_window_move(yctx, wina, left, top); yutani_window_advertise_icon(yctx, wina, "System Monitor", "system-monitor"); ctx_base = init_graphics_yutani_double_buffer(wina); menu_bar.entries = menu_entries; menu_bar.redraw_callback = redraw_window_callback; menu_bar.set = menu_set_create(); struct MenuList * m = menu_create(); /* File */ menu_insert(m, menu_create_normal("exit",NULL,"Exit", _menu_action_exit)); menu_set_insert(menu_bar.set, "file", m); m = menu_create(); menu_insert(m, menu_create_normal("help",NULL,"Contents",_menu_action_help)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal("star",NULL,"About System Monitor",_menu_action_about)); menu_set_insert(menu_bar.set, "help", m); tt_thin = tt_font_from_shm("sans-serif"); tt_bold = tt_font_from_shm("sans-serif.bold"); if_count = count_interfaces(); for (int i = 0; i < if_count; ++i) { if_colors[i] = hsv_to_rgb((float)i / (float)(if_count)* 6.24 + 0.2, 0.9, 0.9); for (int j = 0; j < 100; ++j) { net_samples[i][j] = -1; } } for (int i = 0; i < 100; ++i) { mem_samples[i] = -1; } initial_stuff(); refresh(times(NULL)); while (!should_exit) { int fds[1] = {fileno(yctx->sock)}; int index = fswait2(1,fds,20); if (index == 0) { yutani_msg_t * m = yutani_poll(yctx); while (m) { if (menu_process_event(yctx, m)) { /* just decorations should be fine */ render_base(); flip(ctx_base); yutani_flip(yctx, wina); } 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_FOCUS_CHANGE: { struct yutani_msg_window_focus_change * wf = (void*)m->data; yutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)wf->wid); if (win == wina) { win->focused = wf->focused; render_base(); flip(ctx_base); yutani_flip(yctx, wina); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; yutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid); if (win == wina) { int result = decor_handle_event(yctx, m); switch (result) { case DECOR_CLOSE: should_exit = 1; break; case DECOR_RIGHT: /* right click in decoration, show appropriate menu */ decor_show_default_menu(wina, wina->x + me->new_x, wina->y + me->new_y); break; default: /* Other actions */ break; } menu_bar_mouse_event(yctx, wina, &menu_bar, me, me->new_x, me->new_y); } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: should_exit = 1; break; default: break; } free(m); m = yutani_poll_async(yctx); } } clock_t ticks = times(NULL); if (ticks > last_redraw + CLOCKS_PER_SEC/12) { refresh(ticks); } } yutani_close(yctx, wina); return 0; } ================================================ FILE: apps/crc32.c ================================================ /** * crc32 - Simple CRC32 calculator for verifying file integrity. * * @copyright * 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 */ #include #include #include static unsigned int crctab[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; #define RBUF_SIZE 10240 int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "usage: %s FILE\n", argv[0]); return 1; } FILE * f = fopen(argv[1], "r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], strerror(errno)); return 1; } char buf[RBUF_SIZE]; unsigned int crc32 = 0xffffffff; while (!feof(f)) { size_t r = fread(buf, 1, RBUF_SIZE, f); if (r == 0 && ferror(f)) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], strerror(errno)); return 1; } for (size_t i = 0; i < r; ++i) { int ind = (crc32 ^ buf[i]) & 0xFF; crc32 = (crc32 >> 8) ^ (crctab[ind]); } } crc32 ^= 0xffffffff; fprintf(stdout, "%8x\n", (unsigned int)crc32); return 0; } ================================================ FILE: apps/cursor-off.c ================================================ /** * @brief 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. * * @copyright * 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 #include #include int main(int argc, char * argv[]) { int fd = open("/dev/port", O_RDWR); if (fd < 0) return 1; pwrite(fd, (unsigned char[]){14}, 1, 0x3D4); pwrite(fd, (unsigned char[]){0xFF}, 1, 0x3D5); pwrite(fd, (unsigned char[]){15}, 1, 0x3D4); pwrite(fd, (unsigned char[]){0xFF}, 1, 0x3D5); return 0; } ================================================ FILE: apps/date.c ================================================ /** * 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. * * @copyright * 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 */ #include #include #include #include #include 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 digits(const char * s, int len) { for (int i = 0; i < len; ++i) { if (s[i] < '0' || s[i] > '9') return 0; } return 1; } int mmddhhmm(struct tm * tm, const char * str) { int month = (str[0]-'0') * 10 + (str[1]-'0'); int day = (str[2]-'0') * 10 + (str[3]-'0'); int hour = (str[4]-'0') * 10 + (str[5]-'0'); int min = (str[6]-'0') * 10 + (str[7]-'0'); if (month < 1 || month > 12) return 0; if (day < 1 || day > 31) return 0; if (hour < 0 || hour > 23) return 0; if (min < 0 || min > 59) return 0; tm->tm_mon = month - 1; tm->tm_mday = day; tm->tm_hour = hour; tm->tm_min = min; return 1; } int ddyy(struct tm * tm, const char * str) { int year = (str[0]-'0') * 1000 + (str[1]-'0') * 100 + (str[2]-'0') * 10 + (str[3]-'0'); tm->tm_year = year - 1900; return 1; } int secs(struct tm * tm, const char * str) { int sec = (str[0]-'0') * 10 + (str[1]-'0'); if (sec < 0 || sec > 59) return 0; tm->tm_sec = sec; return 1; } int main(int argc, char * argv[]) { char * format = "%a %d %b %Y %T %Z"; 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; } } gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); if (optind < argc && *argv[optind] == '+') { format = &argv[optind][1]; } else if (optind < argc) { int len =strlen(argv[optind]); if (len == 8) { if (!digits(argv[optind], 8)) goto _invalid; if (!mmddhhmm(timeinfo, argv[optind])) goto _invalid; goto set_time; } else if (len == 11) { if (argv[optind][8] != '.') goto _invalid; if (!digits(argv[optind], 8) || !digits(argv[optind]+9,2)) goto _invalid; if (!mmddhhmm(timeinfo, argv[optind])) goto _invalid; if (!secs(timeinfo, argv[optind]+9)) goto _invalid; goto set_time; } else if (len == 12) { if (!digits(argv[optind], 12)) goto _invalid; if (!mmddhhmm(timeinfo, argv[optind])) goto _invalid; if (!ddyy(timeinfo, argv[optind]+8)) goto _invalid; goto set_time; } else if (len == 15) { if (argv[optind][12] != '.') goto _invalid; if (!digits(argv[optind], 12) || !digits(argv[optind]+13,2)) goto _invalid; if (!mmddhhmm(timeinfo, argv[optind])) goto _invalid; if (!ddyy(timeinfo, argv[optind]+8)) goto _invalid; if (!secs(timeinfo, argv[optind]+13)) goto _invalid; goto set_time; } _invalid: fprintf(stderr, "date: only 'MMDDhhmm', 'MMDDhhmm.ss', 'MMDDhhmmCCYY' and 'MMDDhhmmCCYY.ss' are supported for setting time.\n"); return 1; set_time: now.tv_usec = 0; now.tv_sec = mktime(timeinfo); return settimeofday(&now, NULL); } strftime(buf,BUFSIZ,format,timeinfo); puts(buf); return 0; } ================================================ FILE: apps/dbg.c ================================================ /** * @brief Debugger. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char * last_command = NULL; static char * binary_path = NULL; static FILE * binary_obj = NULL; static pid_t binary_pid = 0; static int binary_is_child = 0; static void dump_regs(struct URegs * r) { fprintf(stdout, UREGS_FMT, UREGS_ARGS(r)); } #define M(e) [e] = #e const char * signal_names[256] = { M(SIGHUP), M(SIGINT), M(SIGQUIT), M(SIGILL), M(SIGTRAP), M(SIGABRT), M(SIGEMT), M(SIGFPE), M(SIGKILL), M(SIGBUS), M(SIGSEGV), M(SIGSYS), M(SIGPIPE), M(SIGALRM), M(SIGTERM), M(SIGUSR1), M(SIGUSR2), M(SIGCHLD), M(SIGPWR), M(SIGWINCH), M(SIGURG), M(SIGPOLL), M(SIGSTOP), M(SIGTSTP), M(SIGCONT), M(SIGTTIN), M(SIGTTOUT), M(SIGVTALRM), M(SIGPROF), M(SIGXCPU), M(SIGXFSZ), M(SIGWAITING), M(SIGDIAF), M(SIGHATE), M(SIGWINEVENT), M(SIGCAT), }; static int data_read_bytes(pid_t pid, uintptr_t addr, char * buf, size_t size) { for (unsigned int i = 0; i < size; ++i) { if (ptrace(PTRACE_PEEKDATA, pid, (void*)addr++, &buf[i])) { return 1; } } return 0; } static int data_read_int(pid_t pid, uintptr_t addr) { int x; data_read_bytes(pid, addr, (char*)&x, sizeof(int)); return x; } static uintptr_t data_read_ptr(pid_t pid, uintptr_t addr) { uintptr_t x; data_read_bytes(pid, addr, (char*)&x, sizeof(uintptr_t)); return x; } static void string_arg(pid_t pid, uintptr_t ptr, size_t maxsize) { FILE * logfile = stdout; if (ptr == 0) { fprintf(logfile, "NULL"); return; } fprintf(logfile, "\""); size_t size = 0; uint8_t buf = 0; do { long result = ptrace(PTRACE_PEEKDATA, pid, (void*)ptr, &buf); if (result != 0) break; if (!buf) { fprintf(logfile, "\""); return; } if (buf == '\\') fprintf(logfile, "\\\\"); else if (buf == '"') fprintf(logfile, "\\\""); else if (buf >= ' ' && buf < '~') fprintf(logfile, "%c", buf); else if (buf == '\r') fprintf(logfile, "\\r"); else if (buf == '\n') fprintf(logfile, "\\n"); else fprintf(logfile, "\\x%02x", buf); ptr++; size++; if (size > maxsize) break; } while (buf); fprintf(logfile, "\"..."); } extern uintptr_t __ld_symbol_table(void); extern uintptr_t __ld_objects_table(void); static char * read_string(pid_t pid, uintptr_t ptr) { if (!ptr) return strdup("(null)"); size_t len = 0; uint8_t buf = 0; while ((data_read_bytes(pid, ptr + len, (char*)&buf, 1), buf)) len++; char * out = malloc(len + 1); data_read_bytes(pid, ptr, out, len+1); return out; } typedef struct elf_object { FILE * file; Elf64_Header header; char * dyn_string_table; size_t dyn_string_table_size; Elf64_Sym * dyn_symbol_table; size_t dyn_symbol_table_size; Elf64_Dyn * dynamic; Elf64_Word * dyn_hash; void (*init)(void); void (**init_array)(void); size_t init_array_size; uintptr_t base; list_t * dependencies; int loaded; } elf_t; static int find_symbol(pid_t pid, uintptr_t addr_in, char ** name, uintptr_t *addr_out, char ** objname) { intptr_t current_max = INTPTR_MAX; uintptr_t current_addr = (uintptr_t)NULL; uintptr_t current_xname = (uintptr_t)NULL; char * current_name = NULL; char * current_obj = NULL; uintptr_t best_base = 0; /* Can we cheat and peek at ld.so? */ uintptr_t their_symbol_table = data_read_ptr(pid, __ld_symbol_table()); if (their_symbol_table) { hashmap_t map; data_read_bytes(pid, their_symbol_table, (char*)&map, sizeof(hashmap_t)); /* Cool, now let's look at every entry... */ for (size_t i = 0; i < map.size; ++i) { uintptr_t ptr; hashmap_entry_t entry; data_read_bytes(pid, (uintptr_t)map.entries + sizeof(uintptr_t) * i, (char*)&ptr, sizeof(uintptr_t)); int j = 0; while (ptr) { data_read_bytes(pid, ptr, (char*)&entry, sizeof(hashmap_entry_t)); if (entry.value && addr_in >= (uintptr_t)entry.value) { intptr_t x = addr_in - (uintptr_t)entry.value; if (x < current_max) { current_max = x; current_addr = (uintptr_t)entry.value; current_xname = (uintptr_t)entry.key; } } ptr = (uintptr_t)entry.next; j++; } } if (current_xname) { current_name = read_string(pid, current_xname); } } if (addr_in < 0x40000000) { current_obj = strdup("ld.so"); } /* Figure out where this object is in the objects map */ uintptr_t their_objects_table = data_read_ptr(pid, __ld_objects_table()); if (!current_obj && their_objects_table) { hashmap_t map; data_read_bytes(pid, their_objects_table, (char*)&map, sizeof(hashmap_t)); intptr_t cmax = INTPTR_MAX; uintptr_t best_name = 0; for (size_t i = 0; i < map.size; ++i) { uintptr_t ptr; hashmap_entry_t entry; data_read_bytes(pid, (uintptr_t)map.entries + sizeof(uintptr_t) * i, (char*)&ptr, sizeof(uintptr_t)); while (ptr) { data_read_bytes(pid, ptr, (char*)&entry, sizeof(hashmap_entry_t)); if (entry.value) { elf_t obj; data_read_bytes(pid, (uintptr_t)entry.value, (char*)&obj, sizeof(elf_t)); if (addr_in >= obj.base) { intptr_t x = addr_in - (uintptr_t)obj.base; if (x < cmax) { cmax = x; best_name = (uintptr_t)entry.key; best_base = obj.base; } } } ptr = (uintptr_t)entry.next; } } if (best_name) { current_obj = read_string(pid, best_name); } } FILE * f = binary_obj; if (current_obj) { /* Try to open that */ struct stat stat_buf; char path[1024]; sprintf(path, "/lib/%s", current_obj); if (stat(path, &stat_buf)) { sprintf(path, "/usr/lib/%s", current_obj); if (stat(path, &stat_buf)) goto _bail; } f = fopen(path, "r"); } else { _bail: current_obj = strdup(binary_path); best_base = 0; } fseek(f, 0, SEEK_SET); Elf64_Header header; fread(&header, sizeof(Elf64_Header), 1, f); for (unsigned int i = 0; i < header.e_shnum; ++i) { fseek(f, header.e_shoff + header.e_shentsize * i, SEEK_SET); Elf64_Shdr sectionHeader; fread(§ionHeader, sizeof(Elf64_Shdr), 1, f); switch (sectionHeader.sh_type) { case SHT_SYMTAB: case SHT_DYNSYM: { /* Try to get the actual one if possible */ Elf64_Sym * symtab = malloc(sectionHeader.sh_size); if (sectionHeader.sh_addr > 0x40000000) { data_read_bytes(pid, sectionHeader.sh_addr, (char*)symtab, sectionHeader.sh_size); } else { fseek(f, sectionHeader.sh_offset, SEEK_SET); fread(symtab, sectionHeader.sh_size, 1, f); } Elf64_Shdr shdr_strtab; fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET); fread(&shdr_strtab, sizeof(Elf64_Shdr), 1, f); char * strtab = malloc(shdr_strtab.sh_size); fseek(f, shdr_strtab.sh_offset, SEEK_SET); fread(strtab, shdr_strtab.sh_size, 1, f); for (unsigned int i = 0; i < sectionHeader.sh_size / sizeof(Elf64_Sym); ++i) { if (!symtab[i].st_value) continue; if ((symtab[i].st_info & 0xF) == STT_SECTION) continue; if ((symtab[i].st_info & 0xF) == STT_NOTYPE) continue; if (addr_in >= ((uintptr_t)symtab[i].st_value + best_base)) { intptr_t x = addr_in - ((uintptr_t)symtab[i].st_value + best_base); if (x < current_max) { if (current_name) free(current_name); current_max = x; current_addr = symtab[i].st_value + best_base; current_name = strdup(strtab + symtab[i].st_name); } } } free(strtab); free(symtab); } break; } } *addr_out = current_addr; *name = current_name; *objname = current_obj; if (current_name) return 1; if (f != binary_obj) fclose(f); return 0; } static void show_libs(pid_t pid) { hashmap_t map; uintptr_t their_objects_table = data_read_ptr(pid, __ld_objects_table()); data_read_bytes(pid, their_objects_table, (char*)&map, sizeof(hashmap_t)); for (size_t i = 0; i < map.size; ++i) { uintptr_t ptr; hashmap_entry_t entry; data_read_bytes(pid, (uintptr_t)map.entries + sizeof(uintptr_t) * i, (char*)&ptr, sizeof(uintptr_t)); while (ptr) { data_read_bytes(pid, ptr, (char*)&entry, sizeof(hashmap_entry_t)); if (entry.value) { elf_t obj; data_read_bytes(pid, (uintptr_t)entry.value, (char*)&obj, sizeof(elf_t)); char * s = read_string(pid, (uintptr_t)entry.key); fprintf(stderr, "%s @ %#zx\n", s, (uintptr_t)obj.base); } ptr = (uintptr_t)entry.next; } } } static void attempt_backtrace(pid_t pid, struct URegs * regs) { /* We already printed the top, now let's try to dig down */ uintptr_t ip = uregs_ip(regs); uintptr_t bp = uregs_bp(regs); int depth = 0; int max_depth = 20; while (bp && ip && depth < max_depth && ip < 0xFFFfff0000000000UL) { char * name = NULL; char * objname = NULL; uintptr_t addr = 0; if (find_symbol(pid, ip - 1, &name, &addr, &objname)) { fprintf(stderr, "<0x%016zx> %s+%#zx in %s\n", ip, name, ip - addr, objname); free(name); free(objname); } ip = data_read_ptr(pid, bp + sizeof(uintptr_t)); bp = data_read_ptr(pid, bp); depth++; } } static int imatch(const char * a, const char * b) { do { if (!*a && !*b) return 1; if (tolower(*a) != tolower(*b)) return 0; a++; b++; } while (1); } static int signal_from_string(const char * str) { if (isdigit(*str)) { return strtoul(str,NULL,0); } else if (str[0] == 'S' && str[1] == 'I' && str[2] == 'G') { for (int i = 0; i < 256; ++i) { if (signal_names[i] && imatch(signal_names[i], str)) return i; } return -1; } else { for (int i = 0; i < 256; ++i) { if (signal_names[i] && imatch(signal_names[i]+3, str)) return i; } return -1; } return -1; } static void show_commandline(pid_t pid, int status, struct URegs * regs) { fprintf(stderr, "[Process %d, ip=%#zx]\n", pid, uregs_ip(regs)); /* Try to figure out what symbol that is */ char * name = NULL; char * objname = NULL; uintptr_t addr = 0; if (find_symbol(pid, uregs_ip(regs), &name, &addr, &objname)) { fprintf(stderr, " %s+%zx in %s\n", name, uregs_ip(regs) - addr, objname); free(name); free(objname); } while (1) { char buf[4096] = {0}; rline_exit_string = ""; rline_exp_set_prompts("(dbg) ", "", 6, 0); rline_exp_set_syntax("dbg"); rline_exp_set_tab_complete_func(NULL); /* TODO */ if (rline(buf, 4096) == 0) goto _exitDebugger; char *nl = strstr(buf, "\n"); if (nl) *nl = '\0'; if (!strlen(buf)) { if (last_command) { strcpy(buf, last_command); } else { continue; } } else { rline_history_insert(strdup(buf)); rline_scroll = 0; if (last_command) free(last_command); last_command = strdup(buf); } /* Tokenize just the first command */ char * arg = NULL; char * sp = strstr(buf, " "); if (sp) { *sp = '\0'; arg = sp + 1; } if (!strcmp(buf, "show")) { if (!arg) { fprintf(stderr, "Things that can be shown:\n"); fprintf(stderr, " regs\n"); fprintf(stderr, " libs\n"); continue; } if (!strcmp(arg, "regs")) { dump_regs(regs); } else if (!strcmp(arg, "libs")) { show_libs(pid); } else { fprintf(stderr, "Don't know how to show '%s'\n", arg); } } else if (!strcmp(buf, "bt") || !strcmp(buf, "backtrace")) { attempt_backtrace(pid, regs); } else if (!strcmp(buf, "continue") || !strcmp(buf,"c")) { int signum = WSTOPSIG(status); if (signum == SIGINT) signum = 0; ptrace(PTRACE_CONT, pid, NULL, (void*)(uintptr_t)signum); return; } else if (!strcmp(buf, "signal")) { if (!arg) { fprintf(stderr, "'signal' needs an argument\n"); continue; } int signum = signal_from_string(arg); if (signum == -1) { fprintf(stderr, "'%s' is not a recognized signal\n", arg); continue; } ptrace(PTRACE_CONT, pid, NULL, (void*)(uintptr_t)signum); return; } else if (!strcmp(buf, "step") || !strcmp(buf,"s")) { int signum = WSTOPSIG(status); if (signum == SIGINT) signum = 0; ptrace(PTRACE_SINGLESTEP, pid, NULL, (void*)(uintptr_t)signum); return; } else if (!strcmp(buf, "poke")) { char * addr = arg; char * data = strstr(addr, " "); if (!data) { fprintf(stderr, "usage: poke addr byte\n"); continue; } *data = '\0'; data++; uintptr_t addr_ = strtoul(addr, NULL, 0); uintptr_t data_ = strtoul(data, NULL, 0); if (ptrace(PTRACE_POKEDATA, pid, (void*)addr_, (void*)&data_) != 0) { fprintf(stderr, "poke: %s\n", strerror(errno)); continue; } } else if (!strcmp(buf, "print") || !strcmp(buf,"p")) { char * fmt = arg; char * sp = strstr(arg, " "); if (!sp) { fprintf(stderr, "usage: print fmt addr\n"); continue; } *sp = '\0'; sp++; uintptr_t addr = strtoul(sp,NULL,0); /* Parse any leading numbers */ int count = 1; if (*fmt >= '1' && *fmt <= '9') { count = (*fmt - '0'); fmt++; while (*fmt >= '0' && *fmt <= '9') { count *= 10; count += (*fmt - '0'); fmt++; } } /* Parse the format */ for (int i = 0; i < count; ++i) { if (!strcmp(fmt, "x")) { uint8_t buf[1]; data_read_bytes(pid, addr, (char*)buf, 1); printf("%02x", buf[0]); addr += 1; } else if (!strcmp(fmt, "i")) { printf("%d", data_read_int(pid,addr)); addr += sizeof(int); } else if (!strcmp(fmt, "l")) { printf("%ld", (intptr_t)data_read_ptr(pid,addr)); addr += sizeof(long); } else if (!strcmp(fmt, "p")) { printf("%#zx", data_read_ptr(pid,addr)); addr += sizeof(uintptr_t); } else if (!strcmp(fmt, "s")) { string_arg(pid,addr,count == 1 ? 30 : count); break; } else { printf("print: invalid format string"); break; } if (i + 1 < count) { printf(" "); } } printf("\n"); } else if (!strcmp(buf, "help")) { printf("commands:\n" " show (regs, libs)\n" " backtrace\n" " continue\n" " signal signum\n" " step\n" " poke addr byte\n" " print fmt addr\n"); continue; } else { fprintf(stderr, "dbg: unrecognized command '%s'\n", buf); continue; } } _exitDebugger: if (binary_is_child) { fprintf(stderr, "Terminating child process '%d'.\n", pid); ptrace(PTRACE_DETACH, pid, NULL, (void*)(uintptr_t)SIGKILL); } exit(0); } static int usage(char * argv[]) { #define T_I "\033[3m" #define T_O "\033[0m" fprintf(stderr, "usage: %s command...\n" " -h " T_I "Show this help text." T_O "\n", argv[0]); return 1; } #define DEFAULT_PATH "/bin:/usr/bin" static char * find_binary(const char * file) { if (file && (!strstr(file, "/"))) { char * path = getenv("PATH"); if (!path) { path = DEFAULT_PATH; } char * xpath = strdup(path); char * p, * last; for ((p = strtok_r(xpath, ":", &last)); p; p = strtok_r(NULL, ":", &last)) { int r; struct stat stat_buf; char * exe = malloc(strlen(p) + strlen(file) + 2); strcpy(exe, p); strcat(exe, "/"); strcat(exe, file); r = stat(exe, &stat_buf); if (r != 0) { continue; } if (!(stat_buf.st_mode & 0111)) { continue; } free(xpath); return exe; } free(xpath); return NULL; } else if (file) { return strdup(file); } return NULL; } static char * sig_to_str(int signum) { static char _buf[100]; if (signum >= 0 && signum <= 255) { char * maybe = (char*)signal_names[signum]; if (maybe) { return maybe; } } sprintf(_buf, "%d", signum); return _buf; } static void pass_sig(int sig) { kill(binary_pid, sig); signal(SIGINT, pass_sig); } int main(int argc, char * argv[]) { pid_t target_pid = 0; int opt; while ((opt = getopt(argc, argv, "o:p:h")) != -1) { switch (opt) { case 'p': target_pid = atoi(optarg); break; case 'h': return (usage(argv), 0); case '?': return usage(argv); } } if (optind == argc) { return usage(argv); } /* TODO find argv[optind] */ /* TODO load symbols from it, and from its dependencies... with offsets... from ld.so... */ binary_path = find_binary(argv[optind]); if (!binary_path) { fprintf(stderr, "%s: %s: No such file or not an executable.\n", argv[0], argv[optind]); return 1; } binary_obj = fopen(binary_path, "r"); if (!binary_obj) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); return 1; } /* Catch one potential mistake early... */ struct stat stat_buf; fstat(fileno(binary_obj), &stat_buf); if (stat_buf.st_mode & S_IFDIR) { fprintf(stderr, "%s: %s: Is a directory\n", argv[0], binary_path); return 1; } /* Attempt to load symbol information... */ if (target_pid) { binary_pid = target_pid; if (ptrace(PTRACE_ATTACH, binary_pid, NULL, NULL) < 0) { fprintf(stderr, "%s: ptrace: %s\n", argv[0], strerror(errno)); return 1; } signal(SIGINT, pass_sig); } else { binary_is_child = 1; binary_pid = fork(); if (!binary_pid) { if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) { fprintf(stderr, "%s: ptrace: %s\n", argv[0], strerror(errno)); return 1; } execv(binary_path, &argv[optind]); /* If we got to this point at all... */ fprintf(stderr, "%s: %s: execv: %s\n", argv[0], binary_path, strerror(errno)); return 1; } signal(SIGINT, SIG_IGN); } while (1) { int status = 0; pid_t res = waitpid(binary_pid, &status, WSTOPPED); if (res == 0) continue; if (res < 0) { if (errno == EINTR) continue; fprintf(stderr, "%s: waitpid: %s\n", argv[0], strerror(errno)); } else { if (WIFSTOPPED(status)) { if (WSTOPSIG(status) == SIGTRAP) { /* Don't care about TRAP right now */ int event = (status >> 16) & 0xFF; switch (event) { case PTRACE_EVENT_SINGLESTEP: { struct URegs regs; ptrace(PTRACE_GETREGS, res, NULL, ®s); show_commandline(res, status, ®s); } break; default: //ptrace(PTRACE_SIGNALS_ONLY_PLZ, p, NULL, NULL); ptrace(PTRACE_CONT, res, NULL, NULL); break; } } else { printf("Program received signal %s.\n", sig_to_str(WSTOPSIG(status))); struct URegs regs; ptrace(PTRACE_GETREGS, res, NULL, ®s); show_commandline(res, status, ®s); } } else if (WIFSIGNALED(status)) { fprintf(stderr, "Process %d was killed by %s.\n", res, signal_names[WTERMSIG(status)]); return 0; } else if (WIFEXITED(status)) { fprintf(stderr, "Process %d exited normally with status %d.\n", res, WEXITSTATUS(status)); return 0; } else { fprintf(stderr, "Unknown state?\n"); } } } return 0; } ================================================ FILE: apps/demo.c ================================================ /** * @brief Simple executable that was used during initial testing of Misaka. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include static sprite_t wallpaper; static struct TT_Font * _tt_font_thin; static struct tm * timeinfo; static gfx_context_t * ctx; static void redraw(void) { draw_sprite(ctx, &wallpaper, 0, 0); struct utsname u; uname(&u); char string[1024]; snprintf(string, 1024, "ToaruOS %s %s %s", u.release, u.version, u.machine); tt_draw_string_shadow(ctx, _tt_font_thin, string, 15, 30, 30, rgb(255,255,255), rgb(0,0,0), 4); strftime(string,1024,"%a %d %b %Y %T %Z",timeinfo); tt_draw_string_shadow(ctx, _tt_font_thin, string, 15, 30, 60, rgb(255,255,255), rgb(0,0,0), 4); flip(ctx); } int main(int argc, char * argv[]) { fprintf(stderr, "open() = %ld\n", syscall_open("/dev/null", 0, 0)); fprintf(stderr, "open() = %ld\n", syscall_open("/dev/null", 1, 0)); fprintf(stderr, "open() = %ld\n", syscall_open("/dev/null", 1, 0)); ctx = init_graphics_fullscreen_double_buffer(); draw_fill(ctx, rgb(120,120,120)); flip(ctx); _tt_font_thin = tt_font_from_file("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); load_sprite(&wallpaper, "/usr/share/wallpaper.jpg"); draw_fill(ctx, rgb(0,0,0)); flip(ctx); struct timeval now; gettimeofday(&now, NULL); int forked = 0; while (1) { time_t last = now.tv_sec; timeinfo = localtime(&last); redraw(); while (1) { gettimeofday(&now, NULL); if (now.tv_sec != last) break; } if (!forked) { forked = 1; system("uname -a"); } } return 0; } ================================================ FILE: apps/demo.krk ================================================ #!/bin/kuroko import os, kuroko print('Kuroko',kuroko.version, kuroko.buildenv, kuroko.builddate) print('Running on:',os.uname()) ================================================ FILE: apps/dhclient.c ================================================ /** * @brief DHCP client * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include struct ethernet_packet { uint8_t destination[6]; uint8_t source[6]; uint16_t type; uint8_t payload[]; } __attribute__((packed)) __attribute__((aligned(2))); struct ipv4_packet { uint8_t version_ihl; uint8_t dscp_ecn; uint16_t length; uint16_t ident; uint16_t flags_fragment; uint8_t ttl; uint8_t protocol; uint16_t checksum; uint32_t source; uint32_t destination; uint8_t payload[]; } __attribute__ ((packed)) __attribute__((aligned(2))); struct udp_packet { uint16_t source_port; uint16_t destination_port; uint16_t length; uint16_t checksum; uint8_t payload[]; } __attribute__ ((packed)) __attribute__((aligned(2))); struct dhcp_packet { uint8_t op; uint8_t htype; uint8_t hlen; uint8_t hops; uint32_t xid; uint16_t secs; uint16_t flags; uint32_t ciaddr; uint32_t yiaddr; uint32_t siaddr; uint32_t giaddr; uint8_t chaddr[16]; uint8_t sname[64]; uint8_t file[128]; uint32_t magic; uint8_t options[]; } __attribute__ ((packed)) __attribute__((aligned(2))); struct dns_packet { uint16_t qid; uint16_t flags; uint16_t questions; uint16_t answers; uint16_t authorities; uint16_t additional; uint8_t data[]; } __attribute__ ((packed)) __attribute__((aligned(2))); struct tcp_header { uint16_t source_port; uint16_t destination_port; uint32_t seq_number; uint32_t ack_number; uint16_t flags; uint16_t window_size; uint16_t checksum; uint16_t urgent; uint8_t payload[]; } __attribute__((packed)) __attribute__((aligned(2))); struct tcp_check_header { uint32_t source; uint32_t destination; uint8_t zeros; uint8_t protocol; uint16_t tcp_len; uint8_t tcp_header[]; }; #define SOCK_STREAM 1 #define SOCK_DGRAM 2 // Note: Data offset is in upper 4 bits of flags field. Shift and subtract 5 since that is the min TCP size. // If the value is more than 5, multiply by 4 because this field is specified in number of words #define TCP_OPTIONS_LENGTH(tcp) (((((tcp)->flags) >> 12) - 5) * 4) #define TCP_HEADER_LENGTH(tcp) ((((tcp)->flags) >> 12) * 4) #define TCP_HEADER_LENGTH_FLIPPED(tcp) (((htons((tcp)->flags)) >> 12) * 4) #define htonl(l) ( (((l) & 0xFF) << 24) | (((l) & 0xFF00) << 8) | (((l) & 0xFF0000) >> 8) | (((l) & 0xFF000000) >> 24)) #define htons(s) ( (((s) & 0xFF) << 8) | (((s) & 0xFF00) >> 8) ) #define ntohl(l) htonl((l)) #define ntohs(s) htons((s)) #define BROADCAST_MAC {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define IPV4_PROT_UDP 17 #define IPV4_PROT_TCP 6 #define DHCP_MAGIC 0x63825363 #define TCP_FLAGS_FIN (1 << 0) #define TCP_FLAGS_SYN (1 << 1) #define TCP_FLAGS_RES (1 << 2) #define TCP_FLAGS_PSH (1 << 3) #define TCP_FLAGS_ACK (1 << 4) #define TCP_FLAGS_URG (1 << 5) #define TCP_FLAGS_ECE (1 << 6) #define TCP_FLAGS_CWR (1 << 7) #define TCP_FLAGS_NS (1 << 8) #define DATA_OFFSET_5 (0x5 << 12) #define ETHERNET_TYPE_IPV4 0x0800 #define ETHERNET_TYPE_ARP 0x0806 struct payload { struct ethernet_packet eth_header; struct ipv4_packet ip_header; struct udp_packet udp_header; struct dhcp_packet dhcp_header; uint8_t payload[32]; }; static void ip_ntoa(const uint32_t src_addr, char * out) { snprintf(out, 16, "%d.%d.%d.%d", (src_addr & 0xFF000000) >> 24, (src_addr & 0xFF0000) >> 16, (src_addr & 0xFF00) >> 8, (src_addr & 0xFF)); } uint16_t calculate_ipv4_checksum(struct ipv4_packet * p) { uint32_t sum = 0; uint16_t * s = (uint16_t *)p; /* TODO: Checksums for options? */ for (int i = 0; i < 10; ++i) { sum += ntohs(s[i]); } if (sum > 0xFFFF) { sum = (sum >> 16) + (sum & 0xFFFF); } return ~(sum & 0xFFFF) & 0xFFFF; } uint8_t mac_addr[6]; uint32_t xid = 0x1337; void fill(struct payload *it, size_t payload_size) { it->eth_header.source[0] = mac_addr[0]; it->eth_header.source[1] = mac_addr[1]; it->eth_header.source[2] = mac_addr[2]; it->eth_header.source[3] = mac_addr[3]; it->eth_header.source[4] = mac_addr[4]; it->eth_header.source[5] = mac_addr[5]; it->eth_header.destination[0] = 0xFF; it->eth_header.destination[1] = 0xFF; it->eth_header.destination[2] = 0xFF; it->eth_header.destination[3] = 0xFF; it->eth_header.destination[4] = 0xFF; it->eth_header.destination[5] = 0xFF; it->eth_header.type = htons(0x0800); it->ip_header.version_ihl = ((0x4 << 4) | (0x5 << 0)); it->ip_header.dscp_ecn = 0; it->ip_header.length = htons(sizeof(struct ipv4_packet) + sizeof(struct udp_packet) + sizeof(struct dhcp_packet) + payload_size); it->ip_header.ident = htons(1); it->ip_header.flags_fragment = 0; it->ip_header.ttl = 0x40; it->ip_header.protocol = IPV4_PROT_UDP; it->ip_header.checksum = 0; it->ip_header.source = htonl(0); it->ip_header.destination = htonl(0xFFFFFFFF); it->ip_header.checksum = htons(calculate_ipv4_checksum(&it->ip_header)); it->udp_header.source_port = htons(68); it->udp_header.destination_port = htons(67); it->udp_header.length = htons(sizeof(struct udp_packet) + sizeof(struct dhcp_packet) + payload_size); it->udp_header.checksum = 0; /* uh */ it->dhcp_header.op = 1; it->dhcp_header.htype = 1; it->dhcp_header.hlen = 6; it->dhcp_header.hops = 0; it->dhcp_header.xid = htonl(xid); /* transaction id... */ it->dhcp_header.secs = 0; it->dhcp_header.flags = 0; it->dhcp_header.ciaddr = 0; it->dhcp_header.yiaddr = 0; it->dhcp_header.siaddr = 0; it->dhcp_header.giaddr = 0; it->dhcp_header.chaddr[0] = mac_addr[0]; it->dhcp_header.chaddr[1] = mac_addr[1]; it->dhcp_header.chaddr[2] = mac_addr[2]; it->dhcp_header.chaddr[3] = mac_addr[3]; it->dhcp_header.chaddr[4] = mac_addr[4]; it->dhcp_header.chaddr[5] = mac_addr[5]; it->dhcp_header.magic = htonl(DHCP_MAGIC); } static void time_diff(struct timeval *start, struct timeval *end, time_t *sec_diff, suseconds_t *usec_diff) { *sec_diff = end->tv_sec - start->tv_sec; *usec_diff = end->tv_usec - start->tv_usec; if (end->tv_usec < start->tv_usec) { *sec_diff -= 1; *usec_diff = (1000000 + end->tv_usec) - start->tv_usec; } } extern char * _argv_0; static int configure_interface(const char * if_name) { /* Open a raw socket. */ int sock = socket(AF_RAW, SOCK_RAW, 0); if (sock < 0) { perror(_argv_0); return 1; } /* Bind to this interface */ if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, if_name, strlen(if_name)+1)) { perror(_argv_0); return 1; } /* Request the mac address */ char if_path[100]; snprintf(if_path, 100, "/dev/net/%s", if_name); int netdev = open(if_path, O_RDWR); if (netdev < 0) { perror(_argv_0); return 1; } int res = ioctl(netdev, SIOCGIFHWADDR, &mac_addr); if (res == 1) return 0; /* Loopback, skip */ if (res) { fprintf(stderr, "%s: %s: could not get mac address\n", _argv_0, if_name); return 1; } xid = rand(); /* Try to frob the whatsit */ { struct payload thething = { .payload = {53,1,1,55,2,3,6,255,0} }; fill(&thething, 8); send(sock, &thething, sizeof(struct payload), 0); } uint32_t yiaddr; int stage = 1; struct timeval start, end; time_t sec_diff; suseconds_t usec_diff; gettimeofday(&start, NULL); static uint8_t eth_broadcast[6] = {255,255,255,255,255,255}; do { char buf[4096] = {0}; gettimeofday(&end, NULL); time_diff(&start,&end,&sec_diff,&usec_diff); if (sec_diff > 2) { close(netdev); return 1; } struct pollfd fds[1]; fds[0].fd = sock; fds[0].events = POLLIN; int ret = poll(fds,1,200); if (ret == 0) { continue; } if (ret < 0) { fprintf(stderr, "poll: failed\n"); return 1; } ssize_t rsize = recv(sock, &buf, 4096, 0); if (rsize <= 0) { fprintf(stderr, "%s: %s: bad size? %zd\n", _argv_0, if_name, rsize); continue; } struct payload * response = (void*)buf; if (memcmp(response->eth_header.destination,mac_addr,6) && memcmp(response->eth_header.destination,eth_broadcast,6)) { /* Not ours */ continue; } if (ntohs(response->udp_header.destination_port) != 68) { /* Not DHCP */ continue; } if (ntohl(response->dhcp_header.xid) != xid) { /* Not our transaction */ continue; } if (stage == 1) { yiaddr = response->dhcp_header.yiaddr; struct payload thething = { .payload = {53,1,3,50,4, (yiaddr) & 0xFF, (yiaddr >> 8) & 0xFF, (yiaddr >> 16) & 0xFF, (yiaddr >> 24) & 0xFF, 55,2,3,6,255,0} }; fill(&thething, 14); send(sock, &thething, sizeof(struct payload), 0); stage = 2; gettimeofday(&start, NULL); } else if (stage == 2) { yiaddr = response->dhcp_header.yiaddr; char yiaddr_ip[16]; ip_ntoa(ntohl(yiaddr), yiaddr_ip); if (!ioctl(netdev, SIOCSIFADDR, &yiaddr)) { printf("%s: %s: configured for %s\n", _argv_0, if_name, yiaddr_ip); } else { perror(_argv_0); } /* See if we got a gateway and subnet out of it as well, those are cool... */ uint8_t * opt = response->dhcp_header.options; while (*opt && *opt != 255) { uint8_t opt_type = *opt++; uint8_t len = *opt++; if (opt_type == 1) { /* Subnet mask */ uint32_t ip_data; memcpy(&ip_data, opt, 4); char addr[16]; ip_ntoa(ntohl(ip_data), addr); printf("%s: %s: subnet mask %s\n", _argv_0, if_name, addr); ioctl(netdev, SIOCSIFNETMASK, &ip_data); } else if (opt_type == 3) { /* Gateway address - add this to a route table? */ uint32_t ip_data; memcpy(&ip_data, opt, 4); char addr[16]; ip_ntoa(ntohl(ip_data), addr); printf("%s: %s: gateway %s\n", _argv_0, if_name, addr); ioctl(netdev, SIOCSIFGATEWAY, &ip_data); } else if (opt_type == 6) { /* DNS server */ uint32_t ip_data; memcpy(&ip_data, opt, 4); char addr[16]; ip_ntoa(ntohl(ip_data), addr); printf("%s: %s: nameserver %s\n", _argv_0, if_name, addr); FILE * resolve = fopen("/etc/resolv.conf","w"); if (!resolve) resolve = fopen("/var/resolv.conf","w"); if (resolve) { fprintf(resolve, "nameserver %s\n", addr); fclose(resolve); } /* else, read-only file system? */ } opt += len; } close(netdev); close(sock); return 0; } } while (1); return 1; } static int configure_interface_with_backoff(const char * if_name) { int sleep_times[] = {1,3,5,0}; for (int *time = sleep_times; *time; time++) { if (!configure_interface(if_name)) return 0; sleep(*time); } return 1; } int main(int argc, char * argv[]) { int retval = 0; if (argc > 1) { return configure_interface(argv[1]); } else { /* Read /dev/net for interfaces */ DIR * d = opendir("/dev/net"); if (!d) { fprintf(stderr, "%s: no network?\n", _argv_0); return 1; } struct dirent * ent; while ((ent = readdir(d))) { if (ent->d_name[0] == '.') continue; if (configure_interface_with_backoff(ent->d_name)) { retval = 1; } } closedir(d); } return retval; } ================================================ FILE: apps/dirname.c ================================================ /** * @brief dirname - print directory name from path string * * @copyright * 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 */ #include #include #include int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "%s: expected argument\n", argv[0]); return 1; } char * c = dirname(argv[1]); fprintf(stdout, "%s\n", c); return 0; } ================================================ FILE: apps/drawlines.c ================================================ /** * @brief 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. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include 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; static void draw(void) { 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); } 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)); while (!should_exit) { int fds[1] = {fileno(yctx->sock)}; int index = fswait2(1,fds,20); if (index == 0) { yutani_msg_t * m = yutani_poll(yctx); while (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); m = yutani_poll_async(yctx); } } draw(); } yutani_close(yctx, wina); return 0; } ================================================ FILE: apps/du.c ================================================ /** * @brief du - calculate file size usage * * TODO: Should use st_blocks, but we don't set that in the kernel yet? * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include static int show_total = 0; static int human = 0; static int all = 1; static int is_arg = 0; static uint64_t count_thing(char * tmp); static int print_human_readable_size(char * _out, size_t s) { if (s >= 1<<30) { size_t t = s / (1 << 30); return sprintf(_out, "%d.%1dG", (int)t, (int)(s - t * (1 << 30)) / ((1 << 30) / 10)); } else if (s >= 1<<20) { size_t t = s / (1 << 20); return sprintf(_out, "%d.%1dM", (int)t, (int)(s - t * (1 << 20)) / ((1 << 20) / 10)); } else if (s >= 1<<10) { size_t t = s / (1 << 10); return sprintf(_out, "%d.%1dK", (int)t, (int)(s - t * (1 << 10)) / ((1 << 10) / 10)); } else { return sprintf(_out, "%d", (int)s); } } static void print_size(uint64_t size, char * name) { char sizes[30]; if (!human) { sprintf(sizes, "%-7llu", size/1024LLU); } else { print_human_readable_size(sizes, size); } if (strlen(name) > 2 && name[0] == '/' && name[1] == '/') { name = &name[1]; } fprintf(stdout, "%7s %s\n", sizes, name); } static uint64_t count_directory(char * source) { DIR * dirp = opendir(source); if (dirp == NULL) { //fprintf(stderr, "could not open %s\n", source); return 0; } int was_arg = is_arg; is_arg = 0; uint64_t total = 0; struct dirent * ent = readdir(dirp); while (ent != NULL) { if (!strcmp(ent->d_name,".") || !strcmp(ent->d_name,"..")) { ent = readdir(dirp); continue; } char tmp[strlen(source)+strlen(ent->d_name)+2]; sprintf(tmp, "%s/%s", source, ent->d_name); total += count_thing(tmp); ent = readdir(dirp); } closedir(dirp); if (all || was_arg) { print_size(total, source); } return total; } static uint64_t count_thing(char * tmp) { struct stat statbuf; lstat(tmp,&statbuf); if (S_ISDIR(statbuf.st_mode)) { return count_directory(tmp); } else { if (is_arg) { print_size(statbuf.st_size, tmp); } return statbuf.st_size; } } int main(int argc, char * argv[]) { int opt; while ((opt = getopt(argc, argv, "hsc")) != -1) { switch (opt) { case 'h': /* human readable */ human = 1; break; case 'c': show_total = 1; break; case 's': /* summary */ all = 0; break; default: fprintf(stderr, "%s: unrecognized option '%c'\n", argv[0], opt); break; } } int ret = 0; uint64_t total = 0; for (int i = optind; i < argc; ++i) { is_arg = 1; total += count_thing(argv[i]); } if (show_total) { print_size(total, "total"); } return ret; } ================================================ FILE: apps/echo.c ================================================ /** * @brief echo - Print arguments to stdout. * * Prints arguments to stdout, possibly interpreting escape * sequences in the arguments. * * @copyright * 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 */ #include #include #include #include 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; } ================================================ FILE: apps/env.c ================================================ /** * @brief env - Print or set environment * * @copyright * 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 */ #include #include #include #include #include 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; } ================================================ FILE: apps/esh.c ================================================ /** * @brief E-Shell * * This is "experimental shell" - a vaguely-unix-like command * interface. It has a very rudimentary parser that understands * some things like pipes or writing out to a file. It has a * handful of built-in commands, including ones that implement * some more useful shell syntax such as loops and conditionals. * There is support for tab completion of filenames and commands. * * @copyright * 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-2022 K. Lange */ #define _XOPEN_SOURCE 500 #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef environ extern char **environ; #endif #define PIPE_TOKEN "\xFF\xFFPIPE\xFF\xFF" #define STAR_TOKEN "\xFF\xFFSTAR\xFF\xFF" #define WRITE_TOKEN "\xFF\xFFWRITE\xFF\xFF" #define WRITE_ERR_TOKEN "\xFF\xFFWRITEERR\xFF\xFF" #define APPEND_TOKEN "\xFF\xFF""APPEND\xFF" /* A shell command is like a C program */ typedef uint32_t(*shell_command_t) (int argc, char ** argv); /* We have a static array that fits a certain number of them. */ int SHELL_COMMANDS = 64; char ** shell_commands; /* Command names */ shell_command_t * shell_pointers; /* Command functions */ char ** shell_descript; /* Command descriptions */ FILE * shell_stderr; /* specifically for `time` */ /* This is the number of actual commands installed */ int shell_commands_len = 0; int shell_interactive = 1; int last_ret = 0; char ** shell_argv = NULL; int shell_argc = 0; static int current_line = 0; static char * current_file = NULL; int pid; /* Process ID of the shell */ int my_pgid; int is_subshell = 0; struct semaphore { int fds[2]; }; struct semaphore create_semaphore(void) { struct semaphore out; pipe(out.fds); return out; } void raise_semaphore(struct semaphore s){ write(s.fds[1],"x",1); close(s.fds[0]); close(s.fds[1]); } void wait_semaphore(struct semaphore s) { close(s.fds[1]); char buf; read(s.fds[0], &buf, 1); close(s.fds[0]); } void set_pgid(int pgid) { if (shell_interactive == 1) { setpgid(0, pgid); } } void set_pgrp(int pgid) { if (shell_interactive == 1 && !is_subshell) { sigset_t ss; sigfillset(&ss); sigprocmask(SIG_SETMASK, &ss, NULL); tcsetpgrp(STDIN_FILENO, pgid); sigemptyset(&ss); sigprocmask(SIG_SETMASK, &ss, NULL); } } void reset_pgrp() { set_pgrp(my_pgid); } void shell_install_command(char * name, shell_command_t func, char * desc) { if (shell_commands_len == SHELL_COMMANDS-1) { SHELL_COMMANDS *= 2; shell_commands = realloc(shell_commands, sizeof(char *) * SHELL_COMMANDS); shell_pointers = realloc(shell_pointers, sizeof(shell_command_t) * SHELL_COMMANDS); shell_descript = realloc(shell_descript, sizeof(char *) * SHELL_COMMANDS); } shell_commands[shell_commands_len] = name; shell_pointers[shell_commands_len] = func; shell_descript[shell_commands_len] = desc; shell_commands_len++; shell_commands[shell_commands_len] = NULL; } shell_command_t shell_find(char * str) { for (int i = 0; i < shell_commands_len; ++i) { if (!strcmp(str, shell_commands[i])) { return shell_pointers[i]; } } return NULL; } void install_commands(); /* Maximum command length */ #define LINE_LEN 4096 /* Current working directory */ char cwd[1024] = {'/',0}; /* Username */ char username[1024]; /* Hostname for prompt */ char _hostname[256]; /* function to update the cached username */ void getuser() { char * tmp = getenv("USER"); if (tmp) { strcpy(username, tmp); } else { sprintf(username, "%d", getuid()); } } /* function to update the cached hostname */ void gethost() { struct utsname buf; uname(&buf); int len = strlen(buf.nodename); memcpy(_hostname, buf.nodename, len+1); } int display_width_of_string(const char * str) { uint8_t * s = (uint8_t *)str; int out = 0; uint32_t c, state = 0; while (*s) { if (!decode(&state, &c, *s)) { out += wcwidth(c); } else if (state == UTF8_REJECT) { state = 0; } s++; } return out; } void print_extended_ps(char * format, char * buffer, int * display_width) { /* Get the time */ struct tm * timeinfo; struct timeval now; gettimeofday(&now, NULL); //time(NULL); timeinfo = localtime((time_t *)&now.tv_sec); /* Format the date and time for prompt display */ char date_buffer[80]; strftime(date_buffer, 80, "%m/%d", timeinfo); char time_buffer[80]; strftime(time_buffer, 80, "%H:%M:%S", timeinfo); /* Collect the current working directory */ getcwd(cwd, 512); char _cwd[512]; strcpy(_cwd, cwd); /* Collect the user's home directory and apply it to cwd */ char * home = getenv("HOME"); if (home && strstr(cwd, home) == cwd) { char * c = cwd + strlen(home); if (*c == '/' || *c == 0) { sprintf(_cwd, "~%s", c); } } char ret[80] = {0}; if (last_ret != 0) { sprintf(ret, "%d ", last_ret); } size_t offset = 0; int is_visible = 1; char dispchars[1024] = {0}; char * dispout = dispchars; while (*format) { if (*format == '\\') { format++; switch (*format) { case '\\': buffer[offset++] = *format; if (is_visible) *dispout++ = *format; format++; break; case '[': is_visible = 0; format++; break; case ']': is_visible = 1; format++; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': { int i = (*format) - '0'; format++; if (*format >= '0' && *format <= '7') { i *= 8; i += (*format) - '0'; format++; if (*format >= '0' && *format <= '7') { i *= 8; i += (*format) - '0'; format++; } } buffer[offset++] = i; if (is_visible) *dispout++ = i; } break; case 'e': buffer[offset++] = '\033'; if (is_visible) *dispout++ = '\033'; format++; break; case 'd': { int size = sprintf(buffer+offset, "%s", date_buffer); offset += size; if (is_visible) { dispout += sprintf(dispout, "%s", date_buffer); } } format++; break; case 't': { int size = sprintf(buffer+offset, "%s", time_buffer); offset += size; if (is_visible) { dispout += sprintf(dispout, "%s", time_buffer); } } format++; break; case 'h': { int size = sprintf(buffer+offset, "%s", _hostname); offset += size; if (is_visible) { dispout += sprintf(dispout, "%s", _hostname); } } format++; break; case 'u': { int size = sprintf(buffer+offset, "%s", username); offset += size; if (is_visible) { dispout += sprintf(dispout, "%s", username); } } format++; break; case 'w': { int size = sprintf(buffer+offset, "%s", _cwd); offset += size; if (is_visible) { dispout += sprintf(dispout, "%s", _cwd); } } format++; break; case '$': buffer[offset++] = (getuid() == 0 ? '#' : '$'); if (is_visible) *dispout++ = (getuid() == 0 ? '#' : '$'); format++; break; case 'U': /* prompt color string */ { int size = sprintf(buffer+offset, "%s", getuid() == 0 ? "\033[1;38;5;196m" : "\033[1;38;5;47m"); offset += size; /* Does not affect size */ } format++; break; case 'r': { int size = sprintf(buffer+offset, "%s", ret); offset += size; if (is_visible) { dispout += sprintf(dispout, "%s", ret); } } format++; break; default: { int size = sprintf(buffer+offset, "\\%c", *format); offset += size; if (is_visible) { dispout += sprintf(dispout, "\\%c", *format); } } format++; break; } } else { buffer[offset++] = *format; if (is_visible) *dispout++ = *format; format++; } } *display_width = display_width_of_string(dispchars); buffer[offset] = '\0'; } volatile int break_while = 0; pid_t suspended_pgid = 0; hashmap_t * job_hash = NULL; hashmap_t * desc_hash = NULL; void sig_break_loop(int sig) { /* Interrupt handler */ if ((shell_interactive == 1 && !is_subshell)) { break_while = sig; signal(sig, sig_break_loop); } else { signal(sig, SIG_DFL); raise(sig); } } void tab_complete_func(rline_context_t * c) { char * dup = malloc(LINE_LEN); memcpy(dup, c->buffer, LINE_LEN); char *pch, *save; char *argv[1024]; int argc = 0; int cursor = 0; pch = strtok_r(dup, " ", &save); if (!pch) { argv[0] = ""; argc = 0; } while (pch != NULL) { if (pch - dup <= c->offset) cursor = argc; argv[argc] = pch; ++argc; pch = strtok_r(NULL, " ", &save); } argv[argc] = NULL; if (c->offset && c->buffer[c->offset-1] == ' ' && argc) { cursor++; } char * word = argv[cursor]; int word_offset = word ? (c->offset - (argv[cursor] - dup)) : 0; char * prefix = malloc(word_offset + 1); if (word) memcpy(prefix, word, word_offset); prefix[word_offset] = '\0'; /* Complete file path */ list_t * matches = list_create(); char * match = NULL; int free_matches = 0; int no_space_if_only = 0; /* TODO custom auto-complete as a configuration file? */ #define COMPLETE_FILE 1 #define COMPLETE_COMMAND 2 #define COMPLETE_CUSTOM 3 #define COMPLETE_VARIABLE 4 int complete_mode = COMPLETE_FILE; int with_dollar = 0; int command_adj = 0; int cursor_adj = cursor; while (command_adj < argc && strstr(argv[command_adj],"=")) { cursor_adj -= 1; command_adj += 1; } /* Various commands are generally prefixes */ if (command_adj < argc && ( !strcmp(argv[command_adj], "sudo") || !strcmp(argv[command_adj], "gsudo") || !strcmp(argv[command_adj], "time") || /* TODO: Both of these may take additional arguments... */ !strcmp(argv[command_adj], "strace") || !strcmp(argv[command_adj], "dbg") )) { cursor_adj -= 1; command_adj += 1; } /* initial tab completion should be commands, unless typing a file path */ if (cursor_adj == 0 && !strchr(prefix,'/')) { complete_mode = COMPLETE_COMMAND; } if (cursor_adj >= 1 && !strcmp(argv[command_adj], "toggle-abs-mouse")) { complete_mode = COMPLETE_CUSTOM; } if (cursor_adj >= 1 && !strcmp(argv[command_adj], "msk")) { complete_mode = COMPLETE_CUSTOM; } if (cursor_adj >= 1 && !strcmp(argv[command_adj], "ifconfig")) { complete_mode = COMPLETE_CUSTOM; } if (cursor_adj >= 1 && !strcmp(argv[command_adj], "unset")) { complete_mode = COMPLETE_VARIABLE; } /* complete variable names */ if (*prefix == '$') { complete_mode = COMPLETE_VARIABLE; with_dollar = 1; } if (complete_mode == COMPLETE_COMMAND) { /* Complete a command name */ for (int i = 0; i < shell_commands_len; ++i) { if (strstr(shell_commands[i], prefix) == shell_commands[i]) { list_insert(matches, shell_commands[i]); match = shell_commands[i]; } } } else if (complete_mode == COMPLETE_FILE) { /* Complete a file path */ free_matches = 1; char * tmp = strdup(prefix); char * last_slash = strrchr(tmp, '/'); DIR * dirp; char * compare = prefix; if (last_slash) { *last_slash = '\0'; word = word + (last_slash - tmp) + 1; word_offset = word_offset - (last_slash - tmp + 1); compare = word; if (last_slash == tmp) { dirp = opendir("/"); } else { char * home; if (*tmp == '~' && (home = getenv("HOME"))) { char * t = malloc(strlen(tmp) + strlen(home) + 4); sprintf(t, "%s%s",home,tmp+1); dirp = opendir(t); free(t); } else { dirp = opendir(tmp); } } } else { dirp = opendir("."); } if (!dirp) { free(tmp); goto finish_tab; } struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] != '.' || compare[0] == '.') { if (!word || strstr(ent->d_name, compare) == ent->d_name) { struct stat statbuf; /* stat it */ if (last_slash) { char * x; char * home; if (tmp[0] == '~' && (home = getenv("HOME"))) { x = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1 + strlen(home) + 1); snprintf(x, strlen(tmp) + 1 + strlen(ent->d_name) + 1 + strlen(home) + 1, "%s%s/%s",home,tmp+1,ent->d_name); } else { x = malloc(strlen(tmp) + 1 + strlen(ent->d_name) + 1); snprintf(x, strlen(tmp) + 1 + strlen(ent->d_name) + 1, "%s/%s",tmp,ent->d_name); } lstat(x, &statbuf); free(x); } else { lstat(ent->d_name, &statbuf); } char * s; if (S_ISDIR(statbuf.st_mode)) { s = malloc(strlen(ent->d_name) + 2); sprintf(s,"%s/", ent->d_name); no_space_if_only = 1; } else { s = strdup(ent->d_name); } list_insert(matches, s); match = s; } } ent = readdir(dirp); } closedir(dirp); free(tmp); } else if (complete_mode == COMPLETE_CUSTOM) { char * none[] = {NULL}; char ** completions = none; char * toggle_abs_mouse_completions[] = {"relative","absolute",NULL}; char * msk_commands[] = {"update","install","list","count","--version",NULL}; char * ifconfig_commands[] = {"inet","netmask","gateway",NULL}; if (!strcmp(argv[command_adj],"toggle-abs-mouse")) { completions = toggle_abs_mouse_completions; } else if (!strcmp(argv[command_adj], "msk")) { if (cursor_adj == 1) { completions = msk_commands; } else if (cursor_adj > 1 && !strcmp(argv[command_adj+1],"install")) { FILE * f = fopen("/var/msk/manifest","r"); list_t * packages = list_create(); if (f) { while (!feof(f)) { char tmp[4096] = {0}; if (!fgets(tmp, 4096, f)) break; if (tmp[0] == '[') { char * s = strstr(tmp, "]"); if (s) { *s = '\0'; } list_insert(packages, strdup(tmp+1)); } } completions = malloc(sizeof(char *) * (packages->length + 1)); free_matches = 1; size_t i = 0; foreach(node, packages) { completions[i++] = node->value; } completions[i] = NULL; list_free(packages); } } } else if (!strcmp(argv[command_adj], "ifconfig")) { if (cursor_adj == 1) { /* interface names */ DIR * d = opendir("/dev/net"); if (d) { free_matches = 1; list_t * interfaces = list_create(); struct dirent * ent; while ((ent = readdir(d))) { if (ent->d_name[0] == '.') continue; list_insert(interfaces, strdup(ent->d_name)); } closedir(d); completions = malloc(sizeof(char*) * (interfaces->length + 1)); size_t i = 0; foreach(node, interfaces) { completions[i++] = node->value; } completions[i] = NULL; list_free(interfaces); } } else if (cursor_adj > 1) { completions = ifconfig_commands; } } while (*completions) { if (strstr(*completions, prefix) == *completions) { list_insert(matches, *completions); match = *completions; } completions++; } } else if (complete_mode == COMPLETE_VARIABLE) { char ** envvar = environ; free_matches = 1; while (*envvar) { char * tmp = strdup(*envvar); char * c = strchr(tmp, '='); *c = '\0'; if (strstr(tmp, prefix+with_dollar) == tmp) { char * m = malloc(strlen(tmp)+1+with_dollar); sprintf(m, "%s%s", with_dollar ? "$" : "", tmp); list_insert(matches, m); match = m; } free(tmp); envvar++; } } if (matches->length == 1) { /* Insert */ rline_insert(c, &match[word_offset]); if (word && word_offset == (int)strlen(word) && !no_space_if_only) { rline_insert(c, " "); } rline_redraw(c); } else if (matches->length > 1) { if (!c->tabbed) { /* see if there is a minimum subset we can fill in */ size_t j = word_offset; do { char d = match[j]; int diff = 0; foreach(node, matches) { char * match = (char *)node->value; if (match[j] != d || match[j] == '\0') diff = 1; } if (diff) break; j++; } while (j < (size_t)c->requested); if (j > (size_t)word_offset) { char * tmp = strdup(match); tmp[j] = '\0'; rline_insert(c, &tmp[word_offset]); rline_redraw(c); free(tmp); } else { c->tabbed = 1; } } else { /* Print matches */ fprintf(stderr,"\n\033[0m"); size_t j = 0; foreach(node, matches) { char * match = (char *)node->value; fprintf(stderr, "%s", match); ++j; if (j < matches->length) { fprintf(stderr, ", "); } } fprintf(stderr,"\n"); c->callbacks->redraw_prompt(c); fprintf(stderr, "\033[s"); rline_redraw(c); } } finish_tab: if (free_matches) list_destroy(matches); list_free(matches); free(prefix); free(dup); } void add_argument(list_t * argv, char * buf) { char * c = malloc(strlen(buf) + 1); memcpy(c, buf, strlen(buf) + 1); list_insert(argv, c); } void add_environment(list_t * env) { if (env->length) { foreach (node, env) { char * c = node->value; putenv(strdup(c)); } } } #define FALLBACK_PS1 "\\u@\\h \\w\\$ " int read_entry(char * buffer) { char lprompt[1024], rprompt[1024]; int lwidth, rwidth; char * ps1 = getenv("PS1_LEFT"); print_extended_ps(ps1 ? ps1 : FALLBACK_PS1, lprompt, &lwidth); char * ps1r = getenv("PS1_RIGHT"); print_extended_ps(ps1r ? ps1r : "", rprompt, &rwidth); rline_exit_string="exit"; rline_exp_set_syntax("esh"); rline_exp_set_prompts(lprompt, rprompt, lwidth, rwidth); rline_exp_set_shell_commands(shell_commands, shell_commands_len); rline_exp_set_tab_complete_func(tab_complete_func); return rline(buffer, LINE_LEN); } int read_entry_continued(char * buffer) { rline_exit_string="exit"; rline_exp_set_syntax("esh"); rline_exp_set_prompts("> ", "", 2, 0); rline_exp_set_shell_commands(shell_commands, shell_commands_len); rline_exp_set_tab_complete_func(tab_complete_func); return rline(buffer, LINE_LEN); } int variable_char(uint8_t c) { if (c >= 'A' && c <= 'Z') return 1; if (c >= 'a' && c <= 'z') return 1; if (c >= '0' && c <= '9') return 1; if (c == '_') return 1; return 0; } int variable_char_first(uint8_t c) { if (c == '?') return 1; if (c == '$') return 1; if (c == '#') return 1; return 0; } struct alternative { const char * command; const char * replacement; const char * description; }; #define ALT_BIM "bim", "vi-like text editor" #define ALT_FETCH "fetch", "URL downloader" #define ALT_NETIF "ifconfig", "to see network configuration" static struct alternative cmd_alternatives[] = { /* Propose bim as an alternative for common text editors */ {"vim", ALT_BIM}, {"vi", ALT_BIM}, {"emacs", ALT_BIM}, {"nano", ALT_BIM}, /* Propose fetch for some common URL-getting tools */ {"curl", ALT_FETCH}, {"wget", ALT_FETCH}, /* We don't have ip or ifconfig commands, suggest cat /proc/netif */ {"ipconfig", ALT_NETIF}, {"ip", ALT_NETIF}, /* Some random other stuff */ {"more", "bim -", "paging to a text editor"}, {"less", "bim -", "paging to a text editor"}, {NULL, NULL, NULL}, }; void run_cmd(char ** args) { int i = execvp(*args, args); shell_command_t func = shell_find(*args); if (func) { int argc = 0; while (args[argc]) { argc++; } i = func(argc, args); } else { if (i != 0) { if (errno == ENOENT) { fprintf(stderr, "%s: Command not found\n", *args); for (struct alternative * alt = cmd_alternatives; alt->command; alt++) { if (!strcmp(*args, alt->command)) { fprintf(stderr, "Consider this alternative:\n\n\t%s -- \033[3m%s\033[0m\n\n", alt->replacement, alt->description); break; } } } else if (errno == ELOOP) { fprintf(stderr, "esh: Bad interpreter (maximum recursion depth reached)\n"); } else if (errno == ENOEXEC) { fprintf(stderr, "esh: Bad interpreter\n"); } else { fprintf(stderr, "esh: Invalid executable\n"); } i = 127; } } exit(i); } int is_number(const char * c) { while (*c) { if (!isdigit(*c)) return 0; c++; } return 1; } /** * Prints "Segmentation fault", etc. */ static void handle_status(int ret_code) { if (WIFSIGNALED(ret_code)) { int sig = WTERMSIG(ret_code); if (sig == SIGINT || sig == SIGPIPE) { fprintf(stderr, "\n"); return; } char * str = strsignal(sig); if (shell_interactive == 1) { fprintf(stderr, "%s\n", str); } else if (shell_interactive == 2) { fprintf(stderr, "%s: line %d: %s\n", current_file, current_line, str); } } } static void describe_job(pid_t pid); int wait_for_child(int pgid, char * name, int retpid) { int waitee = (shell_interactive == 1 && !is_subshell) ? -pgid : pgid; int outpid; int ret_code = 0; int ret_code_real = 0; int e; int _stopped = 0; do { outpid = waitpid(waitee, &ret_code, WSTOPPED); e = errno; if (outpid == retpid) { ret_code_real = ret_code; } if (outpid != -1 && WIFSTOPPED(ret_code)) { suspended_pgid = pgid; if (name) { void * old = hashmap_set(job_hash, (void*)(intptr_t)pgid, strdup(name)); if (old) free(old); } void * old = hashmap_set(desc_hash, (void*)(intptr_t)pgid, strdup(WSTOPSIG(ret_code) ? strsignal(WSTOPSIG(ret_code)) : "Stopped")); if (old) free(old); _stopped = 1; if (!shell_interactive) continue; break; } else if (outpid != -1) { suspended_pgid = 0; if (hashmap_has(job_hash, (void*)(intptr_t)pgid)) { hashmap_remove(job_hash, (void*)(intptr_t)pgid); hashmap_remove(desc_hash, (void*)(intptr_t)pgid); } } } while (outpid != -1 || (outpid == -1 && e != ECHILD)); reset_pgrp(); handle_status(ret_code_real); if (_stopped && shell_interactive) { fprintf(stderr, "\n"); describe_job(pgid); } return WEXITSTATUS(ret_code_real); } int shell_exec(char * buffer, size_t size, FILE * file, char ** out_buffer) { *out_buffer = NULL; /* Read previous history entries */ if (buffer[0] == '!') { int x = atoi((char *)((uintptr_t)buffer + 1)); if (x > 0 && x <= rline_history_count) { buffer = rline_history_get(x - 1); } else { fprintf(stderr, "esh: !%d: event not found\n", x); return 0; } } char * argv[1024]; int tokenid = 0; char quoted = 0; char backtick = 0; char buffer_[512] = {0}; int collected = 0; int force_collected = 0; list_t * args = list_create(); int have_star = 0; while (1) { char * p = buffer; while (*p) { switch (*p) { case '$': if (quoted == '\'') { goto _just_add; } else { if (backtick) { goto _just_add; } p++; char var[100]; int coll = 0; if (*p == '{') { p++; while (*p != '}' && *p != '\0' && (coll < 100)) { var[coll] = *p; coll++; var[coll] = '\0'; p++; } if (*p == '}') { p++; } } else { while (*p != '\0' && (variable_char(*p) || (coll == 0 && variable_char_first(*p))) && (coll < 100)) { var[coll] = *p; coll++; var[coll] = '\0'; if (coll == 1 && (isdigit(*p) || *p == '?' || *p == '$' || *p == '#')) { p++; break; /* Don't let these keep going */ } p++; } } /* Special cases */ char *c = NULL; char tmp[128]; if (!strcmp(var, "?")) { sprintf(tmp,"%d",last_ret); c = tmp; } else if (!strcmp(var, "$")) { sprintf(tmp,"%d",getpid()); c = tmp; } else if (!strcmp(var, "#")) { sprintf(tmp,"%d",shell_argc ? shell_argc-1 : 0); c = tmp; } else if (is_number(var)) { int a = atoi(var); if (a >= 0 && a < shell_argc) { c = shell_argv[a]; } } else if (!strcmp(var, "RANDOM")) { sprintf(tmp,"%d",rand()%32768); /* sure, modulo is bad for range restriction, shut up */ c = tmp; } else { c = getenv(var); } if (c) { backtick = 0; for (int i = 0; i < (int)strlen(c); ++i) { if (c[i] == ' ' && !quoted) { /* If we are not quoted and we reach a space, it signals a new argument */ if (collected || force_collected) { buffer_[collected] = '\0'; add_argument(args, buffer_); buffer_[0] = '\0'; have_star = 0; collected = 0; force_collected = 0; } } else { buffer_[collected] = c[i]; collected++; } } buffer_[collected] = '\0'; } continue; } case '~': if (quoted || collected || backtick) { goto _just_add; } else { if (p[1] == 0 || p[1] == '/' || p[1] == '\n' || p[1] == ' ' || p[1] == ';') { char * c = getenv("HOME"); if (!c) { goto _just_add; } for (int i = 0; i < (int)strlen(c); ++i) { buffer_[collected] = c[i]; collected++; } buffer_[collected] = '\0'; goto _next; } else { goto _just_add; } } break; case '\"': force_collected = 1; if (quoted == '\"') { if (backtick) { goto _just_add; } quoted = 0; goto _next; } else if (!quoted) { if (backtick) { goto _just_add; } quoted = *p; goto _next; } goto _just_add; case '\'': force_collected = 1; if (quoted == '\'') { if (backtick) { goto _just_add; } quoted = 0; goto _next; } else if (!quoted) { if (backtick) { goto _just_add; } quoted = *p; goto _next; } goto _just_add; case '*': if (quoted) { goto _just_add; } if (backtick) { goto _just_add; } if (have_star) { goto _just_add; /* TODO multiple globs */ } have_star = 1; collected += sprintf(&buffer_[collected], STAR_TOKEN); goto _next; case '\\': if (quoted == '\'') { goto _just_add; } if (backtick) { goto _just_add; } backtick = 1; goto _next; case ' ': if (backtick) { goto _just_add; } if (!quoted) { goto _new_arg; } goto _just_add; case '\n': if (!quoted) { goto _done; } goto _just_add; case '|': if (!quoted && !backtick) { if (collected || force_collected) { add_argument(args, buffer_); } force_collected = 0; collected = sprintf(buffer_, "%s", PIPE_TOKEN); goto _new_arg; } goto _just_add; case '>': if (!quoted && !backtick) { if (collected || force_collected) { if (!strcmp(buffer_,"2")) { /* Special case */ force_collected = 0; collected = sprintf(buffer_,"%s", WRITE_ERR_TOKEN); goto _new_arg; } add_argument(args, buffer_); } force_collected = 0; collected = sprintf(buffer_, "%s", WRITE_TOKEN); goto _new_arg; } goto _just_add; case ';': if (!quoted && !backtick) { *out_buffer = ++p; goto _done; } goto _just_add; case '#': if (!quoted && !backtick) { goto _done; /* Support comments; must not be part of an existing arg */ } goto _just_add; default: if (backtick) { buffer_[collected] = '\\'; collected++; buffer_[collected] = '\0'; } _just_add: backtick = 0; buffer_[collected] = *p; collected++; buffer_[collected] = '\0'; goto _next; } _new_arg: backtick = 0; if (collected || force_collected) { add_argument(args, buffer_); buffer_[0] = '\0'; have_star = 0; collected = 0; force_collected = 0; } _next: p++; } _done: if (quoted || backtick) { backtick = 0; if (shell_interactive == 1) { break_while = 0; read_entry_continued(buffer); if (break_while) { break_while = 0; return 1; } rline_history_append_line(buffer); continue; } else if (shell_interactive == 2) { fgets(buffer, size, file); continue; } else { fprintf(stderr, "Syntax error: Unterminated quoted string.\n"); return 127; } } if (collected || force_collected) { add_argument(args, buffer_); break; } break; } int cmdi = 0; char ** arg_starts[100] = { &argv[0], NULL }; char * output_files[100] = { NULL }; int file_args[100] = {0}; int argcs[100] = {0}; char * err_files[100] = { NULL }; int err_args[100] = {0}; int next_is_file = 0; int next_is_err = 0; list_t * extra_env = list_create(); list_t * glob_args = list_create(); int i = 0; foreach(node, args) { char * c = node->value; if (next_is_err) { if (next_is_err == 1 && !strcmp(c, WRITE_TOKEN)) { next_is_err = 2; err_args[cmdi] = O_WRONLY | O_CREAT | O_APPEND; continue; } err_files[cmdi] = c; continue; } if (!strcmp(c, WRITE_ERR_TOKEN)) { next_is_err = 1; err_args[cmdi] = O_WRONLY | O_CREAT | O_TRUNC; continue; } if (next_is_file) { if (next_is_file == 1 && !strcmp(c, WRITE_TOKEN)) { next_is_file = 2; file_args[cmdi] = O_WRONLY | O_CREAT | O_APPEND; continue; } output_files[cmdi] = c; continue; } if (!strcmp(c, WRITE_TOKEN)) { next_is_file = 1; file_args[cmdi] = O_WRONLY | O_CREAT | O_TRUNC; continue; } if (!strcmp(c, PIPE_TOKEN)) { if (arg_starts[cmdi] == &argv[i]) { fprintf(stderr, "Syntax error: Unexpected pipe token\n"); return 2; } argv[i] = 0; i++; cmdi++; arg_starts[cmdi] = &argv[i]; continue; } if (i == 0 && strstr(c,"=")) { list_insert(extra_env, c); continue; } char * glob = strstr(c, STAR_TOKEN); if (glob) { /* Globbing */ glob[0] = '\0'; glob[1] = '\0'; char * before = c; char * after = &glob[8]; char * dir = NULL; int has_before = !!strlen(before); int has_after = !!strlen(after); if (1) { /* read current directory, add all */ DIR * dirp; char * prepend = ""; if (has_before) { dir = strrchr(before,'/'); if (!dir) { dirp = opendir("."); } else if (dir == before) { dirp = opendir("/"); prepend = before; before++; } else { *dir = '\0'; dirp = opendir(before); prepend = before; before = dir+1; } } else { dirp = opendir("."); } int before_i = i; if (dirp) { struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] != '.' || (dir ? (dir[1] == '.') : (before && before[0] == '.'))) { char * s = malloc(sizeof(char) * (strlen(ent->d_name) + 1)); memcpy(s, ent->d_name, strlen(ent->d_name) + 1); char * t = s; if (has_before) { if (strstr(s,before) != s) { free(s); goto _nope; } t = &s[strlen(before)]; } if (has_after) { if (strlen(t) >= strlen(after)) { if (!strcmp(after,&t[strlen(t)-strlen(after)])) { char * out = malloc(strlen(s) + 2 + strlen(prepend)); sprintf(out,"%s%s%s", prepend, !!*prepend ? "/" : "", s); list_insert(glob_args, out); argv[i] = out; i++; argcs[cmdi]++; } } } else { char * out = malloc(strlen(s) + 2 + strlen(prepend)); sprintf(out,"%s%s%s", prepend, !!*prepend ? "/" : "", s); list_insert(glob_args, out); argv[i] = out; i++; argcs[cmdi]++; } free(s); } _nope: ent = readdir(dirp); } closedir(dirp); } if (before_i == i) { /* no matches */ glob[0] = '*'; if (dir) { *dir = '/'; } memmove(&glob[1], after, strlen(after)+1); argv[i] = c; i++; argcs[cmdi]++; } } } else { argv[i] = c; i++; argcs[cmdi]++; } } argv[i] = NULL; /* Ensure globs get freed */ foreach(node, glob_args) { list_insert(args, node->value); } list_free(glob_args); free(glob_args); if (i == 0) { add_environment(extra_env); list_free(extra_env); free(extra_env); list_destroy(args); free(args); return -1; } if (!*arg_starts[cmdi]) { fprintf(stderr, "Syntax error: Unexpected end of input\n"); list_free(extra_env); free(extra_env); list_destroy(args); free(args); return 2; } tokenid = i; unsigned int child_pid; int last_child; int nowait = (!strcmp(argv[tokenid-1],"&")); if (nowait) { argv[tokenid-1] = NULL; } int pgid = 0; if (cmdi > 0) { int last_output[2]; pipe(last_output); child_pid = fork(); if (!child_pid) { set_pgid(0); if (!nowait) set_pgrp(getpid()); is_subshell = 1; dup2(last_output[1], STDOUT_FILENO); close(last_output[0]); add_environment(extra_env); run_cmd(arg_starts[0]); } pgid = child_pid; for (int j = 1; j < cmdi; ++j) { int tmp_out[2]; pipe(tmp_out); if (!fork()) { is_subshell = 1; set_pgid(pgid); dup2(tmp_out[1], STDOUT_FILENO); dup2(last_output[0], STDIN_FILENO); close(tmp_out[0]); close(last_output[1]); add_environment(extra_env); run_cmd(arg_starts[j]); } close(last_output[0]); close(last_output[1]); last_output[0] = tmp_out[0]; last_output[1] = tmp_out[1]; } struct semaphore s = create_semaphore(); last_child = fork(); if (!last_child) { is_subshell = 1; set_pgid(pgid); raise_semaphore(s); if (output_files[cmdi]) { int fd = open(output_files[cmdi], file_args[cmdi], 0666); if (fd < 0) { fprintf(stderr, "sh: %s: %s\n", output_files[cmdi], strerror(errno)); return -1; } else { dup2(fd, STDOUT_FILENO); } } if (err_files[cmdi]) { int fd = open(err_files[cmdi], err_args[cmdi], 0666); if (fd < 0) { fprintf(stderr, "sh: %s: %s\n", err_files[cmdi], strerror(errno)); return -1; } else { dup2(fd, STDERR_FILENO); } } dup2(last_output[0], STDIN_FILENO); close(last_output[1]); add_environment(extra_env); run_cmd(arg_starts[cmdi]); } close(last_output[0]); close(last_output[1]); wait_semaphore(s); /* Now execute the last piece and wait on all of them */ } else { shell_command_t func = shell_find(*arg_starts[0]); if (func) { int old_out = -1; int old_err = -1; if (output_files[0]) { old_out = dup(STDOUT_FILENO); int fd = open(output_files[cmdi], file_args[cmdi], 0666); if (fd < 0) { fprintf(stderr, "sh: %s: %s\n", output_files[cmdi], strerror(errno)); return -1; } else { dup2(fd, STDOUT_FILENO); } } if (err_files[0]) { old_err = dup(STDERR_FILENO); int fd = open(err_files[cmdi], err_args[cmdi], 0666); if (fd < 0) { fprintf(stderr, "sh: %s: %s\n", err_files[cmdi], strerror(errno)); return -1; } else { dup2(fd, STDERR_FILENO); } } int result = func(argcs[0], arg_starts[0]); if (old_out != -1) dup2(old_out, STDOUT_FILENO); if (old_err != -1) dup2(old_err, STDERR_FILENO); return result; } else { struct semaphore s = create_semaphore(); child_pid = fork(); if (!child_pid) { set_pgid(0); if (!nowait) set_pgrp(getpid()); raise_semaphore(s); is_subshell = 1; if (output_files[cmdi]) { int fd = open(output_files[cmdi], file_args[cmdi], 0666); if (fd < 0) { fprintf(stderr, "sh: %s: %s\n", output_files[cmdi], strerror(errno)); return -1; } else { dup2(fd, STDOUT_FILENO); } } if (err_files[cmdi]) { int fd = open(err_files[cmdi], err_args[cmdi], 0666); if (fd < 0) { fprintf(stderr, "sh: %s: %s\n", err_files[cmdi], strerror(errno)); return -1; } else { dup2(fd, STDERR_FILENO); } } add_environment(extra_env); run_cmd(arg_starts[0]); } wait_semaphore(s); pgid = child_pid; last_child = child_pid; } } if (nowait) { if (shell_interactive == 1) { fprintf(stderr, "[%d] %s\n", pgid, arg_starts[0][0]); hashmap_set(job_hash, (void*)(intptr_t)pgid, strdup(arg_starts[0][0])); hashmap_set(desc_hash, (void*)(intptr_t)pgid, strdup("Running")); } else { hashmap_set(job_hash, (void*)(intptr_t)last_child, strdup(arg_starts[0][0])); } list_free(extra_env); free(extra_env); list_destroy(args); free(args); return 0; } int ret = wait_for_child(shell_interactive == 1 ? pgid : last_child, arg_starts[0][0], last_child); list_free(extra_env); free(extra_env); list_destroy(args); free(args); return ret; } void add_path_contents(char * path) { DIR * dirp = opendir(path); if (!dirp) return; /* Failed to load directly */ struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] != '.') { char * s = malloc(sizeof(char) * (strlen(ent->d_name) + 1)); memcpy(s, ent->d_name, strlen(ent->d_name) + 1); shell_install_command(s, NULL, NULL); } ent = readdir(dirp); } closedir(dirp); } struct command { char * string; void * func; char * desc; }; static int comp_shell_commands(const void *p1, const void *p2) { return strcmp(((struct command *)p1)->string, ((struct command *)p2)->string); } void sort_commands() { struct command commands[SHELL_COMMANDS]; for (int i = 0; i < shell_commands_len; ++i) { commands[i].string = shell_commands[i]; commands[i].func = shell_pointers[i]; commands[i].desc = shell_descript[i]; } qsort(&commands, shell_commands_len, sizeof(struct command), comp_shell_commands); for (int i = 0; i < shell_commands_len; ++i) { shell_commands[i] = commands[i].string; shell_pointers[i] = commands[i].func; shell_descript[i] = commands[i].desc; } } void show_version(void) { printf("esh 1.10.0\n"); } void show_usage(int argc, char * argv[]) { printf( "Esh: The Experimental Shell\n" "\n" "usage: %s [-lha] [path]\n" "\n" " -c \033[4mcmd\033[0m \033[3mparse and execute cmd\033[0m\n" //-c cmd \033[... " -R \033[3mdisable experimental line editor\033[0m\n" " -v \033[3mshow version information\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); } void add_path(void) { char * envvar = getenv("PATH"); if (!envvar) { add_path_contents("/bin"); return; } char * tmp = strdup(envvar); do { char * end = strstr(tmp,":"); if (end) { *end = '\0'; end++; } add_path_contents(tmp); tmp = end; } while (tmp); free(tmp); } int run_script(FILE * f) { current_line = 1; while (!feof(f)) { char buf[LINE_LEN] = {0}; fgets(buf, LINE_LEN, f); int ret; char * out = NULL; char * b = buf; do { ret = shell_exec(b, LINE_LEN, f, &out); b = out; } while (b); current_line++; if (ret >= 0) last_ret = ret; } fclose(f); return last_ret; } void source_eshrc(void) { char * home = getenv("HOME"); if (!home) return; char tmp[512]; sprintf(tmp, "%s/.eshrc", home); FILE * f = fopen(tmp, "r"); if (!f) return; current_file = tmp; run_script(f); } int main(int argc, char ** argv) { pid = getpid(); signal(SIGINT, sig_break_loop); srand(getpid() + time(0)); job_hash = hashmap_create_int(10); desc_hash = hashmap_create_int(10); getuser(); gethost(); install_commands(); int err = dup(STDERR_FILENO); shell_stderr = fdopen(err, "w"); if (argc > 1) { int c; while ((c = getopt(argc, argv, "c:v?")) != -1) { switch (c) { case 'c': shell_interactive = 0; { char * out = NULL; do { last_ret = shell_exec(optarg, strlen(optarg), NULL, &out); optarg = out; } while (optarg); } return (last_ret == -1) ? 0 : last_ret; case 'v': show_version(); return 0; case '?': show_usage(argc, argv); return 0; } } } if (optind < argc) { shell_interactive = 2; FILE * f = fopen(argv[optind],"r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); return 1; } shell_argc = argc - 1; shell_argv = &argv[optind]; current_file = argv[optind]; return run_script(f); } shell_interactive = 1; my_pgid = getpgid(0); source_eshrc(); add_path(); sort_commands(); while (1) { char buffer[LINE_LEN] = {0}; list_t * keys = hashmap_keys(job_hash); foreach(node, keys) { int pid = (intptr_t)node->value; int status = 0; if (waitpid(-pid, &status, WNOHANG|WUNTRACED) > 0) { char * desc; if (WIFSTOPPED(status)) { desc = strdup(WSTOPSIG(status) ? strsignal(WSTOPSIG(status)) : "Stopped"); } else { desc = strdup(WTERMSIG(status) ? strsignal(WTERMSIG(status)) : "Done"); } void * old = hashmap_set(desc_hash, (void*)(intptr_t)pid, desc); if (old) free(old); describe_job(pid); if (WIFEXITED(status)) { if (hashmap_has(job_hash, (void*)(intptr_t)pid)) { hashmap_remove(job_hash, (void*)(intptr_t)pid); hashmap_remove(desc_hash, (void*)(intptr_t)pid); } } } } list_free(keys); free(keys); read_entry(buffer); char * history = malloc(strlen(buffer) + 1); memcpy(history, buffer, strlen(buffer) + 1); if (buffer[0] != ' ' && buffer[0] != '\n' && buffer[0] != '!') { rline_history_insert(history); } else { free(history); } int ret; char * out = NULL; char * b = buffer; do { ret = shell_exec(b, LINE_LEN, stdin, &out); b = out; } while (b); if (ret >= 0) last_ret = ret; rline_scroll = 0; } return 0; } /* * cd [path] */ uint32_t shell_cmd_cd(int argc, char * argv[]) { if (argc > 1) { if (chdir(argv[1])) { goto cd_error; } /* else success */ } else /* argc < 2 */ { char * home = getenv("HOME"); if (home) { if (chdir(home)) { goto cd_error; } } else { char home_path[1200]; sprintf(home_path, "/home/%s", username); if (chdir(home_path)) { goto cd_error; } } } return 0; cd_error: fprintf(stderr, "%s: could not cd '%s': %s\n", argv[0], argv[1], strerror(errno)); return 1; } /* * history */ uint32_t shell_cmd_history(int argc, char * argv[]) { for (int i = 0; i < rline_history_count; ++i) { printf("%d\t%s\n", i + 1, rline_history_get(i)); } return 0; } uint32_t shell_cmd_export(int argc, char * argv[]) { if (argc > 1) { if (putenv(argv[1]) < 0) { fprintf(stderr, "%s: putenv: %s\n", argv[0], strerror(errno)); return 1; } } return 0; } uint32_t shell_cmd_exit(int argc, char * argv[]) { if (argc > 1) { exit(atoi(argv[1])); } else { exit(0); } return -1; } uint32_t shell_cmd_help(int argc, char * argv[]) { show_version(); printf("\nThis shell is not POSIX-compliant, please be careful.\n\n"); printf("Built-in commands:\n"); /* First, determine max width of command names */ unsigned int max_len = 0; for (int i = 0; i < shell_commands_len; ++i) { if (!shell_descript[i]) continue; if (strlen(shell_commands[i]) > max_len) { max_len = strlen(shell_commands[i]); } } /* Then print the commands their help text */ for (int i = 0; i < shell_commands_len; ++i) { if (!shell_descript[i]) continue; printf(" %-*s - %s\n", max_len + 1, shell_commands[i], shell_descript[i]); } return 0; } uint32_t shell_cmd_if(int argc, char * argv[]) { char ** if_args = &argv[1]; char ** then_args = NULL; char ** else_args = NULL; for (int i = 2; i < argc; ++i) { if (!strcmp(argv[i],"then")) { argv[i] = NULL; then_args = &argv[i+1]; } else if (!strcmp(argv[i],"else")) { argv[i] = NULL; else_args = &argv[i+1]; } } if (!then_args) { fprintf(stderr, "%s: syntax error: expected 'then' clause\n", argv[0]); return 1; } if (else_args && else_args < then_args) { fprintf(stderr, "%s: syntax error: 'else' clause before 'then' clase\n", argv[0]); return 1; } pid_t child_pid = fork(); if (!child_pid) { set_pgid(0); set_pgrp(getpid()); is_subshell = 1; run_cmd(if_args); } int pid, ret_code = 0; do { pid = waitpid(child_pid, &ret_code, 0); } while (pid != -1 || (pid == -1 && errno != ECHILD)); handle_status(ret_code); if (WEXITSTATUS(ret_code) == 0) { shell_command_t func = shell_find(*then_args); if (func) { int argc = 0; while (then_args[argc]) { argc++; } return func(argc, then_args); } else { child_pid = fork(); if (!child_pid) { set_pgid(0); set_pgrp(getpid()); is_subshell = 1; run_cmd(then_args); } do { pid = waitpid(child_pid, &ret_code, 0); } while (pid != -1 || (pid == -1 && errno != ECHILD)); reset_pgrp(); handle_status(ret_code); return WEXITSTATUS(ret_code); } } else if (else_args) { shell_command_t func = shell_find(*else_args); if (func) { int argc = 0; while (else_args[argc]) { argc++; } return func(argc, else_args); } else { child_pid = fork(); if (!child_pid) { set_pgid(0); set_pgrp(getpid()); is_subshell = 1; run_cmd(else_args); } do { pid = waitpid(child_pid, &ret_code, 0); } while (pid != -1 || (pid == -1 && errno != ECHILD)); handle_status(ret_code); return WEXITSTATUS(ret_code); } } reset_pgrp(); return 0; } uint32_t shell_cmd_while(int argc, char * argv[]) { char ** while_args = &argv[1]; char ** do_args = NULL; for (int i = 2; i < argc; ++i) { if (!strcmp(argv[i],"do")) { argv[i] = NULL; do_args = &argv[i+1]; } } if (!do_args) { fprintf(stderr, "%s: syntax error: expected 'do' clause\n", argv[0]); return 1; } break_while = 0; reset_pgrp(); do { pid_t child_pid = fork(); if (!child_pid) { set_pgid(0); set_pgrp(getpid()); is_subshell = 1; run_cmd(while_args); } int pid, ret_code = 0; do { pid = waitpid(child_pid, &ret_code, 0); } while (pid != -1 || (pid == -1 && errno != ECHILD)); handle_status(ret_code); if (WEXITSTATUS(ret_code) == 0) { child_pid = fork(); if (!child_pid) { set_pgid(0); set_pgrp(getpid()); is_subshell = 1; run_cmd(do_args); } do { pid = waitpid(child_pid, &ret_code, 0); if (pid != -1 && WIFSIGNALED(ret_code)) { int sig = WTERMSIG(ret_code); if (sig == SIGINT) return 127; /* break */ } } while (pid != -1 || (pid == -1 && errno != ECHILD)); } else { reset_pgrp(); return WEXITSTATUS(ret_code); } } while (!break_while); return 127; } uint32_t shell_cmd_export_cmd(int argc, char * argv[]) { if (argc < 3) { fprintf(stderr, "%s: syntax error: not enough arguments\n", argv[0]); return 1; } int pipe_fds[2]; pipe(pipe_fds); pid_t child_pid = fork(); if (!child_pid) { set_pgid(0); set_pgrp(getpid()); is_subshell = 1; dup2(pipe_fds[1], STDOUT_FILENO); close(pipe_fds[0]); run_cmd(&argv[2]); } close(pipe_fds[1]); char buf[1024]; size_t accum = 0; do { int r = read(pipe_fds[0], buf+accum, 1023-accum); if (r == 0) break; if (r < 0) { return -r; } accum += r; } while (accum < 1023); waitpid(child_pid, NULL, 0); reset_pgrp(); buf[accum] = '\0'; if (accum && buf[accum-1] == '\n') { buf[accum-1] = '\0'; } setenv(argv[1], buf, 1); return 0; } uint32_t shell_cmd_empty(int argc, char * argv[]) { for (int i = 1; i < argc; i++) { if (argv[i] && *argv[i]) return 1; } return 0; } uint32_t shell_cmd_equals(int argc, char * argv[]) { if (argc < 3) return 1; return !!strcmp(argv[1], argv[2]); } uint32_t shell_cmd_return(int argc, char * argv[]) { if (argc < 2) return 0; return atoi(argv[1]); } uint32_t shell_cmd_source(int argc, char * argv[]) { if (argc < 2) return 0; FILE * f = fopen(argv[1], "r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], strerror(errno)); return 1; } current_file = argv[1]; return run_script(f); } uint32_t shell_cmd_exec(int argc, char * argv[]) { if (argc < 2) return 1; return execvp(argv[1], &argv[1]); } uint32_t shell_cmd_not(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "%s: expected command argument\n", argv[0]); return 1; } int ret_code = 0; pid_t child_pid = fork(); if (!child_pid) { set_pgid(0); set_pgrp(getpid()); is_subshell = 1; run_cmd(&argv[1]); } do { pid = waitpid(child_pid, &ret_code, 0); } while (pid != -1 || (pid == -1 && errno != ECHILD)); reset_pgrp(); handle_status(ret_code); return !WEXITSTATUS(ret_code); } uint32_t shell_cmd_unset(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "%s: expected command argument\n", argv[0]); return 1; } if (unsetenv(argv[1]) < 0) { fprintf(stderr, "%s: unsetenv: %s\n", argv[0], strerror(errno)); return 1; } return 0; } uint32_t shell_cmd_read(int argc, char * argv[]) { int raw = 0; int i = 1; char * var = "REPLY"; if (i < argc && !strcmp(argv[i], "-r")) { raw = 1; i++; } if (i < argc) { var = argv[i]; } char tmp[4096]; fgets(tmp, 4096, stdin); if (*tmp && tmp[strlen(tmp)-1] == '\n') { tmp[strlen(tmp)-1] = '\0'; } if (raw) { setenv(var, tmp, 1); return 0; } char tmp2[4096] = {0}; char * out = tmp2; char * in = tmp; /* TODO: This needs to actually read more if a \ at the end of the line is found */ while (*in) { if (*in == '\\') { in++; if (*in == '\n') { in++; } } else { *out = *in; out++; in++; } } setenv(var, tmp2, 1); return 0; } int get_available_job(int argc, char * argv[]) { if (argc < 2) { if (!suspended_pgid) { list_t * keys = hashmap_keys(job_hash); foreach(node, keys) { suspended_pgid = (intptr_t)node->value; break; } list_free(keys); free(keys); if (!suspended_pgid) { return 0; } } return suspended_pgid; } else { return atoi(argv[1]); } } uint32_t shell_cmd_fg(int argc, char * argv[]) { int pid = get_available_job(argc,argv); if (!pid || !hashmap_has(job_hash, (void*)(intptr_t)pid)) { fprintf(stderr, "no current job\n"); return 1; } set_pgrp(pid); if (kill(-pid, SIGCONT) < 0) { reset_pgrp(); fprintf(stderr, "no current job / bad pid\n"); if (hashmap_has(job_hash, (void*)(intptr_t)pid)) { hashmap_remove(job_hash, (void*)(intptr_t)pid); hashmap_remove(desc_hash, (void*)(intptr_t)pid); } return 1; } return wait_for_child(pid, NULL, pid); } static void describe_job(pid_t pid) { fprintf(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)); } uint32_t shell_cmd_bg(int argc, char * argv[]) { int pid = get_available_job(argc,argv); if (!pid || !hashmap_has(job_hash, (void*)(intptr_t)pid)) { fprintf(stderr, "no current job\n"); return 1; } if (kill(-pid, SIGCONT) < 0) { fprintf(stderr, "no current job / bad pid\n"); if (hashmap_has(job_hash, (void*)(intptr_t)pid)) { hashmap_remove(job_hash, (void*)(intptr_t)pid); hashmap_remove(desc_hash, (void*)(intptr_t)pid); } return 1; } describe_job(pid); return 0; } uint32_t shell_cmd_jobs(int argc, char * argv[]) { list_t * keys = hashmap_keys(job_hash); foreach(node, keys) { int pid = (intptr_t)node->value; describe_job(pid); } list_free(keys); free(keys); return 0; } uint32_t shell_cmd_rehash(int argc, char * argv[]) { /* PATH commands are malloc'd */ for (int i = 0; i < shell_commands_len; ++i) { if (!shell_pointers[i]) { free(shell_commands[i]); } } /* Clear array */ shell_commands_len = 0; /* Reset capacity */ SHELL_COMMANDS = 64; /* Free existing */ free(shell_commands); free(shell_pointers); free(shell_descript); /* Reallocate + install builtins */ install_commands(); /* Reload PATH */ add_path(); return 0; } uint32_t shell_cmd_time(int argc, char * argv[]) { int pid, ret_code = 0; struct timeval start, end; gettimeofday(&start, NULL); struct tms timeBefore; times(&timeBefore); if (argc > 1) { pid_t child_pid = fork(); if (!child_pid) { set_pgid(0); set_pgrp(getpid()); is_subshell = 1; run_cmd(&argv[1]); } do { pid = waitpid(child_pid, &ret_code, 0); } while (pid != -1 || (pid == -1 && errno != ECHILD)); reset_pgrp(); handle_status(ret_code); } gettimeofday(&end, NULL); time_t sec_diff = end.tv_sec - start.tv_sec; suseconds_t usec_diff = end.tv_usec - start.tv_usec; if (end.tv_usec < start.tv_usec) { sec_diff -= 1; usec_diff = (1000000 + end.tv_usec) - start.tv_usec; } int minutes = sec_diff / 60; sec_diff = sec_diff % 60; fprintf(shell_stderr, "\nreal\t%dm%d.%.03ds\n", minutes, (int)sec_diff, (int)(usec_diff / 1000)); /* User and system times from children */ struct tms timeBuf; times(&timeBuf); fprintf(shell_stderr, "user\t%dm%d.%.03ds\n", (int)(((timeBuf.tms_cutime - timeBefore.tms_cutime) / (60 * CLOCKS_PER_SEC))), (int)(((timeBuf.tms_cutime - timeBefore.tms_cutime) / (CLOCKS_PER_SEC)) % 60), (int)(((timeBuf.tms_cutime - timeBefore.tms_cutime) / (CLOCKS_PER_SEC / 1000)) % 1000)); fprintf(shell_stderr, "sys\t%dm%d.%.03ds\n", (int)(((timeBuf.tms_cstime - timeBefore.tms_cstime) / (60 * CLOCKS_PER_SEC))), (int)(((timeBuf.tms_cstime - timeBefore.tms_cstime) / (CLOCKS_PER_SEC)) % 60), (int)(((timeBuf.tms_cstime - timeBefore.tms_cstime) / (CLOCKS_PER_SEC / 1000)) % 1000)); return WEXITSTATUS(ret_code); } void install_commands() { shell_commands = malloc(sizeof(char *) * SHELL_COMMANDS); shell_pointers = malloc(sizeof(shell_command_t) * SHELL_COMMANDS); shell_descript = malloc(sizeof(char *) * SHELL_COMMANDS); shell_install_command("cd", shell_cmd_cd, "change directory"); shell_install_command("exit", shell_cmd_exit, "exit the shell"); shell_install_command("export", shell_cmd_export, "set environment variables: export VAR=value"); shell_install_command("help", shell_cmd_help, "display this help text"); shell_install_command("history", shell_cmd_history, "list command history"); shell_install_command("if", shell_cmd_if, "if ... then ... [else ...]"); shell_install_command("while", shell_cmd_while, "while ... do ..."); shell_install_command("empty?", shell_cmd_empty, "empty? args..."); shell_install_command("equals?", shell_cmd_equals, "equals? arg1 arg2"); shell_install_command("return", shell_cmd_return, "return status code"); shell_install_command("export-cmd", shell_cmd_export_cmd, "set variable to result of command: export-cmd VAR command..."); shell_install_command("source", shell_cmd_source, "run a shell script in the context of this shell"); shell_install_command(".", shell_cmd_source, "alias for 'source'"); shell_install_command("exec", shell_cmd_exec, "replace shell (or subshell) with command"); shell_install_command("not", shell_cmd_not, "invert status of command"); shell_install_command("unset", shell_cmd_unset, "unset variable"); shell_install_command("read", shell_cmd_read, "read user input"); shell_install_command("fg", shell_cmd_fg, "resume a suspended job"); shell_install_command("jobs", shell_cmd_jobs, "list stopped jobs"); shell_install_command("bg", shell_cmd_bg, "restart suspended job in the background"); shell_install_command("rehash", shell_cmd_rehash, "reset shell command memory"); shell_install_command("time", shell_cmd_time, "time a command"); } ================================================ FILE: apps/false.c ================================================ /** * @brief false - returns a failure status code. * * @copyright * 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 */ int main() { return 1; } ================================================ FILE: apps/fetch.c ================================================ /** * @file apps/fetch.c * @brief Obtain files over HTTP. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define SIZE 512 #define BOUNDARY "------ToaruOSFetchUploadBoundary" struct http_req { char domain[SIZE]; char path[SIZE]; int port; int ssl; }; 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}; int parse_url(char * d, struct http_req * r) { if (strstr(d, "http://") == d) { d += strlen("http://"); r->port = 80; r->ssl = 0; } else if (strstr(d, "https://") == d) { d += strlen("https://"); r->port = 443; r->ssl = 1; } else { fprintf(stderr, "Unrecognized protocol: %s\n", d); return 1; } 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); } if (strstr(r->domain,":")) { char * port = strstr(r->domain,":"); *port = '\0'; port++; r->port = atoi(port); } return 0; } #define BAR_WIDTH 20 #define bar_perc "||||||||||||||||||||" #define bar_spac " " void print_progress(int force) { static uint64_t last_size = 0; if (!fetch_options.show_progress) return; if (!force && (last_size + 102400 > fetch_options.size)) return; last_size = fetch_options.size; struct timeval now; gettimeofday(&now, NULL); fprintf(stderr,"\033[?25l\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 (!force && 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); } } else { fprintf(stderr," (%.2f sec elapsed)", timediff); } } fprintf(stderr,"\033[K\033[?25h"); 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; } #define MAX_HTTP_LINE 1024 void read_http_line(char * buf, FILE * f) { memset(buf, 0x00, MAX_HTTP_LINE); fgets(buf, MAX_HTTP_LINE-1, 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[MAX_HTTP_LINE]; 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[MAX_HTTP_LINE]; 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; gettimeofday(&fetch_options.start, NULL); 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; print_progress(0); if (fetch_options.machine_readable && fetch_options.content_length) { fprintf(stdout,"%zu %zu\n",fetch_options.size,fetch_options.content_length); } bytes_to_read -= r; } print_progress(1); 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; if (parse_url(argv[optind], &my_req)) { return 1; } if (my_req.ssl) { /* TODO look for a viable backend. */ fprintf(stderr, "%s: no tls backend\n", argv[0]); return 1; } 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+"); if (!fetch_options.out) { perror("fopen"); return 1; } } int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return 1; } struct hostent * remote = gethostbyname(my_req.domain); if (!remote) { perror("gethostbyname"); return 1; } struct sockaddr_in addr; addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, remote->h_addr, remote->h_length); addr.sin_port = htons(my_req.port); if (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) { perror("connect"); return 1; } FILE * f = fdopen(sock,"w+"); 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]; size_t r = fread(buf, 1, 1024, in_file); fwrite(buf, 1, r, f); if (fetch_options.slow_upload) { usleep(1000 * fetch_options.slow_upload); /* TODO fix terrible network stack; hopefully this ensures we send stuff right. */ } } fclose(in_file); fprintf(f,"\r\n--" BOUNDARY "%08x--\r\n", boundary_fuzz); fflush(f); } else if (fetch_options.cookie) { fprintf(f, "GET /%s HTTP/1.0\r\n" "User-Agent: curl/7.35.0\r\n" "Host: %s\r\n" "Accept: */*\r\n" "Cookie: %s\r\n" "\r\n", my_req.path, my_req.domain, fetch_options.cookie); } else { fprintf(f, "GET /%s HTTP/1.0\r\n" "User-Agent: curl/7.35.0\r\n" "Host: %s\r\n" "Accept: */*\r\n" "\r\n", my_req.path, my_req.domain); } http_fetch(f); fflush(fetch_options.out); if (fetch_options.show_progress) { fprintf(stderr,"\n"); } if (fetch_options.machine_readable) { fprintf(stdout,"done\n"); } return 0; } ================================================ FILE: apps/file-browser.c ================================================ /** * @brief Graphical file browser * @file apps/file-browser.c * * Based on the original Python implementation and inspired by * Nautilus and Thunar. Also provides a "wallpaper" mode for * managing the desktop backgrond. * * @copyright * 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-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define APPLICATION_TITLE "File Browser" #define SCROLL_AMOUNT 120 #define WALLPAPER_PATH "/usr/share/wallpaper.jpg" struct File { char name[256]; /* Displayed name (icon label) */ char icon[256]; /* Icon identifier */ char link[256]; /* Link target for symlinks */ char launcher[256]; /* Launcher spec */ char filename[256]; /* Actual filename for launchers */ char filetype[256]; /* Textual description of file type */ uint64_t size; /* File size */ int type; /* File type: 0 = normal, 1 = directory, 2 = launcher */ int selected; /* Selection status */ }; static yutani_t * yctx; static yutani_window_t * main_window; static gfx_context_t * ctx; static int application_running = 1; /* Big loop exit condition */ static int show_hidden = 0; /* Whether or not show hidden files */ static int scroll_offset = 0; /* How far the icon view should be scrolled */ static int available_height = 0; /* How much space is available in the main window for the icon view */ static int is_desktop_background = 0; /* If we're in desktop background mode */ static int is_picker_dialog = 0; /* If we're an open-file dialog */ static int menu_bar_height = MENU_BAR_HEIGHT; /* Height of the menu bar, if present - it's not in desktop mode */ static int nav_bar_height = 36; static sprite_t * wallpaper_buffer = NULL; /* Prebaked wallpaper texture */ static sprite_t * wallpaper_old = NULL; /* Previous wallpaper when transitioning */ static uint64_t timer = 0; /* Timer for wallpaper transition fade */ static char title[512]; /* Application title bar */ static int FILE_HEIGHT = 80; /* Height of one row of icons */ static int FILE_WIDTH = 100; /* Width of one column of icons */ static int FILE_PTR_WIDTH = 1; /* How many icons wide the display should be */ static sprite_t * contents_sprite = NULL; /* Icon view rendering context */ static gfx_context_t * contents = NULL; /* Icon view rendering context */ static char * current_directory = NULL; /* Current directory path */ static int hilighted_offset = -1; /* Which file is hovered by the mouse */ static struct File ** file_pointers = NULL; /* List of file pointers */ static ssize_t file_pointers_len = 0; /* How many files are in the current list */ static uint64_t last_click = 0; /* For double click */ static int last_click_offset = -1; /* So that clicking two different things quickly doesn't count as a double click */ static struct TT_Font * tt_font_thin = NULL; static struct TT_Font * tt_font_bold = NULL; static struct MenuEntry * _menu_entry_show_icons = NULL; static struct MenuEntry * _menu_entry_show_tiles = NULL; static struct MenuEntry * _menu_entry_show_list = NULL; static struct MenuEntry * _menu_entry_up = NULL; static struct MenuEntry * _menu_entry_up_ctx_a = NULL; static struct MenuEntry * _menu_entry_up_ctx_b = NULL; /** * Navigation input box */ static char nav_bar[512] = {0}; static int nav_bar_cursor = 0; static int nav_bar_cursor_x = 0; static int nav_bar_focused = 0; static int nav_bar_blink = 0; static struct timeval nav_bar_last_blinked; /* Status bar displayed at the bottom of the window */ static char window_status[1024] = {0}; /* Button row visibility statuses */ static int _button_hilights[4] = {3,3,3,3}; static int _button_disabled[4] = {1,1,0,0}; static int _button_hover = -1; /* Menu bar entries */ static struct menu_bar menu_bar = {0}; static struct menu_bar_entries menu_entries[] = { {"File", "file"}, /* 0 */ {"Edit", "edit"}, /* 1 */ {"View", "view"}, /* 2 */ {"Go", "go"}, /* 3 */ {"Help", "help"}, /* 4 */ {NULL, NULL}, }; /* Right click context menu */ static struct MenuList * context_menu = NULL; static struct MenuList * directory_context_menu = NULL; /** * Accurate time comparison. * * These methods were taken from the compositor and * allow us to time double-clicks accurately. */ static uint64_t precise_current_time(void) { struct timeval t; gettimeofday(&t, NULL); time_t sec_diff = t.tv_sec; suseconds_t usec_diff = t.tv_usec; return (uint64_t)((uint64_t)sec_diff * 1000LL + usec_diff / 1000); } static uint64_t precise_time_since(uint64_t start_time) { uint64_t now = precise_current_time(); uint64_t diff = now - start_time; /* Milliseconds */ return diff; } /** * When in desktop mode, we fake decoration boundaries to * position the icon view correctly. When in normal mode, * we just passt through the actual bounds. */ static int _decor_get_bounds(yutani_window_t * win, struct decor_bounds * bounds) { if (is_desktop_background) { memset(bounds, 0, sizeof(struct decor_bounds)); bounds->height = 54; bounds->width = 20; bounds->top_height = 54; bounds->left_width = 20; return 0; } return decor_get_bounds(win, bounds); } /** * This should probably be in a yutani core library... * * If a down and up event were close enough together to be considered a click. */ static int _close_enough(struct yutani_msg_window_mouse_event * me) { if (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) { return 1; } return 0; } /** * Select view mode. */ #define VIEW_MODE_ICONS 0 #define VIEW_MODE_TILES 1 #define VIEW_MODE_LIST 2 static int view_mode = VIEW_MODE_ICONS; /** * Clear out the space for an icon. * We clear to transparent so that the desktop background can be shown in desktop mode. */ static void clear_offset(int offset) { /* From the flat array offset, figure out the x/y offset. */ int offset_y = offset / FILE_PTR_WIDTH; int offset_x = offset % FILE_PTR_WIDTH; draw_rectangle_solid(contents, offset_x * FILE_WIDTH, offset_y * FILE_HEIGHT, FILE_WIDTH, FILE_HEIGHT, rgba(0,0,0,0)); } static int print_human_readable_size(char * _out, uint64_t s) { if (s >= 1LL << 30) { size_t t = s / (1LL << 30); return sprintf(_out, "%d.%1d GiB", (int)t, (int)((int)(s - t * (1LL << 30)) / ((1LL << 30) / 10))); } else if (s >= 1<<20) { size_t t = s / (1 << 20); return sprintf(_out, "%d.%1d MiB", (int)t, (int)(s - t * (1 << 20)) / ((1 << 20) / 10)); } else if (s >= 1<<10) { size_t t = s / (1 << 10); return sprintf(_out, "%d.%1d KiB", (int)t, (int)(s - t * (1 << 10)) / ((1 << 10) / 10)); } else { return sprintf(_out, "%d B", (int)s); } } #define HILIGHT_BORDER_TOP rgb(54,128,205) #define HILIGHT_GRADIENT_TOP rgb(93,163,236) #define HILIGHT_GRADIENT_BOTTOM rgb(56,137,220) #define HILIGHT_BORDER_BOTTOM rgb(47,106,167) /** * Draw an icon view entry */ static void draw_file(struct File * f, int offset) { /* From the flat array offset, figure out the x/y offset. */ int offset_y = offset / FILE_PTR_WIDTH; int offset_x = offset % FILE_PTR_WIDTH; int x = offset_x * FILE_WIDTH; int y = offset_y * FILE_HEIGHT; /* Load the icon sprite from the cache */ if (view_mode == VIEW_MODE_ICONS) { sprite_t * icon = icon_get_48(f->icon); /* If the display name is too long to fit, cut it with an ellipsis. */ int name_width; char * name = tt_ellipsify(f->name, 13, tt_font_thin, FILE_WIDTH - 8, &name_width); /* Draw the icon */ int center_x_icon = (FILE_WIDTH - icon->width) / 2; int center_x_text = (FILE_WIDTH - name_width) / 2; draw_sprite(contents, icon, center_x_icon + x, y + 2); if (f->selected) { tt_set_size(tt_font_thin, 13); /* If this file is selected, paint the icon blue... */ if (main_window->focused) { draw_sprite_alpha_paint(contents, icon, center_x_icon + x, y + 2, 0.5, rgb(72,167,255)); draw_rounded_rectangle(contents, center_x_text + x - 2, y + 53, name_width + 6, 20, 3, rgb(72,167,255)); tt_draw_string(contents, tt_font_thin, center_x_text + x, y + 54 + 13, name, rgb(255,255,255)); } else { draw_rounded_rectangle(contents, center_x_text + x - 2, y + 53, name_width + 6, 20, 3, rgb(190,190,190)); tt_draw_string(contents, tt_font_thin, center_x_text + x, y + 54 + 13, name, rgb(20,20,20)); } } else { if (is_desktop_background) { /* If this is the desktop view, white text with a drop shadow */ tt_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); tt_draw_string_shadow(contents, tt_font_thin, name, 13, center_x_text + x, y + 54, rgb(255,255,255), rgb(0,0,0), 4); } else { /* Otherwise, black text */ tt_draw_string(contents, tt_font_thin, center_x_text + x, y + 54 + 13, name, rgb(0,0,0)); } } if (offset == hilighted_offset) { /* The hovered icon should have some added brightness, so paint it white */ draw_sprite_alpha_paint(contents, icon, center_x_icon + x, y + 2, 0.3, rgb(255,255,255)); } if (f->link[0]) { /* For symlinks, draw an indicator */ sprite_t * arrow = icon_get_16("forward"); draw_sprite(contents, arrow, center_x_icon + 32 + x, y + 32); } free(name); } else if (view_mode == VIEW_MODE_TILES) { sprite_t * icon = icon_get_48(f->icon); uint32_t text_color = rgb(0,0,0); /* If selected, draw background box */ if (f->selected) { struct gradient_definition edge = {FILE_HEIGHT - 4, y+2, HILIGHT_BORDER_TOP, HILIGHT_BORDER_BOTTOM}; struct gradient_definition body = {FILE_HEIGHT - 6, y+3, HILIGHT_GRADIENT_TOP, HILIGHT_GRADIENT_BOTTOM}; draw_rounded_rectangle_pattern(contents, x + 2,y + 2, FILE_WIDTH-4, FILE_HEIGHT-4, 3, gfx_vertical_gradient_pattern, &edge); draw_rounded_rectangle_pattern(contents, x + 3,y + 3, FILE_WIDTH-6, FILE_HEIGHT-6, 4, gfx_vertical_gradient_pattern, &body); text_color = rgb(255,255,255); } draw_sprite(contents, icon, x + 11, y + 11); if (offset == hilighted_offset) { /* The hovered icon should have some added brightness, so paint it white */ draw_sprite_alpha_paint(contents, icon, x + 11, y + 11, 0.3, rgb(255,255,255)); } char * name = tt_ellipsify(f->name, 13, tt_font_bold, FILE_WIDTH - 81, NULL); char * type = tt_ellipsify(f->filetype, 13, tt_font_thin, FILE_WIDTH - 81, NULL); if (f->type == 0) { tt_set_size(tt_font_bold, 13); tt_draw_string(contents, tt_font_bold, x + 70, y + 8 + 13, name, text_color); tt_set_size(tt_font_thin, 13); tt_draw_string(contents, tt_font_thin, x + 70, y + 25 + 13, type, text_color); char line_three[48] = {0}; if (*f->link) { sprintf(line_three, "Symbolic link"); } else { print_human_readable_size(line_three, f->size); } tt_draw_string(contents, tt_font_thin, x + 70, y + 42 + 13, line_three, text_color); } else { tt_set_size(tt_font_bold, 13); tt_draw_string(contents, tt_font_bold, x + 70, y + 15 + 13, name, text_color); tt_set_size(tt_font_thin, 13); tt_draw_string(contents, tt_font_thin, x + 70, y + 32 + 13, type, text_color); } free(name); free(type); } else if (view_mode == VIEW_MODE_LIST) { sprite_t * icon = icon_get_16(f->icon); uint32_t text_color = rgb(0,0,0); if (f->selected) { struct gradient_definition edge = {FILE_HEIGHT - 4, y+2, HILIGHT_BORDER_TOP, HILIGHT_BORDER_BOTTOM}; struct gradient_definition body = {FILE_HEIGHT - 6, y+3, HILIGHT_GRADIENT_TOP, HILIGHT_GRADIENT_BOTTOM}; draw_rounded_rectangle_pattern(contents, x + 2,y + 2, FILE_WIDTH-4, FILE_HEIGHT-4, 3, gfx_vertical_gradient_pattern, &edge); draw_rounded_rectangle_pattern(contents, x + 3,y + 3, FILE_WIDTH-6, FILE_HEIGHT-6, 4, gfx_vertical_gradient_pattern, &body); text_color = rgb(255,255,255); } else if (offset == hilighted_offset) { draw_rounded_rectangle(contents, x + 2, y + 2, FILE_WIDTH - 4, FILE_HEIGHT - 4, 3, rgb(180,180,180)); draw_rounded_rectangle(contents, x + 3, y + 3, FILE_WIDTH - 6, FILE_HEIGHT - 6, 4, rgb(255,255,255)); } if (icon->width != 16 || icon->height != 16) { draw_sprite_scaled(contents, icon, x + 4, y + 4, 16, 16); } else { draw_sprite(contents, icon, x + 4, y + 4); } char * name = tt_ellipsify(f->name, 13, tt_font_thin, FILE_WIDTH - 26, NULL); tt_set_size(tt_font_thin, 13); tt_draw_string(contents, tt_font_thin, x + 24, y + 15, name, text_color); free(name); } } /** * Get file from array offset, with bounds check */ static struct File * get_file_at_offset(int offset) { if (offset >= 0 && offset < file_pointers_len) { return file_pointers[offset]; } return NULL; } /** * Redraw all icon view entries */ static void redraw_files(void) { /* Fill to blank */ draw_fill(contents, rgba(0,0,0,0)); for (int i = 0; i < file_pointers_len; ++i) { draw_file(file_pointers[i], i); } } /** * Set the application title. */ static void set_title(char * directory) { /* Do nothing in desktop mode to avoid advertisement. */ if (is_desktop_background) return; if (is_picker_dialog) { /* TODO: Maybe make this an option... */ sprintf(title, "Open File"); } else if (directory) { sprintf(title, "%s - " APPLICATION_TITLE, directory); } else { /* Otherwise, just "File Browser" */ sprintf(title, APPLICATION_TITLE); } /* Advertise to the panel */ yutani_window_advertise_icon(yctx, main_window, title, "folder"); } /** * Check if a file name ends with an extension. * * Can also be used for exact matches. */ static int has_extension(struct File * f, char * extension) { int i = strlen(f->name); int j = strlen(extension); do { if (f->name[i] != (extension)[j]) break; if (j == 0) return 1; if (i == 0) break; i--; j--; } while (1); return 0; } /** * Forward/backward history; we're always in the middle of these. * When we navigate somewhere new, clear the forward history, but * keep the back history and append the previous location. */ static list_t * history_back; static list_t * history_forward; /** * Update the status bar text at the bottom of the window * based on the selected items in the file view. */ static void update_status(void) { uint64_t total_size = 0; uint64_t selected_size = 0; int selected_count = 0; struct File * selected = NULL; for (int i = 0; i < file_pointers_len; ++i) { total_size += file_pointers[i]->size; if (file_pointers[i]->selected) { selected_count += 1; selected_size += file_pointers[i]->size; selected = file_pointers[i]; } } char tmp_size[50]; if (selected_count == 0) { print_human_readable_size(tmp_size, total_size); sprintf(window_status, "%zd item%s (%s)", file_pointers_len, file_pointers_len == 1 ? "" : "s", tmp_size); } else if (selected_count == 1) { print_human_readable_size(tmp_size, selected->size); sprintf(window_status, "\"%s\" (%s) %s", selected->name, tmp_size, selected->filetype); } else { print_human_readable_size(tmp_size, selected_size); sprintf(window_status, "%d items selected (%s)", selected_count, tmp_size); } } /** * Read the contents of a directory into the icon view. */ static void load_directory(const char * path, int modifies_history) { /* Free the current icon view entries */ DIR * dirp = opendir(path); if (!dirp) { /* * Display a warning dialog with the appropriate error message for * why this directory failed to load. XXX This uses `showdialog` * but it should use a dialog library like with the buttons. */ if (!fork()) { char tmp[512]; sprintf(tmp, "Could not open directory \"%s\": %s", path, strerror(errno)); char * args[] = {"showdialog","File Browser","/usr/share/icons/48/folder.png",tmp,NULL}; execvp(args[0],args); exit(0); } return; } /* Free the previously loaded directory */ if (file_pointers) { for (int i = 0; i < file_pointers_len; ++i) { free(file_pointers[i]); } free(file_pointers); } if (modifies_history) { /* Clear forward history */ list_destroy(history_forward); list_free(history_forward); free(history_forward); history_forward = list_create(); /* Append current pointer */ if (current_directory) { list_insert(history_back, strdup(current_directory)); } } if (current_directory) { free(current_directory); } /* Set button displays appropriately */ _button_disabled[0] = !(history_back->length); /* Back */ _button_disabled[1] = !(history_forward->length); /* Forward */ _button_disabled[2] = 0; /* Up */ _button_disabled[3] = 0; /* Home */ char * home = getenv("HOME"); if (home && !strcmp(path, home)) { /* If the current directory is the user's homedir, present it that way in the title */ set_title("Home"); /* Disable the 'go home' button */ _button_disabled[3] = 1; } else if (!strcmp(path, "/")) { set_title("File System"); /* If this is the root of the file system, disable the up button */ _button_disabled[2] = 1; } else { /* Otherwise use just the directory base name */ char * tmp = strdup(path); char * base = basename(tmp); set_title(base); free(tmp); } extern void menu_update_enabled(struct MenuEntry*, int); if (_menu_entry_up) menu_update_enabled(_menu_entry_up, !_button_disabled[2]); if (_menu_entry_up_ctx_a) menu_update_enabled(_menu_entry_up_ctx_a, !_button_disabled[2]); if (_menu_entry_up_ctx_b) menu_update_enabled(_menu_entry_up_ctx_b, !_button_disabled[2]); /* If we ended up in a path with //two/initial/slashes, fix that. */ if (path[0] == '/' && path[1] == '/') { current_directory = strdup(path+1); } else { current_directory = strdup(path); } strcpy(nav_bar, current_directory); /* TODO: Show relative time informaton... */ #if 0 /* Get the current time */ struct tm * timeinfo; struct timeval now; gettimeofday(&now, NULL); //time(NULL); timeinfo = localtime((time_t *)&now.tv_sec); int this_year = timeinfo->tm_year; #endif list_t * file_list = list_create(); struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] == '.' && (ent->d_name[1] == '\0' || (ent->d_name[1] == '.' && ent->d_name[2] == '\0'))) { /* skip . and .. */ ent = readdir(dirp); continue; } if (show_hidden || (ent->d_name[0] != '.')) { /* Set display name from file name */ struct File * f = malloc(sizeof(struct File)); sprintf(f->name, "%s", ent->d_name); /* snprintf? copy min()? */ struct stat statbuf; struct stat statbufl; /* Calculate absolute path to file */ char tmp[strlen(path)+strlen(ent->d_name)+2]; sprintf(tmp, "%s/%s", path, ent->d_name); lstat(tmp, &statbuf); f->size = statbuf.st_size; /* Read link target for symlinks */ if (S_ISLNK(statbuf.st_mode)) { memcpy(&statbufl, &statbuf, sizeof(struct stat)); stat(tmp, &statbuf); readlink(tmp, f->link, 256); } else { f->link[0] = '\0'; } f->launcher[0] = '\0'; f->filetype[0] = '\0'; f->selected = 0; if (S_ISDIR(statbuf.st_mode)) { /* Is this /cdrom? */ if (!strcmp(tmp,"//cdrom")) { sprintf(f->icon, "cd"); } else { sprintf(f->icon, "folder"); } sprintf(f->filetype, "Directory"); f->type = 1; } else { /* Regular file */ /* Default regular files to open in bim */ sprintf(f->launcher, "exec terminal bim"); if (is_desktop_background && has_extension(f, ".launcher")) { /* In desktop mode, read launchers specially */ FILE * file = fopen(tmp,"r"); char tbuf[1024]; while (!feof(file)) { fgets(tbuf, 1024, file); char * nl = strchr(tbuf,'\n'); if (nl) *nl = '\0'; char * eq = strchr(tbuf,'='); if (!eq) continue; *eq = '\0'; eq++; if (!strcmp(tbuf, "icon")) { sprintf(f->icon, "%s", eq); } else if (!strcmp(tbuf, "run")) { sprintf(f->launcher, "%s #", eq); } else if (!strcmp(tbuf, "title")) { sprintf(f->name, eq); } } sprintf(f->filetype, "Launcher"); sprintf(f->filename, "%s", ent->d_name); f->type = 2; } else { /* Handle various file types */ if (has_extension(f, ".c")) { sprintf(f->icon, "c"); sprintf(f->filetype, "C Source"); } else if (has_extension(f, ".h")) { sprintf(f->icon, "h"); sprintf(f->filetype, "C Header"); } else if (has_extension(f, ".bmp")) { sprintf(f->icon, "image"); sprintf(f->launcher, "exec imgviewer"); sprintf(f->filetype, "Bitmap Image"); } else if (has_extension(f, ".tga")) { sprintf(f->icon, "image"); sprintf(f->launcher, "exec imgviewer"); sprintf(f->filetype, "Targa Image"); } else if (has_extension(f, ".jpg") || has_extension(f,".jpeg")) { sprintf(f->icon, "image"); sprintf(f->launcher, "exec imgviewer"); sprintf(f->filetype, "JPEG Image"); } else if (has_extension(f, ".png")) { sprintf(f->icon, "image"); sprintf(f->launcher, "exec imgviewer"); sprintf(f->filetype, "Portable Network Graphics Image"); } else if (has_extension(f, ".sdf")) { sprintf(f->icon, "font"); sprintf(f->filetype, "Legacy SDF Font"); } else if (has_extension(f, ".ttf")) { sprintf(f->icon, "font"); sprintf(f->launcher,"exec font-preview"); sprintf(f->filetype, "TrueType Font"); } else if (has_extension(f, ".pdf")) { sprintf(f->icon, "pdf"); sprintf(f->launcher,"exec maybe-pdfviewer.krk"); sprintf(f->filetype, "Portable Document Format"); } else if (has_extension(f, ".tgz") || has_extension(f, ".tar.gz")) { sprintf(f->icon, "package_targz"); sprintf(f->filetype, "Compressed Archive File"); /* TODO: Archive viewer */ } else if (has_extension(f, ".tar")) { sprintf(f->icon, "package_tar"); sprintf(f->filetype, "Archive File"); } else if (has_extension(f, ".a")) { sprintf(f->icon, "package_a"); sprintf(f->filetype, "Archive File"); } else if (has_extension(f, ".zip")) { sprintf(f->icon, "package_zip"); sprintf(f->filetype, "ZIP Archive File"); } else if (has_extension(f, ".sh")) { sprintf(f->icon, "sh"); if (statbuf.st_mode & 0111) { /* Make executable */ sprintf(f->launcher, "SELF"); sprintf(f->filetype, "Executable Shell Script"); } else { sprintf(f->filetype, "Shell Script"); } } else if (has_extension(f, ".krk")) { sprintf(f->icon, "krk"); if (statbuf.st_mode & 0111) { /* Make executable */ sprintf(f->launcher, "SELF"); sprintf(f->filetype, "Executable Kuroko Script"); } else { sprintf(f->filetype, "Kuroko Script"); } } else if (has_extension(f, ".py")) { sprintf(f->icon, "py"); if (statbuf.st_mode & 0111) { /* Make executable */ sprintf(f->launcher, "SELF"); sprintf(f->filetype, "Executable Python Script"); } else { sprintf(f->filetype, "Python Script"); } } else if (has_extension(f, ".ko")) { sprintf(f->icon, "so"); sprintf(f->filetype, "Kernel Module"); } else if (has_extension(f, ".o")) { sprintf(f->icon, "so"); sprintf(f->filetype, "Object File"); } else if (has_extension(f, ".so")) { sprintf(f->icon, "so"); sprintf(f->filetype, "Shared Object File"); } else if (has_extension(f, ".S")) { sprintf(f->icon, "file"); sprintf(f->filetype, "Assembly Source"); } else if (has_extension(f, ".ld")) { sprintf(f->icon, "file"); sprintf(f->filetype, "Linker Script"); } else if (has_extension(f, ".md")) { sprintf(f->icon, "file"); sprintf(f->filetype, "Markdown Text Document"); } else if (has_extension(f, ".eshrc")) { sprintf(f->icon, "sh"); sprintf(f->filetype, "Shell Configuration"); } else if (has_extension(f, ".bim3rc")) { sprintf(f->icon, "krk"); sprintf(f->filetype, "Bim Configuration"); } else if (has_extension(f, ".biminfo")) { sprintf(f->icon, "file"); sprintf(f->filetype, "Bim Status Cache"); } else if (has_extension(f, ".conf")) { sprintf(f->icon, "file"); sprintf(f->filetype, "Configuration File"); } else if (has_extension(f, ".launcher")) { sprintf(f->icon, "file"); sprintf(f->filetype, "Application Launcher"); } else if (has_extension(f, ".trt")) { sprintf(f->icon, "file"); sprintf(f->filetype, "Toaru Rich Text Document"); sprintf(f->launcher, "exec help-browser"); } else if (has_extension(f, ".json")) { sprintf(f->icon, "file"); sprintf(f->filetype, "JavaScript Object Notation File"); } else if (statbuf.st_mode & 0111) { /* Executable files - use their name for their icon, and launch themselves. */ sprintf(f->icon, "%s", f->name); sprintf(f->launcher, "SELF"); sprintf(f->filetype, "Executable"); } else { sprintf(f->icon, "file"); sprintf(f->filetype, "File"); } f->type = 0; } } list_insert(file_list, f); } ent = readdir(dirp); } closedir(dirp); /* Store the entries in a flat array. */ file_pointers = malloc(sizeof(struct File *) * file_list->length); file_pointers_len = file_list->length; int i = 0; foreach (node, file_list) { file_pointers[i] = node->value; i++; } update_status(); /* Free our temporary linked list */ list_free(file_list); free(file_list); /* Sort files */ int comparator(const void * c1, const void * c2) { const struct File * f1 = *(const struct File **)(c1); const struct File * f2 = *(const struct File **)(c2); /* Launchers before directories before files */ if (f1->type > f2->type) return -1; if (f2->type > f1->type) return 1; /* Launchers sorted by filename, not by display name */ if (f1->type == 2 && f2->type == 2) { return strcmp(f1->filename, f2->filename); } /* Files sorted by name */ return strcmp(f1->name, f2->name); } qsort(file_pointers, file_pointers_len, sizeof(struct File *), comparator); /* Reset scroll offset when navigating */ scroll_offset = 0; } /** * Resize and redraw the icon view */ static void reinitialize_contents(void) { /* If there already is a context, free it. */ if (contents) { free(contents); } /* If there already is a context buffer, free it. */ if (contents_sprite) { sprite_free(contents_sprite); } /* Get window bounds to determine how wide we can make our icon view */ struct decor_bounds bounds; _decor_get_bounds(main_window, &bounds); if (is_desktop_background) { /** * TODO: Actually calculate an optimal FILE_PTR_WIDTH or fix this to * work properly with vertical rows of files */ FILE_PTR_WIDTH = 1; } else if (view_mode == VIEW_MODE_LIST) { FILE_PTR_WIDTH = 1; FILE_WIDTH = (ctx->width - bounds.width); } else if (view_mode == VIEW_MODE_TILES) { /* We want to get close to 260 */ int avail = ctx->width - bounds.width; int columns = avail / 260; FILE_WIDTH = avail / columns; FILE_PTR_WIDTH = (ctx->width - bounds.width) / FILE_WIDTH; } else if (view_mode == VIEW_MODE_ICONS) { int avail = ctx->width - bounds.width; int columns = avail / 100; FILE_WIDTH = avail / columns; FILE_PTR_WIDTH = (ctx->width - bounds.width) / FILE_WIDTH; } else { FILE_PTR_WIDTH = (ctx->width - bounds.width) / FILE_WIDTH; } /* Calculate required height to fit files */ int calculated_height = (file_pointers_len / FILE_PTR_WIDTH + !!(file_pointers_len % FILE_PTR_WIDTH)) * FILE_HEIGHT; /* Create buffer */ contents_sprite = create_sprite(FILE_PTR_WIDTH * FILE_WIDTH, calculated_height, ALPHA_EMBEDDED); contents = init_graphics_sprite(contents_sprite); /* Draw file entries */ redraw_files(); } #define BUTTON_SPACE 34 #define BUTTON_COUNT 4 /** * Render toolbar buttons */ static void _draw_buttons(struct decor_bounds bounds) { /* Draws the toolbar background as a gradient; XXX hardcoded theme details */ uint32_t gradient_top = rgb(59,59,59); uint32_t gradient_bot = rgb(40,40,40); for (int i = 0; i < 36; ++i) { uint32_t c = interp_colors(gradient_top, gradient_bot, i * 255 / 36); draw_rectangle(ctx, bounds.left_width, bounds.top_height + menu_bar_height + i, BUTTON_SPACE * BUTTON_COUNT, 1, c); } int x = 0; int i = 0; #define draw_button(label) do { \ struct 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)}; \ ttk_button_draw(ctx, &_up); \ x += BUTTON_SPACE; i++; } while (0) /* Draw actual buttons */ draw_button("back"); draw_button("forward"); draw_button("up"); draw_button("home"); } /** * Determine what character offset the cursor should be at for * a given X coordinate. */ static void _figure_out_navbar_cursor(int x, struct decor_bounds bounds) { x = x - bounds.left_width - 2 - BUTTON_SPACE * BUTTON_COUNT - 5; if (x <= 0) { nav_bar_cursor_x = 0; return; } char * tmp = strdup(nav_bar); int candidate = 0; tt_set_size(tt_font_thin, 13); while (*tmp && x + 2 < (candidate = tt_string_width(tt_font_thin, tmp))) { tmp[strlen(tmp)-1] = '\0'; } nav_bar_cursor_x = candidate; nav_bar_cursor = strlen(tmp); free(tmp); } /** * Recalculate the location of the cursor indicator bar * based on the current cursor character offset; * also handles cursor bounds within the text * (eg. to avoid cursor moving beyond the beginning) */ static void _recalculate_nav_bar_cursor(void) { if (nav_bar_cursor < 0) { nav_bar_cursor = 0; } if (nav_bar_cursor > (int)strlen(nav_bar)) { nav_bar_cursor = strlen(nav_bar); } char * tmp = strdup(nav_bar); tmp[nav_bar_cursor] = '\0'; tt_set_size(tt_font_thin, 13); nav_bar_cursor_x = tt_string_width(tt_font_thin, tmp); free(tmp); } /** * Draw the navigation input box. */ static void _draw_nav_bar(struct decor_bounds bounds) { /* Draw toolbar background */ uint32_t gradient_top = rgb(59,59,59); uint32_t gradient_bot = rgb(40,40,40); int x = BUTTON_SPACE * BUTTON_COUNT; for (int i = 0; i < 36; ++i) { uint32_t c = interp_colors(gradient_top, gradient_bot, i * 255 / 36); draw_rectangle(ctx, bounds.left_width + BUTTON_SPACE * BUTTON_COUNT, bounds.top_height + menu_bar_height + i, ctx->width - bounds.width - BUTTON_SPACE * BUTTON_COUNT, 1, c); } /* Draw input box */ if (nav_bar_focused && main_window->focused) { struct gradient_definition edge = {28, bounds.top_height + menu_bar_height + 3, rgb(0,120,220), rgb(0,120,220)}; draw_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); draw_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)); } else { struct gradient_definition edge = {28, bounds.top_height + menu_bar_height + 3, rgb(90,90,90), rgb(110,110,110)}; draw_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); draw_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)); } /* Draw the nav bar text, ellipsified if needed */ int max_width = main_window->width - bounds.width - x - 12; char * name = tt_ellipsify(nav_bar, 13, tt_font_thin, max_width, NULL); tt_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)); free(name); if (nav_bar_focused && main_window->focused && !nav_bar_blink) { /* Draw cursor indicator at cursor_x */ draw_line(ctx, bounds.left_width + 2 + x + 5 + nav_bar_cursor_x, bounds.left_width + 2 + x + 5 + nav_bar_cursor_x, bounds.top_height + menu_bar_height + 8, bounds.top_height + menu_bar_height + 8 + 15, rgb(0,0,0)); } } #define STATUS_HEIGHT 24 /** * Draw the status bar at the bottom of the window */ static void _draw_status(struct decor_bounds bounds) { /* Background gradient */ uint32_t gradient_top = rgb(80,80,80); uint32_t gradient_bot = rgb(59,59,59); draw_rectangle(ctx, bounds.left_width, ctx->height - bounds.bottom_height - STATUS_HEIGHT, ctx->width - bounds.width, 1, rgb(110,110,110) ); for (int i = 1; i < STATUS_HEIGHT; ++i) { uint32_t c = interp_colors(gradient_top, gradient_bot, i * 255 / STATUS_HEIGHT); draw_rectangle(ctx, bounds.left_width, ctx->height - bounds.bottom_height - STATUS_HEIGHT + i, ctx->width - bounds.width, 1, c ); } /* Text with draw shadow */ { sprite_t * _tmp_s = create_sprite(ctx->width - bounds.width - 4, STATUS_HEIGHT-3, ALPHA_EMBEDDED); gfx_context_t * _tmp = init_graphics_sprite(_tmp_s); draw_fill(_tmp, rgba(0,0,0,0)); tt_set_size(tt_font_thin, 13); tt_draw_string(_tmp, tt_font_thin, 1, 14, window_status, rgb(0,0,0)); blur_context_box(_tmp, 4); tt_draw_string(_tmp, tt_font_thin, 0, 13, window_status, rgb(255,255,255)); free(_tmp); draw_sprite(ctx, _tmp_s, bounds.left_width + 4, ctx->height - bounds.bottom_height - STATUS_HEIGHT + 3); sprite_free(_tmp_s); } } /** * Redraw the navigation input box (while typing) */ static void _redraw_nav_bar(void) { struct decor_bounds bounds; _decor_get_bounds(main_window, &bounds); _draw_nav_bar(bounds); flip(ctx); yutani_flip(yctx, main_window); } /** * Blink the navbar cursor, maybe. */ static void maybe_blink_cursor(void) { if (!nav_bar_focused) return; struct timeval t; gettimeofday(&t, NULL); time_t sec_diff = t.tv_sec - nav_bar_last_blinked.tv_sec; suseconds_t usec_diff = t.tv_usec - nav_bar_last_blinked.tv_usec; if (t.tv_usec < nav_bar_last_blinked.tv_usec) { sec_diff -= 1; usec_diff = (1000000 + t.tv_usec) - nav_bar_last_blinked.tv_usec; } if (sec_diff >= 1 || usec_diff >= 530000) { nav_bar_blink = !nav_bar_blink; gettimeofday(&nav_bar_last_blinked, NULL); _redraw_nav_bar(); } } static void nav_bar_set_focused(void) { nav_bar_focused = 1; nav_bar_blink = 0; gettimeofday(&nav_bar_last_blinked, NULL); } /** * navbar: Text editing helpers for ^W, deletes one directory element */ static void nav_bar_backspace_word(void) { if (!*nav_bar) return; if (nav_bar_cursor == 0) return; char * after = strdup(&nav_bar[nav_bar_cursor]); if (nav_bar[nav_bar_cursor-1] == '/') { nav_bar[nav_bar_cursor-1] = '\0'; nav_bar_cursor--; } while (nav_bar_cursor && nav_bar[nav_bar_cursor-1] != '/') { nav_bar[nav_bar_cursor-1] = '\0'; nav_bar_cursor--; } strcat(nav_bar, after); free(after); _recalculate_nav_bar_cursor(); nav_bar_set_focused(); _redraw_nav_bar(); } /** * navbar: Text editing helper for backspace, deletes one character */ static void nav_bar_backspace(void) { if (nav_bar_cursor == 0) return; char * after = strdup(&nav_bar[nav_bar_cursor]); nav_bar[nav_bar_cursor-1] = '\0'; nav_bar_cursor--; strcat(nav_bar, after); free(after); _recalculate_nav_bar_cursor(); nav_bar_set_focused(); _redraw_nav_bar(); } static void nav_bar_delete(void) { if (!nav_bar[nav_bar_cursor]) return; char * after = strdup(&nav_bar[nav_bar_cursor+1]); nav_bar[nav_bar_cursor] = '\0'; strcat(nav_bar, after); free(after); _recalculate_nav_bar_cursor(); nav_bar_set_focused(); _redraw_nav_bar(); } /** * navbar: Text editing helper for inserting characters */ static void nav_bar_insert_char(char c) { char * tmp = strdup(nav_bar); tmp[nav_bar_cursor] = '\0'; char * after = strdup(&nav_bar[nav_bar_cursor]); sprintf(nav_bar, "%s%c%s", tmp, c, after); free(tmp); free(after); nav_bar_cursor += 1; _recalculate_nav_bar_cursor(); nav_bar_set_focused(); _redraw_nav_bar(); } /** * navbar: Move editing cursor one character left */ static void nav_bar_cursor_left(int modifiers) { if (!*nav_bar) return; if (nav_bar_cursor == 0) return; if (modifiers & (KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL)) { if (nav_bar[nav_bar_cursor-1] == '/') { nav_bar_cursor--; } while (nav_bar_cursor && nav_bar[nav_bar_cursor-1] != '/') { nav_bar_cursor--; } } else { nav_bar_cursor--; } _recalculate_nav_bar_cursor(); nav_bar_set_focused(); _redraw_nav_bar(); } /** * navbar: Move editing cursor one character right */ static void nav_bar_cursor_right(int modifiers) { if (!*nav_bar) return; if (modifiers & (KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL)) { if (nav_bar[nav_bar_cursor] == '/') { nav_bar_cursor++; } while (nav_bar[nav_bar_cursor] && nav_bar[nav_bar_cursor] != '/') { nav_bar_cursor++; } } else { nav_bar_cursor++; } _recalculate_nav_bar_cursor(); nav_bar_set_focused(); _redraw_nav_bar(); } /** * Redraw the entire window. */ static void redraw_window(void) { if (!is_desktop_background) { /* Clear to white and draw decorations */ draw_fill(ctx, rgb(255,255,255)); render_decorations(main_window, ctx, title); } else { /* Draw wallpaper in desktop mode */ if (wallpaper_old) { draw_sprite(ctx, wallpaper_old, 0, 0); uint64_t ellapsed = precise_time_since(timer); if (ellapsed > 1000) { sprite_free(wallpaper_old); wallpaper_old = NULL; draw_sprite(ctx, wallpaper_buffer, 0, 0); } else { draw_sprite_alpha(ctx, wallpaper_buffer, 0, 0, (float)ellapsed / 1000.0); } } else { draw_sprite(ctx, wallpaper_buffer, 0, 0); } } struct decor_bounds bounds; _decor_get_bounds(main_window, &bounds); if (!is_desktop_background) { /* Position, size, and draw the menu bar */ if (menu_bar_height) { menu_bar.x = bounds.left_width; menu_bar.y = bounds.top_height; menu_bar.width = ctx->width - bounds.width; menu_bar.window = main_window; menu_bar_render(&menu_bar, ctx); } /* Draw toolbar */ _draw_buttons(bounds); _draw_nav_bar(bounds); _draw_status(bounds); } /* Draw the icon view, clipped to the viewport and scrolled appropriately. */ gfx_clear_clip(ctx); gfx_add_clip(ctx, bounds.left_width, bounds.top_height + menu_bar_height + nav_bar_height, ctx->width - bounds.width, available_height); draw_sprite(ctx, contents_sprite, bounds.left_width, bounds.top_height + menu_bar_height + nav_bar_height - scroll_offset); gfx_clear_clip(ctx); gfx_add_clip(ctx, 0, 0, ctx->width, ctx->height); /* Flip graphics context and inform compositor */ flip(ctx); yutani_flip(yctx, main_window); } /** * Loads and bakes the wallpaper to the appropriate size. */ static void draw_background(int width, int height) { /* If the wallpaper is already loaded, free it. */ if (wallpaper_buffer) { if (wallpaper_old) { sprite_free(wallpaper_old); } wallpaper_old = wallpaper_buffer; timer = precise_current_time(); } /* Open the wallpaper */ sprite_t * wallpaper = malloc(sizeof(sprite_t)); char * wallpaper_path = WALLPAPER_PATH; int free_it = 0; char * home = getenv("HOME"); if (home) { char tmp[512]; sprintf(tmp, "%s/.wallpaper.conf", home); FILE * c = fopen(tmp, "r"); if (c) { char line[1024]; while (!feof(c)) { fgets(line, 1024, c); char * nl = strchr(line, '\n'); if (nl) *nl = '\0'; if (line[0] == ';') { continue; } if (strstr(line, "wallpaper=") == line) { free_it = 1; wallpaper_path = strdup(line+strlen("wallpaper=")); break; } } fclose(c); } } load_sprite(wallpaper, wallpaper_path); if (free_it) { free(wallpaper_path); } /* Create a new buffer to hold the baked wallpaper */ wallpaper_buffer = create_sprite(width, height, 0); gfx_context_t * ctx = init_graphics_sprite(wallpaper_buffer); /* Calculate the appropriate scaled size to fit the screen. */ float x = (float)width / (float)wallpaper->width; float y = (float)height / (float)wallpaper->height; int nh = (int)(x * (float)wallpaper->height); int nw = (int)(y * (float)wallpaper->width); /* Clear to black to avoid odd transparency issues along edges */ draw_fill(ctx, rgb(0,0,0)); /* Scale the wallpaper into the buffer. */ if (nw == wallpaper->width && nh == wallpaper->height) { /* No scaling necessary */ draw_sprite(ctx, wallpaper, 0, 0); } else if (nw >= width) { /* Scaled wallpaper is wider, height should match. */ draw_sprite_scaled(ctx, wallpaper, ((int)width - nw) / 2, 0, nw+2, height); } else { /* Scaled wallpaper is taller, width should match. */ draw_sprite_scaled(ctx, wallpaper, 0, ((int)height - nh) / 2, width+2, nh); } /* Free the original wallpaper. */ sprite_free(wallpaper); free(ctx); } /** * Resize window when asked by the compositor. */ static void resize_finish(int w, int h) { if (w < 300 || h < 300) { yutani_window_resize_offer(yctx, main_window, w < 300 ? 300 : w, h < 300 ? 300 : h); return; } int width_changed = (main_window->width != (unsigned int)w); yutani_window_resize_accept(yctx, main_window, w, h); reinit_graphics_yutani(ctx, main_window); struct decor_bounds bounds; _decor_get_bounds(main_window, &bounds); /* Recalculate available size */ available_height = ctx->height - menu_bar_height - nav_bar_height - bounds.height - (is_desktop_background ? 0 : STATUS_HEIGHT); fprintf(stderr, "available_height = %d; bounds.bottom_height = %d, (isd...) = %d\n", available_height, bounds.bottom_height, (is_desktop_background ? 0 : STATUS_HEIGHT)); /* If the width changed, we need to rebuild the icon view */ if (width_changed) { reinitialize_contents(); } /* Make sure we're not scrolled weirdly after resizing */ if (available_height > contents->height) { scroll_offset = 0; } else { if (scroll_offset > contents->height - available_height) { scroll_offset = contents->height - available_height; } } /* If the desktop background changes size, we have to reload and rescale the wallpaper */ if (is_desktop_background) { draw_background(w, h); } /* Redraw */ redraw_window(); yutani_window_resize_done(yctx, main_window); yutani_flip(yctx, main_window); } /* TODO: We don't have an input box yet. */ #if 0 static void _menu_action_input_path(struct MenuEntry * entry) { } #endif /* File > Exit */ static void _menu_action_exit(struct MenuEntry * entry) { application_running = 0; } /* Go > ... generic handler */ static void _menu_action_navigate(struct MenuEntry * entry) { /* go to entry->action */ struct MenuEntry_Normal * _entry = (void*)entry; load_directory(_entry->action, 1); reinitialize_contents(); redraw_window(); } /* Go > Up */ static void _menu_action_up(struct MenuEntry * entry) { /* go up */ char * tmp = strdup(current_directory); char * dir = dirname(tmp); load_directory(dir, 1); reinitialize_contents(); redraw_window(); } /* [Context] > Refresh */ static void _menu_action_refresh(struct MenuEntry * entry) { char * tmp = strdup(current_directory); load_directory(tmp, 0); reinitialize_contents(); redraw_window(); } /* Help > Contents */ static void _menu_action_help(struct MenuEntry * entry) { /* show help documentation */ system("help-browser file-browser.trt &"); redraw_window(); } /* [Context] > Copy */ static void _menu_action_copy(struct MenuEntry * entry) { size_t output_size = 0; /* Calculate required space for the clipboard */ int base_is_root = !strcmp(current_directory, "/"); /* avoid redundant slash */ for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { output_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 */ } } /* Nothing to copy? */ if (!output_size) return; /* Create the clipboard contents as a LF-separated list of absolute paths */ char * clipboard = malloc(output_size+1); /* last nil */ clipboard[0] = '\0'; for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { strcat(clipboard, current_directory); if (!base_is_root) { strcat(clipboard, "/"); } strcat(clipboard, file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name); strcat(clipboard, "\n"); } } if (clipboard[output_size-1] == '\n') { /* Remove trailing line feed */ clipboard[output_size-1] = '\0'; } yutani_set_clipboard(yctx, clipboard); free(clipboard); } static void _menu_action_paste(struct MenuEntry * entry) { yutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD); } static void _menu_action_delete(struct MenuEntry * entry) { size_t filesToDelete = 0; for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) filesToDelete++; } int base_is_root = !strcmp(current_directory, "/"); /* avoid redundant slash */ char ** args = malloc(sizeof(char*) * (filesToDelete + 3)); args[0] = "/bin/prompt_and_delete.krk"; args[1] = malloc(100); snprintf(args[1],100,"%d",getpid()); size_t counter = 2; for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { const char * name = file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name; size_t len = strlen(current_directory) + !base_is_root + strlen(name) + 1; args[counter] = malloc(len); snprintf(args[counter], len, "%s%s%s", current_directory, base_is_root ? "" : "/", name); counter++; } } args[counter] = NULL; pid_t child = fork(); if (!child) { execv(args[0], args); exit(1); } else if (child < 0) { fprintf(stderr, "Error forking.\n"); } for (size_t i = 1; i < counter; ++i) { free(args[i]); } free(args); } /* Help > About File Browser */ static void _menu_action_about(struct MenuEntry * entry) { /* Show About dialog */ char about_cmd[1024] = "\0"; strcat(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\" "); char coords[100]; sprintf(coords, "%d %d &", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2); strcat(about_cmd, coords); system(about_cmd); redraw_window(); } /** * Generic application launcher - like system(), but without the wait. * Also sets the working directory to the currently-opened directory. */ static void launch_application(char * app) { if (!fork()) { if (current_directory) chdir(current_directory); char * tmp = malloc(strlen(app) + 10); sprintf(tmp, "%s", app); char * args[] = {"/bin/sh", "-c", tmp, NULL}; execvp(args[0], args); exit(1); } } /* Generic handler for various launcher menus */ static void launch_application_menu(struct MenuEntry * self) { struct MenuEntry_Normal * _self = (void *)self; launch_application((char *)_self->action); } /** * Perform the appropriate action to open a File */ static void open_file(struct File * f) { if (f->type == 1) { char tmp[1024]; if (is_desktop_background) { /* Always open directories in new file browser windows when launched from desktop */ sprintf(tmp,"file-browser \"%s/%s\"", current_directory, f->name); launch_application(tmp); } else { /* In normal mode, navigate to this directory. */ sprintf(tmp,"%s/%s", current_directory, f->name); load_directory(tmp, 1); reinitialize_contents(); redraw_window(); } } else if (f->launcher[0]) { if (is_picker_dialog) { int base_is_root = !strcmp(current_directory, "/"); /* avoid redundant slash */ for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { printf("%s%s%s\n", current_directory, base_is_root ? "" : "/", file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name); } } exit(0); } char tmp[4096]; if (!strcmp(f->launcher, "SELF")) { /* "SELF" launchers are for binaries. */ sprintf(tmp, "exec ./%s", f->name); } else { /* Other launchers should take file names as arguments. * NOTE: If you don't want the file name, you can append # to your launcher. * Since it's parsed by the shell, this will yield a comment. */ sprintf(tmp, "%s \"%s\"", f->launcher, f->name); } launch_application(tmp); } } /* [Context] > Open */ static void _menu_action_open(struct MenuEntry * self) { for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { open_file(file_pointers[i]); } } } /* [Context] > Edit in Bim */ static void _menu_action_edit(struct MenuEntry * self) { for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { char tmp[1024]; sprintf(tmp, "exec terminal bim \"%s\"", file_pointers[i]->type == 2 ? file_pointers[i]->filename : file_pointers[i]->name); launch_application(tmp); } } } /* View > (Show/Hide) Hidden Files */ static void _menu_action_toggle_hidden(struct MenuEntry * self) { show_hidden = !show_hidden; menu_update_toggle_state(self, show_hidden); _menu_action_refresh(NULL); } static void _menu_action_select_all(struct MenuEntry * self) { for (int i = 0; i < file_pointers_len; ++i) { file_pointers[i]->selected = 1; } reinitialize_contents(); update_status(); redraw_window(); } /** * Set the view mode for the file view * We support three modes: * - Icons: Standard view, label centered below icon. * - Tiles: Like Windows Explorer, labels left-aligned to the right of * the icon, with extra details also displayed. Bold file name. * - List: One-line-per-file, icon on the left, left aligne file name. */ static void set_view_mode(int mode) { switch (mode) { default: case VIEW_MODE_ICONS: FILE_HEIGHT = 80; FILE_WIDTH = 100; view_mode = VIEW_MODE_ICONS; break; case VIEW_MODE_TILES: FILE_HEIGHT = 70; FILE_WIDTH = 260; view_mode = VIEW_MODE_TILES; break; case VIEW_MODE_LIST: FILE_HEIGHT = 24; FILE_WIDTH = 100; /* Readjusts elsewhere */ view_mode = VIEW_MODE_LIST; break; } } /** * Dropdown action handler for view mode entries; * use menuitem action to determine which view mode to set. */ static void _menu_action_view_mode(struct MenuEntry * entry) { struct MenuEntry_Normal * _entry = (void*)entry; int mode = VIEW_MODE_ICONS; if (!strcmp(_entry->action, "icons")) { mode = VIEW_MODE_ICONS; } else if (!strcmp(_entry->action, "tiles")) { mode = VIEW_MODE_TILES; } else if (!strcmp(_entry->action, "list")) { mode = VIEW_MODE_LIST; } set_view_mode(mode); menu_update_toggle_state(_menu_entry_show_icons, view_mode == VIEW_MODE_ICONS); menu_update_toggle_state(_menu_entry_show_tiles, view_mode == VIEW_MODE_TILES); menu_update_toggle_state(_menu_entry_show_list, view_mode == VIEW_MODE_LIST); reinitialize_contents(); redraw_window(); } /** * Receive pastes, which are presumed to be file names of files * which have been copied and should now be pasted into a new * directory; will not overwrite if pasted into the same directory * or a directory with a file with the same name. * * XXX: Calls `cp` to perform actual copy. * * TODO: Actually check if clipboard contains a file name. * TODO: Handle pastes into the navbar of arbitrary text. */ static void handle_clipboard(char * contents) { fprintf(stderr, "Received clipboard:\n%s\n",contents); char * file = contents; while (file && *file) { char * next_file = strchr(file, '\n'); if (next_file) { *next_file = '\0'; next_file++; } /* determine if the destination already exists */ char * cheap_basename = strrchr(file, '/'); if (!cheap_basename) cheap_basename = file; else cheap_basename++; char destination[4096]; sprintf(destination, "%s/%s", current_directory, cheap_basename); struct stat statbuf; if (!stat(destination, &statbuf)) { char message[4096]; sprintf(message, "showdialog \"File Browser\" /usr/share/icons/48/folder.png \"Not overwriting file '%s'.\"", cheap_basename); launch_application(message); } else { char cp[1024]; sprintf(cp, "cp -r \"%s\" \"%s\"", file, current_directory); if (system(cp)) { char message[4096]; sprintf(message, "showdialog \"File Browser\" /usr/share/icons/48/folder.png \"Error copying file '%s'.\"", cheap_basename); launch_application(message); } } file = next_file; } _menu_action_refresh(NULL); } /** * Toggle the selected status of the highlighted icon. * * When Ctrl is held, the current selection is maintained. */ static void toggle_selected(int hilighted_offset, int modifiers) { struct File * f = get_file_at_offset(hilighted_offset); /* No file at this offset, do nothing. */ if (!f) return; /* Toggle selection of the current file */ f->selected = !f->selected; /* If Ctrl wasn't held, unselect everything else. */ if (!(modifiers & YUTANI_KEY_MODIFIER_CTRL)) { for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i] != f && file_pointers[i]->selected) { file_pointers[i]->selected = 0; clear_offset(i); draw_file(file_pointers[i], i); } } } update_status(); /* Redraw the file */ clear_offset(hilighted_offset); draw_file(f, hilighted_offset); /* And repaint the window */ redraw_window(); } /** * Handle button hover highlights */ static int _down_button = -1; static void _set_hilight(int index, int hilight) { int _update = 0; if (_button_hover != index || (_button_hover == index && index != -1 && _button_hilights[index] != hilight)) { if (_button_hover != -1 && _button_hilights[_button_hover] != 3) { _button_hilights[_button_hover] = 3; _update = 1; } _button_hover = index; if (index != -1 && !_button_disabled[index]) { _button_hilights[_button_hover] = hilight; _update = 1; } if (_update) { redraw_window(); } } } /** * Handle toolbar button clicking */ static void _handle_button_press(int index) { if (index != -1 && _button_disabled[index]) return; /* can't click disabled buttons */ switch (index) { case 0: /* Back */ if (history_back->length) { list_insert(history_forward, strdup(current_directory)); node_t * next = list_pop(history_back); load_directory(next->value, 0); free(next->value); free(next); reinitialize_contents(); redraw_window(); } break; case 1: /* Forward */ if (history_forward->length) { list_insert(history_back, strdup(current_directory)); node_t * next = list_pop(history_forward); load_directory(next->value, 0); free(next->value); free(next); reinitialize_contents(); redraw_window(); } break; case 2: /* Up */ _menu_action_up(NULL); break; case 3: /* Home */ { struct MenuEntry_Normal _fake = {.action = getenv("HOME") }; _menu_action_navigate(&_fake); } break; default: /* ??? */ break; } } /** * Scroll file view up */ static void _scroll_up(void) { scroll_offset -= SCROLL_AMOUNT; if (scroll_offset < 0) { scroll_offset = 0; } } /** * Scroll file view down */ static void _scroll_down(void) { if (available_height > contents->height) { scroll_offset = 0; } else { scroll_offset += SCROLL_AMOUNT; if (scroll_offset > contents->height - available_height) { scroll_offset = contents->height - available_height; } } } static int signalResponse = 0; /** * Desktop mode responsds to sig_usr2 by returning to * the bottom of the Z-order stack. */ static void sig_usr2(int sig) { signalResponse |= 1; } /** * Desktop mode responds to sig_usr1 by resizing the window * to the current display size. */ static void sig_usr1(int sig) { signalResponse |= 2; } static void sig_urg(int sig) { signalResponse |= 4; } /** * Accept keyboard arrows left/right/up/down and select the appropriate * file in the file view based on the currently selected file; if multiple * files are currently selected, the first one (up/left) is used as the basis * for the new selection. */ static void arrow_select(int x, int y) { if (!file_pointers_len) return; /* Find first selected */ int selected = -1; for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { selected = i; file_pointers[i]->selected = 0; clear_offset(i); draw_file(file_pointers[i], i); } } if (selected == -1) { selected = 0; } else { int offset_y = selected / FILE_PTR_WIDTH; int offset_x = selected % FILE_PTR_WIDTH; offset_y += y; offset_x += x; if (offset_x >= FILE_PTR_WIDTH) { offset_x = FILE_PTR_WIDTH - 1; } if (offset_x < 0) { offset_x = 0; } if (offset_y < 0) { offset_y = 0; } selected = offset_y * FILE_PTR_WIDTH + offset_x; if (selected >= file_pointers_len) selected = file_pointers_len - 1; if (selected < 0) selected = 0; } int offset_y = selected / FILE_PTR_WIDTH; if (offset_y * FILE_HEIGHT < scroll_offset) { scroll_offset = offset_y * FILE_HEIGHT; } if (offset_y * FILE_HEIGHT + FILE_HEIGHT > scroll_offset + available_height) { scroll_offset = offset_y * FILE_HEIGHT + FILE_HEIGHT - available_height; } file_pointers[selected]->selected = 1; clear_offset(selected); draw_file(file_pointers[selected], selected); update_status(); redraw_window(); } static void redraw_window_callback(struct menu_bar * self) { (void)self; redraw_window(); } static void show_context_menu(struct yutani_msg_window_mouse_event * me) { if (!context_menu->window && !directory_context_menu->window) { struct File * f = get_file_at_offset(hilighted_offset); if (f && !f->selected) { toggle_selected(hilighted_offset, me->modifiers); } int _have_selection = 0; for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { _have_selection = 1; break; } } if (_have_selection) { menu_show_at(context_menu, main_window, me->new_x, me->new_y); } else { menu_show_at(directory_context_menu, main_window, me->new_x, me->new_y); } } } static void set_signal_handler(int signum, void (*handler)(int)) { struct sigaction action; action.sa_handler = handler; sigemptyset(&action.sa_mask); action.sa_flags = 0; /* do not restart syscalls, do not reset handler */ sigaction(signum, &action, NULL); } int main(int argc, char * argv[]) { yctx = yutani_init(); init_decorations(); tt_font_thin = tt_font_from_shm("sans-serif"); tt_font_bold = tt_font_from_shm("sans-serif.bold"); int arg_ind = 1; if (argc > 1 && !strcmp(argv[1], "--wallpaper")) { is_desktop_background = 1; menu_bar_height = 0; nav_bar_height = 0; set_signal_handler(SIGUSR1, sig_usr1); set_signal_handler(SIGUSR2, sig_usr2); draw_background(yctx->display_width, yctx->display_height); main_window = yutani_window_create_flags(yctx, yctx->display_width, yctx->display_height, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS); yutani_window_move(yctx, main_window, 0, 0); yutani_set_stack(yctx, main_window, YUTANI_ZORDER_BOTTOM); arg_ind++; FILE * f = fopen("/var/run/.wallpaper.pid", "w"); fprintf(f, "%d\n", getpid()); fclose(f); } else if (argc > 1 && !strcmp(argv[1], "--picker")) { is_picker_dialog = 1; menu_bar_height = 0; main_window = yutani_window_create_flags(yctx, 640, 400, YUTANI_WINDOW_FLAG_DIALOG_ANIMATION); main_window->decorator_flags |= DECOR_FLAG_NO_MAXIMIZE; yutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2); set_view_mode(VIEW_MODE_LIST); } else { main_window = yutani_window_create(yctx, 800, 600); yutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2); set_view_mode(VIEW_MODE_TILES); } set_signal_handler(SIGURG, sig_urg); if (arg_ind < argc) { chdir(argv[arg_ind]); } ctx = init_graphics_yutani_double_buffer(main_window); struct decor_bounds bounds; _decor_get_bounds(main_window, &bounds); set_title(NULL); if (menu_bar_height) { menu_bar.entries = menu_entries; menu_bar.redraw_callback = redraw_window_callback; menu_bar.set = menu_set_create(); struct MenuList * m = menu_create(); /* File */ menu_insert(m, menu_create_normal("exit",NULL,"Exit", _menu_action_exit)); menu_set_insert(menu_bar.set, "file", m); m = menu_create(); menu_insert(m, menu_create_normal(NULL,NULL,"Copy",_menu_action_copy)); menu_insert(m, menu_create_normal(NULL,NULL,"Paste",_menu_action_paste)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal(NULL,NULL,"Select all",_menu_action_select_all)); menu_set_insert(menu_bar.set, "edit", m); m = menu_create(); menu_insert(m, menu_create_normal("refresh",NULL,"Refresh", _menu_action_refresh)); menu_insert(m, menu_create_separator()); menu_insert(m, (_menu_entry_show_icons = menu_create_toggle("icons","Show Icons", view_mode == VIEW_MODE_ICONS, _menu_action_view_mode))); menu_insert(m, (_menu_entry_show_tiles = menu_create_toggle("tiles","Show Tiles", view_mode == VIEW_MODE_TILES, _menu_action_view_mode))); menu_insert(m, (_menu_entry_show_list = menu_create_toggle("list", "Show List", view_mode == VIEW_MODE_LIST, _menu_action_view_mode))); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_toggle(NULL,"Show Hidden Files", 0, _menu_action_toggle_hidden)); menu_set_insert(menu_bar.set, "view", m); m = menu_create(); /* Go */ menu_insert(m, menu_create_normal("home",getenv("HOME"),"Home",_menu_action_navigate)); menu_insert(m, menu_create_normal(NULL,"/","File System",_menu_action_navigate)); menu_insert(m, (_menu_entry_up = menu_create_normal("up",NULL,"Up",_menu_action_up))); menu_set_insert(menu_bar.set, "go", m); m = menu_create(); menu_insert(m, menu_create_normal("help",NULL,"Contents",_menu_action_help)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal("star",NULL,"About " APPLICATION_TITLE,_menu_action_about)); menu_set_insert(menu_bar.set, "help", m); } available_height = ctx->height - menu_bar_height - nav_bar_height - bounds.height - (is_desktop_background ? 0 : STATUS_HEIGHT); context_menu = menu_create(); /* Right-click menu */ menu_insert(context_menu, menu_create_normal(NULL,NULL,"Open",_menu_action_open)); menu_insert(context_menu, menu_create_normal(NULL,NULL,"Edit in Bim",_menu_action_edit)); menu_insert(context_menu, menu_create_separator()); menu_insert(context_menu, menu_create_normal(NULL,NULL,"Copy",_menu_action_copy)); menu_insert(context_menu, menu_create_normal(NULL,NULL,"Paste",_menu_action_paste)); menu_insert(context_menu, menu_create_separator()); menu_insert(context_menu, menu_create_normal(NULL,NULL,"Delete",_menu_action_delete)); if (!is_desktop_background) { menu_insert(context_menu, menu_create_separator()); menu_insert(context_menu, (_menu_entry_up_ctx_a = menu_create_normal("up",NULL,"Up",_menu_action_up))); } menu_insert(context_menu, menu_create_normal("refresh",NULL,"Refresh",_menu_action_refresh)); menu_insert(context_menu, menu_create_normal("utilities-terminal","terminal","Open Terminal",launch_application_menu)); directory_context_menu = menu_create(); /* the other right click menu */ menu_insert(directory_context_menu, menu_create_normal(NULL,NULL,"Paste",_menu_action_paste)); menu_insert(directory_context_menu, menu_create_separator()); if (!is_desktop_background) { menu_insert(directory_context_menu, (_menu_entry_up_ctx_b = menu_create_normal("up",NULL,"Up",_menu_action_up))); } menu_insert(directory_context_menu, menu_create_normal("refresh",NULL,"Refresh",_menu_action_refresh)); menu_insert(directory_context_menu, menu_create_normal("utilities-terminal","terminal","Open Terminal",launch_application_menu)); history_back = list_create(); history_forward = list_create(); /* Load the current working directory */ char tmp[1024]; getcwd(tmp, 1024); load_directory(tmp, 1); /* Draw files */ reinitialize_contents(); redraw_window(); while (application_running) { waitpid(-1, NULL, WNOHANG); int fds[1] = {fileno(yctx->sock)}; int index = fswait2(1,fds,wallpaper_old ? 10 : 200); if (index < 0 && signalResponse) { if (signalResponse & 1) { yutani_set_stack(yctx, main_window, YUTANI_ZORDER_BOTTOM); _menu_action_refresh(NULL); } else if (signalResponse & 2) { yutani_window_resize_offer(yctx, main_window, yctx->display_width, yctx->display_height); } else if (signalResponse & 4) { _menu_action_refresh(NULL); } signalResponse = 0; continue; } maybe_blink_cursor(); if (index == 1) { if (wallpaper_old) { redraw_window(); } continue; } int redraw = 0; yutani_msg_t * m = yutani_poll(yctx); while (m) { if (menu_process_event(yctx, m)) { redraw = 1; } switch (m->type) { case YUTANI_MSG_WELCOME: if (is_desktop_background) { yutani_window_resize_offer(yctx, main_window, yctx->display_width, yctx->display_height); } break; case YUTANI_MSG_KEY_EVENT: { struct yutani_msg_key_event * ke = (void*)m->data; if (ke->event.action == KEY_ACTION_DOWN && ke->wid == main_window->wid) { if (nav_bar_focused) { switch (ke->event.key) { case KEY_ESCAPE: nav_bar_focused = 0; redraw_window(); break; case KEY_BACKSPACE: if (ke->event.modifiers & (KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL)) { nav_bar_backspace_word(); } else { nav_bar_backspace(); } break; case KEY_CTRL_W: nav_bar_backspace_word(); break; case '\n': nav_bar_focused = 0; char * tmp = strdup(nav_bar); load_directory(tmp, 1); reinitialize_contents(); redraw_window(); break; default: if (isgraph(ke->event.key)) { nav_bar_insert_char(ke->event.key); } else { switch (ke->event.keycode) { case KEY_ARROW_LEFT: nav_bar_cursor_left(ke->event.modifiers); break; case KEY_ARROW_RIGHT: nav_bar_cursor_right(ke->event.modifiers); break; case KEY_DEL: nav_bar_delete(); break; } } break; } break; } switch (ke->event.keycode) { case KEY_PAGE_UP: _scroll_up(); redraw = 1; break; case KEY_PAGE_DOWN: _scroll_down(); redraw = 1; break; /* if not focused on anything focusable */ case KEY_ARROW_DOWN: arrow_select(0,1); break; case KEY_ARROW_UP: arrow_select(0,-1); break; case KEY_ARROW_LEFT: arrow_select(-1,0); break; case KEY_ARROW_RIGHT: arrow_select(1,0); break; case KEY_BACKSPACE: if (!is_desktop_background) { _menu_action_up(NULL); } break; case '\n': _menu_action_open(NULL); break; case 'l': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_CTRL && !is_desktop_background) { nav_bar_set_focused(); redraw_window(); } break; case 'f': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[0]); } break; case 'e': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[1]); } break; case 'v': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[2]); } break; case 'g': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[3]); } break; case 'h': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT && menu_bar_height) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[4]); } break; case 'q': if (!is_desktop_background) { _menu_action_exit(NULL); } break; default: break; } } } 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*)(uintptr_t)wf->wid); if (win == main_window) { win->focused = wf->focused; redraw_files(); redraw = 1; } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; if (wr->wid == main_window->wid) { resize_finish(wr->width, wr->height); } } break; case YUTANI_MSG_CLIPBOARD: { struct yutani_msg_clipboard * cb = (void *)m->data; char * selection_text; if (*cb->content == '\002') { int size = atoi(&cb->content[2]); FILE * clipboard = yutani_open_clipboard(yctx); selection_text = malloc(size + 1); fread(selection_text, 1, size, clipboard); selection_text[size] = '\0'; fclose(clipboard); } else { selection_text = malloc(cb->size+1); memcpy(selection_text, cb->content, cb->size); selection_text[cb->size] = '\0'; } handle_clipboard(selection_text); free(selection_text); } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; yutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid); struct decor_bounds bounds; _decor_get_bounds(win, &bounds); if (win == main_window) { int result = decor_handle_event(yctx, m); switch (result) { case DECOR_CLOSE: _menu_action_exit(NULL); break; case DECOR_RIGHT: /* right click in decoration, show appropriate menu */ decor_show_default_menu(main_window, main_window->x + me->new_x, main_window->y + me->new_y); break; default: /* Other actions */ break; } /* Menu bar */ if (menu_bar_height) menu_bar_mouse_event(yctx, main_window, &menu_bar, me, me->new_x, me->new_y); if (nav_bar_height && me->new_y > (int)(bounds.top_height + menu_bar_height) && me->new_y < (int)(bounds.top_height + menu_bar_height + nav_bar_height) && me->new_x > (int)(bounds.left_width) && me->new_x < (int)(main_window->width - bounds.right_width)) { int x = me->new_x - bounds.left_width - 2; if (x >= 0) { int i = x / 34; if (i < 4) { if (me->command == YUTANI_MOUSE_EVENT_DOWN) { _set_hilight(i, 2); nav_bar_focused = 0; _down_button = i; } else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) { if (_down_button != -1 && _down_button == i) { _handle_button_press(i); _set_hilight(i, 1); } _down_button = -1; } else { if (!(me->buttons & YUTANI_MOUSE_BUTTON_LEFT)) { _set_hilight(i, 1); } else { if (_down_button == i) { _set_hilight(i, 2); } else if (_down_button != -1) { _set_hilight(_down_button, 3); } } } if (main_window->mouse_state == YUTANI_CURSOR_TYPE_IBEAM) { yutani_window_show_mouse(yctx, main_window, YUTANI_CURSOR_TYPE_RESET); } } else { _set_hilight(-1,0); if (me->command == YUTANI_MOUSE_EVENT_DOWN) { nav_bar_set_focused(); _figure_out_navbar_cursor(me->new_x, bounds); redraw = 1; } if (main_window->mouse_state == YUTANI_CURSOR_TYPE_RESET) { yutani_window_show_mouse(yctx, main_window, YUTANI_CURSOR_TYPE_IBEAM); } } } } else { if (main_window->mouse_state == YUTANI_CURSOR_TYPE_IBEAM) { yutani_window_show_mouse(yctx, main_window, YUTANI_CURSOR_TYPE_RESET); } if (me->command == YUTANI_MOUSE_EVENT_DOWN) { if (nav_bar_focused) { nav_bar_focused = 0; redraw = 1; } } if (_button_hover != -1) { _button_hilights[_button_hover] = 3; _button_hover = -1; redraw = 1; /* Double redraw ??? */ } } if (!is_desktop_background && me->new_y > (int)(main_window->height - bounds.bottom_height - STATUS_HEIGHT)) { } else if (me->new_y > (int)(bounds.top_height + menu_bar_height + nav_bar_height) && me->new_y < (int)(main_window->height - bounds.bottom_height) && me->new_x > (int)(bounds.left_width) && me->new_x < (int)(main_window->width - bounds.right_width) && me->command != YUTANI_MOUSE_EVENT_LEAVE) { if (me->buttons & YUTANI_MOUSE_SCROLL_UP) { /* Scroll up */ _scroll_up(); redraw = 1; } else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) { _scroll_down(); redraw = 1; } /* Get offset into contents */ int y_into = me->new_y - bounds.top_height - menu_bar_height - nav_bar_height + scroll_offset; int x_into = me->new_x - bounds.left_width; int offset = (y_into / FILE_HEIGHT) * FILE_PTR_WIDTH + x_into / FILE_WIDTH; if (x_into > FILE_PTR_WIDTH * FILE_WIDTH) { offset = -1; } if (offset != hilighted_offset) { int old_offset = hilighted_offset; hilighted_offset = offset; if (old_offset != -1) { clear_offset(old_offset); struct File * f = get_file_at_offset(old_offset); if (f) { clear_offset(old_offset); draw_file(f, old_offset); } } struct File * f = get_file_at_offset(hilighted_offset); if (f) { clear_offset(hilighted_offset); draw_file(f, hilighted_offset); } redraw = 1; } if (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) { struct File * f = get_file_at_offset(hilighted_offset); if (f) { if (last_click_offset == hilighted_offset && precise_time_since(last_click) < 400) { open_file(f); last_click = 0; } else { last_click = precise_current_time(); last_click_offset = hilighted_offset; toggle_selected(hilighted_offset, me->modifiers); } } else { if (!(me->modifiers & YUTANI_KEY_MODIFIER_CTRL)) { for (int i = 0; i < file_pointers_len; ++i) { if (file_pointers[i]->selected) { file_pointers[i]->selected = 0; update_status(); clear_offset(i); draw_file(file_pointers[i], i); } } redraw = 1; } } } else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) { show_context_menu(me); } } else { int old_offset = hilighted_offset; hilighted_offset = -1; if (old_offset != -1) { clear_offset(old_offset); struct File * f = get_file_at_offset(old_offset); if (f) { clear_offset(old_offset); draw_file(f, old_offset); } redraw = 1; } } } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: _menu_action_exit(NULL); break; default: break; } free(m); m = yutani_poll_async(yctx); } if (redraw || wallpaper_old) { redraw_window(); } } } ================================================ FILE: apps/find-timezone.c ================================================ /** * @brief Query a remote API to get timezone information based geoip lookup. * * We ask @see ip-api.com for geo-ip data, which includes a timezone offset. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include typedef struct JSON_Value Value; #define LOCATION_DATA_PATH "/tmp/location-data.json" int main(int argc, char * argv[]) { /* See if the location data already exists... */ char cmdline[1024]; Value * locationData = json_parse_file(LOCATION_DATA_PATH); if (!locationData) { sprintf(cmdline, "fetch -o \"" LOCATION_DATA_PATH "\" \"http://ip-api.com/json/?fields=lat,lon,city,offset\""); system(cmdline); locationData = json_parse_file(LOCATION_DATA_PATH); } /* If we still failed to load it, then bail. */ if (!locationData) { return 1; } double offset = JSON_KEY(locationData, "offset")->number; printf("%d\n", (int)offset); return 0; } ================================================ FILE: apps/font-preview.c ================================================ /** * @brief TrueType font previewer * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include /* Pointer to graphics memory */ static yutani_t * yctx; static yutani_window_t * window = NULL; static gfx_context_t * ctx = NULL; static struct TT_Font * tt_font = NULL; static int decor_left_width = 0; static int decor_top_height = 0; static int decor_right_width = 0; static int decor_bottom_height = 0; static int decor_width = 0; static int decor_height = 0; static int width = 640; static int height = 480; char * tt_get_name_string(struct TT_Font * font, int identifier); char * preview_string = "The quick brown fox jumps over the lazy dog."; char * tt_font_name = NULL; char window_title[1024] = "Font Preview"; void redraw(void) { draw_fill(ctx, rgb(255,255,255)); int y = 10; if (tt_font_name) { tt_set_size(tt_font, 48); y += 48; tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, tt_font_name, rgb(0,0,0)); y += 10; } tt_set_size(tt_font, 22); y += 26; tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, "abcdefghijklmnopqrstuvwxyz", rgb(0,0,0)); y += 26; tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", rgb(0,0,0)); y += 26; tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, "0123456789.:,;(*!?')", rgb(0,0,0)); y += 10; int sizes[] = {7,10,13,16,19,22,25,48,64,92,0}; for (int * s = sizes; *s; ++s) { tt_set_size(tt_font, *s); y += *s + 4; tt_draw_string(ctx, tt_font, decor_left_width + 10, decor_top_height + y, preview_string, rgb(0,0,0)); } render_decorations(window, ctx, window_title); flip(ctx); } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, window, w, h); reinit_graphics_yutani(ctx, window); struct decor_bounds bounds; decor_get_bounds(window, &bounds); decor_left_width = bounds.left_width; decor_top_height = bounds.top_height; decor_right_width = bounds.right_width; decor_bottom_height = bounds.bottom_height; decor_width = bounds.width; decor_height = bounds.height; width = w - decor_left_width - decor_right_width; height = h - decor_top_height - decor_bottom_height; redraw(); yutani_window_resize_done(yctx, window); yutani_flip(yctx, window); } int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "usage: %s FONT\n", argv[0]); return 1; } tt_font = tt_font_from_file(argv[1]); if (!tt_font) { fprintf(stderr, "%s: failed to load font\n", argv[0]); return 1; } yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]); return 1; } init_decorations(); if (argc > 2) { preview_string = argv[2]; } else { char * maybe = tt_get_name_string(tt_font, 19); if (maybe) preview_string = maybe; } struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); decor_left_width = bounds.left_width; decor_top_height = bounds.top_height; decor_right_width = bounds.right_width; decor_bottom_height = bounds.bottom_height; decor_width = bounds.width; decor_height = bounds.height; window = yutani_window_create(yctx, width + decor_width, height + decor_height); yutani_window_move(yctx, window, 100, 100); tt_font_name = tt_get_name_string(tt_font, 4); if (tt_font_name) { sprintf(window_title, "%s - Font Preview", tt_font_name); } yutani_window_advertise_icon(yctx, window, window_title, "font"); ctx = init_graphics_yutani_double_buffer(window); redraw(); yutani_flip(yctx, window); int playing = 1; while (playing) { yutani_msg_t * m = yutani_poll(yctx); while (m) { if (menu_process_event(yctx, m)) { redraw(); yutani_flip(yctx, window); } switch (m->type) { 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*)(uintptr_t)wf->wid); if (win && win == window) { win->focused = wf->focused; redraw(); yutani_flip(yctx, window); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; 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; } ================================================ FILE: apps/font-tool.c ================================================ /** * @file apps/font-tool.c * @brief Print information about TrueType fonts. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include static void usage(char * argv[]) { printf( "usage: %s [-n] [FONT]\n" "Print information about TrueType fonts. If FONT is not specified,\n" "the system monospace font will be used.\n" "\n" " -n --name \033[3mPrint the stored name of the font.\033[0m\n" " -s --strings \033[3mPrint all supported entries in the names table.\033[0m\n" " -h --help \033[3mShow this help message.\033[0m\n" "\n", argv[0]); } #define SHOW_NAME (1 << 0) #define SHOW_STRINGS (1 << 1) extern char * tt_get_name_string(struct TT_Font * font, int identifier); int main(int argc, char * argv[]) { static struct option long_opts[] = { {"name", no_argument, 0, 'n'}, {"help", no_argument, 0, 'h'}, {"strings", no_argument, 0, 's'}, {0,0,0,0} }; int flags = 0; /* Read some arguments */ int index, c; while ((c = getopt_long(argc, argv, "nhs", long_opts, &index)) != -1) { if (!c) { if (long_opts[index].flag == 0) { c = long_opts[index].val; } } switch (c) { case 'h': usage(argv); return 0; break; case 'n': flags |= SHOW_NAME; break; case 's': flags |= SHOW_STRINGS; default: break; } } struct TT_Font * my_font; if (optind < argc) { my_font = tt_font_from_file(argv[optind]); if (!my_font) { fprintf(stderr, "%s: %s: Could not load font.\n", argv[0], argv[optind]); return 1; } } else { my_font = tt_font_from_shm("monospace"); if (!my_font) { fprintf(stderr, "Unknown.\n"); return 1; } } if (flags & SHOW_NAME) { fprintf(stdout, "%s\n", tt_get_name_string(my_font, 4)); } if (flags & SHOW_STRINGS) { struct IDName { int identifier; const char * description; } names[] = { {0, "Copyright"}, {1, "Font family"}, {2, "Font style"}, {3, "Subfamily identification"}, {4, "Full name"}, {5, "Version"}, {6, "PostScript name"}, {7, "Trademark notice"}, {8, "Manufacturer"}, {9, "Designer"}, {10, "Description"}, {11, "Vendor URL"}, {12, "Designer URL"}, {13, "License description"}, {14, "License URL"}, /* 15 is reserved */ {16, "Preferred family"}, {17, "Preferred subfamily"}, {18, "macOS name"}, {19, "Sample text"}, /* Other stuff */ }; for (size_t i = 0; i < sizeof(names)/sizeof(*names); ++i) { char * value = tt_get_name_string(my_font, names[i].identifier); if (value) { fprintf(stdout, "%s: %s\n", names[i].description, value); } } } return 0; } ================================================ FILE: apps/free.c ================================================ /** * @brief free - Show free / used / total RAM * * @copyright * 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 */ #include #include #include #include void show_usage(int argc, char * argv[]) { printf( "free - show available memory\n" "\n" "usage: %s [-utk?]\n" "\n" " -u \033[3mshow used instead of free\033[0m\n" " -t \033[3minclude a total\033[0m\n" " -k \033[3muse kilobytes instead of megabytes\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); } int main(int argc, char * argv[]) { int show_used = 0; int use_kilobytes = 0; int show_total = 0; int c; while ((c = getopt(argc, argv, "utk?")) != -1) { switch (c) { case 'u': show_used = 1; break; case 't': show_total = 1; break; case 'k': use_kilobytes = 1; break; case '?': show_usage(argc, argv); return 0; } } const char * unit = "kB"; FILE * f = fopen("/proc/meminfo", "r"); if (!f) return 1; int total, free, used; char buf[1024] = {0}; fgets(buf, 1024, f); char * a, * b; a = strchr(buf, ' '); a++; b = strchr(a, '\n'); *b = '\0'; total = atoi(a); fgets(buf, 1024, f); a = strchr(buf, ' '); a++; b = strchr(a, '\n'); *b = '\0'; free = atoi(a); //fscanf(f, "MemTotal: %d kB\nMemFree: %d kB\n", &total, &free); used = total - free; if (!use_kilobytes) { unit = "MB"; free /= 1024; used /= 1024; total /= 1024; } if (show_used) { printf("%d %s", used, unit); } else { printf("%d %s", free, unit); } if (show_total) { printf(" / %d %s", total, unit); } printf("\n"); return 0; } ================================================ FILE: apps/getty.c ================================================ /** * @brief getty - Manage a TTY. * * Wraps a serial port (or other dumb connection) with a pty * and manages a login for it. * * This is not really what 'getty' should do - see @ref login-loop.c * for something more akin to the "getty" on other platforms. * * @copyright * 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 */ #include #include #include #include #include #include #include #include int main(int argc, char * argv[]) { int fd_serial; char * file = "/dev/ttyS0"; char * user = NULL; if (getuid() != 0) { fprintf(stderr, "%s: only root can do that\n", argv[0]); return 1; } int opt; while ((opt = getopt(argc, argv, "a:")) != -1) { switch (opt) { case 'a': user = optarg; break; } } if (optind < argc) { file = argv[optind]; optind++; } fd_serial = open(file, O_RDWR); if (fd_serial < 0) { perror("open"); return 1; } setsid(); dup2(fd_serial, 0); dup2(fd_serial, 1); dup2(fd_serial, 2); ioctl(STDIN_FILENO, TIOCSCTTY, &(int){1}); tcsetpgrp(STDIN_FILENO, getpid()); system("stty sane"); if (optind < argc) { if (*argv[optind] >= '0' && *argv[optind] <= '9' && strlen(argv[optind]) < 30) { char tmp[100]; snprintf(tmp, 100, "stty %s", argv[optind]); system(tmp); optind++; } } if (optind < argc) { /* If there's still arguments, assume TERM value */ setenv("TERM", argv[optind], 1); optind++; } system("ttysize -q"); char * tokens[] = {"/bin/login",NULL,NULL,NULL}; if (user) { tokens[1] = "-f"; tokens[2] = user; } execvp(tokens[0], tokens); exit(1); } ================================================ FILE: apps/glogin-provider.c ================================================ /** * @brief Graphical login display. * * Called by @ref glogin to show a graphical login prompt. * * @copyright * 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-2015 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TRACE_APP_NAME "glogin-provider" static sprite_t logo; static gfx_context_t * ctx; static uint16_t win_width; static uint16_t win_height; static int uid = 0; #define USERNAME_BOX 1 #define PASSWORD_BOX 2 static int LOGO_FINAL_OFFSET = 100; static int BOX_WIDTH = 272; static int BOX_HEIGHT = 104; static int BOX_ROUNDNESS = 8; static int CENTER_BOX_X=1; static int CENTER_BOX_Y=1; static int BOX_LEFT=-1; static int BOX_RIGHT=-1; static int BOX_TOP=-1; static int BOX_BOTTOM=-1; static int BOX_COLOR_R=0; static int BOX_COLOR_G=0; static int BOX_COLOR_B=0; static int BOX_COLOR_A=127; static char * WALLPAPER = "/usr/share/wallpaper.jpg"; static char * LOGO = "/usr/share/logo_login.png"; static struct TT_Font * tt_font_thin = NULL; static struct TT_Font * tt_font_bold = NULL; #define TEXTBOX_INTERIOR_LEFT 4 #define EXTRA_TEXT_OFFSET 15 int center_x(int x) { return (win_width - x) / 2; } int center_y(int y) { return (win_height - y) / 2; } #define INPUT_SIZE 1024 int buffer_put(char * input_buffer, char c) { int input_collected = strlen(input_buffer); if (c == 8) { /* Backspace */ if (input_collected > 0) { input_collected--; input_buffer[input_collected] = '\0'; } return 0; } if (c < 10 || (c > 10 && c < 32) || c > 126) { return 0; } input_buffer[input_collected] = c; input_collected++; input_buffer[input_collected] = '\0'; if (input_collected == INPUT_SIZE - 1) { return 1; } return 0; } struct text_box { int x; int y; unsigned int width; unsigned int height; uint32_t text_color; struct login_container * parent; int is_focused:1; int is_password:1; unsigned int cursor; char * buffer; char * placeholder; }; struct login_container { int x; int y; unsigned int width; unsigned int height; struct text_box * username_box; struct text_box * password_box; int show_error:1; }; void draw_text_box(gfx_context_t * ctx, struct text_box * tb) { int x = tb->parent->x + tb->x; int y = tb->parent->y + tb->y; if (tb->is_focused) { draw_rounded_rectangle(ctx, 1 + x, 1 + y, tb->width - 2, tb->height - 2, 4, rgb(8,193,236)); draw_rounded_rectangle(ctx, 2 + x, 2 + y, tb->width - 4, tb->height - 4, 4, rgb(244,244,244)); } else { draw_rounded_rectangle(ctx, 1 + x, 1 + y, tb->width - 2, tb->height - 2, 4, rgb(158,169,177)); } /* Line width 2? */ char * text = tb->buffer; char password_circles[512]; uint32_t color = tb->text_color; if (strlen(tb->buffer) == 0 && !tb->is_focused) { text = tb->placeholder; color = rgba(0,0,0,127); } else if (tb->is_password) { strcpy(password_circles, ""); for (unsigned int i = 0; i < strlen(tb->buffer); ++i) { strcat(password_circles, "●"); } text = password_circles; } tt_set_size(tt_font_thin, 13); gfx_context_t * clipped = init_graphics_subregion(ctx, x + 2, y + 2, tb->width - 4, tb->height - 4); tt_draw_string(clipped, tt_font_thin, 2, 13, text, color); if (tb->is_focused) { int width = tt_string_width(tt_font_thin, text); draw_line(clipped, width + 2, width + 2, 0, tb->height - 4, tb->text_color); } free(clipped); } void draw_login_container(gfx_context_t * ctx, struct login_container * lc) { /* Draw rounded rectangle */ draw_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)); /* Draw labels */ if (lc->show_error) { char * error_message = "Incorrect username or password."; tt_set_size(tt_font_thin, 13); tt_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)); } draw_text_box(ctx, lc->username_box); draw_text_box(ctx, lc->password_box); } /** * Get hostname information updated with the current time. * * @param hostname */ static void get_updated_hostname_with_time_info(char hostname[]) { // get hostname char _hostname[256]; gethostname(_hostname, 255); // get current time struct tm * timeinfo; struct timeval now; gettimeofday(&now, NULL); //time(NULL); timeinfo = localtime((time_t *)&now.tv_sec); // format the hostname info char _date[256]; strftime(_date, 256, "%a %B %d %Y", timeinfo); sprintf(hostname, "%s // %s", _hostname, _date); } int main (int argc, char ** argv) { if (getuid() != 0) { return 1; } fprintf(stdout, "Hello\n"); yutani_t * y = yutani_init(); if (!y) { fprintf(stderr, "[glogin] Connection to server failed.\n"); return 1; } /* Load config */ { confreader_t * conf = confreader_load("/etc/glogin.conf"); if (conf) { LOGO_FINAL_OFFSET = confreader_intd(conf, "style", "logo_padding", LOGO_FINAL_OFFSET); BOX_WIDTH = confreader_intd(conf, "style", "box_width", BOX_WIDTH); BOX_HEIGHT = confreader_intd(conf, "style", "box_height", BOX_HEIGHT); BOX_ROUNDNESS = confreader_intd(conf, "style", "box_roundness", BOX_ROUNDNESS); CENTER_BOX_X = confreader_intd(conf, "style", "center_box_x", CENTER_BOX_X); CENTER_BOX_Y = confreader_intd(conf, "style", "center_box_y", CENTER_BOX_Y); BOX_LEFT = confreader_intd(conf, "style", "box_left", BOX_LEFT); BOX_RIGHT = confreader_intd(conf, "style", "box_right", BOX_RIGHT); BOX_TOP = confreader_intd(conf, "style", "box_top", BOX_TOP); BOX_BOTTOM = confreader_intd(conf, "style", "box_bottom", BOX_BOTTOM); BOX_COLOR_R = confreader_intd(conf, "style", "box_color_r", BOX_COLOR_R); BOX_COLOR_G = confreader_intd(conf, "style", "box_color_g", BOX_COLOR_G); BOX_COLOR_B = confreader_intd(conf, "style", "box_color_b", BOX_COLOR_B); BOX_COLOR_A = confreader_intd(conf, "style", "box_color_a", BOX_COLOR_A); WALLPAPER = confreader_getd(conf, "image", "wallpaper", WALLPAPER); LOGO = confreader_getd(conf, "image", "logo", LOGO); confreader_free(conf); } TRACE("Loading complete"); } TRACE("Loading logo..."); load_sprite(&logo, LOGO); TRACE("... done."); /* Generate surface for background */ sprite_t * bg_sprite; int width = y->display_width; int height = y->display_height; char * bg_cache = NULL; /* Do something with a window */ TRACE("Connecting to window server..."); yutani_window_t * wina = yutani_window_create_flags(y, width, height, YUTANI_WINDOW_FLAG_DISALLOW_RESIZE | YUTANI_WINDOW_FLAG_DISALLOW_DRAG); assert(wina); ctx = init_graphics_yutani_double_buffer(wina); draw_fill(ctx, rgba(0,0,0,255)); TRACE("... done."); tt_font_thin = tt_font_from_shm("sans-serif"); tt_font_bold = tt_font_from_shm("sans-serif.bold"); redo_everything: win_width = width; win_height = height; TRACE("Loading wallpaper..."); { sprite_t * wallpaper = malloc(sizeof(sprite_t)); load_sprite(wallpaper, WALLPAPER); float x = (float)width / (float)wallpaper->width; float y = (float)height / (float)wallpaper->height; int nh = (int)(x * (float)wallpaper->height); int nw = (int)(y * (float)wallpaper->width);; bg_sprite = create_sprite(width, height, ALPHA_OPAQUE); gfx_context_t * bg = init_graphics_sprite(bg_sprite); if (nw > width) { draw_sprite_scaled(bg, wallpaper, (width - nw) / 2, 0, nw, height); } else { draw_sprite_scaled(bg, wallpaper, 0, (height - nh) / 2, width, nh); } /* Three box blurs = good enough approximation of a guassian, but faster*/ blur_context_box(bg, 20); blur_context_box(bg, 20); blur_context_box(bg, 20); free(bg); free(wallpaper); } TRACE("... done."); draw_fill(ctx, rgb(0,0,0)); draw_sprite(ctx, bg_sprite, center_x(width), center_y(height)); bg_cache = malloc(sizeof(uint32_t) * width * height); memcpy(bg_cache, ctx->backbuffer, sizeof(uint32_t) * width * height); while (1) { #if 0 flip(ctx); yutani_flip(y, wina); #endif //yutani_set_stack(y, wina, 0); yutani_focus_window(y, wina->wid); char username[INPUT_SIZE] = {0}; char password[INPUT_SIZE] = {0}; char hostname[512]; // we do it here to calculate the final string position get_updated_hostname_with_time_info(hostname); char kernel_v[512]; { struct utsname u; uname(&u); char * os_name_ = "ToaruOS"; snprintf(kernel_v, 512, "%s %s", os_name_, u.release); } uid = 0; int box_x, box_y; if (CENTER_BOX_X) { box_x = center_x(BOX_WIDTH); } else if (BOX_LEFT == -1) { box_x = win_width - BOX_RIGHT - BOX_WIDTH; } else { box_x = BOX_LEFT; } if (CENTER_BOX_Y) { box_y = center_y(0) + 8; } else if (BOX_TOP == -1) { box_y = win_width - BOX_BOTTOM - BOX_HEIGHT; } else { box_y = BOX_TOP; } int focus = 0; tt_set_size(tt_font_bold, 12); int hostname_label_left = width - 10 - tt_string_width(tt_font_bold, hostname); int kernel_v_label_left = 10; struct text_box username_box = { (BOX_WIDTH - 170) / 2, 30, 170, 20, rgb(0,0,0), NULL, 0, 0, 0, username, "Username" }; struct text_box password_box = { (BOX_WIDTH - 170) / 2, 58, 170, 20, rgb(0,0,0), NULL, 0, 1, 0, password, "Password" }; struct login_container lc = { box_x, box_y, BOX_WIDTH, BOX_HEIGHT, &username_box, &password_box, 0 }; username_box.parent = &lc; password_box.parent = &lc; while (1) { focus = 0; memset(username, 0x0, INPUT_SIZE); memset(password, 0x0, INPUT_SIZE); while (1) { // update time info get_updated_hostname_with_time_info(hostname); memcpy(ctx->backbuffer, bg_cache, sizeof(uint32_t) * width * height); draw_sprite(ctx, &logo, center_x(logo.width), center_y(logo.height) - LOGO_FINAL_OFFSET); tt_draw_string_shadow(ctx, tt_font_bold, hostname, 12, hostname_label_left, height - 22, rgb(255,255,255), rgb(0,0,0), 4); tt_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); if (focus == USERNAME_BOX) { username_box.is_focused = 1; password_box.is_focused = 0; } else if (focus == PASSWORD_BOX) { username_box.is_focused = 0; password_box.is_focused = 1; } else { username_box.is_focused = 0; password_box.is_focused = 0; } draw_login_container(ctx, &lc); flip(ctx); yutani_flip(y, wina); struct yutani_msg_key_event kbd; struct yutani_msg_window_mouse_event mou; int msg_type = 0; collect_events: do { yutani_msg_t * msg = yutani_poll(y); switch (msg->type) { case YUTANI_MSG_KEY_EVENT: { struct yutani_msg_key_event * ke = (void*)msg->data; if (ke->event.action == KEY_ACTION_DOWN) { memcpy(&kbd, ke, sizeof(struct yutani_msg_key_event)); msg_type = 1; } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)msg->data; memcpy(&mou, me, sizeof(struct yutani_msg_mouse_event)); msg_type = 2; } break; case YUTANI_MSG_WELCOME: { struct yutani_msg_welcome * mw = (void*)msg->data; yutani_window_resize(y, wina, mw->display_width, mw->display_height); } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)msg->data; width = wr->width; height = wr->height; yutani_window_resize_accept(y, wina, width, height); reinit_graphics_yutani(ctx, wina); yutani_window_resize_done(y, wina); sprite_free(bg_sprite); free(bg_cache); goto redo_everything; } break; } free(msg); } while (!msg_type); if (msg_type == 1) { if (kbd.event.keycode == '\n') { if (focus == USERNAME_BOX) { focus = PASSWORD_BOX; continue; } else if (focus == PASSWORD_BOX) { break; } else { focus = USERNAME_BOX; continue; } } if (kbd.event.keycode == '\t') { if (focus == USERNAME_BOX) { focus = PASSWORD_BOX; } else { focus = USERNAME_BOX; } continue; } if (kbd.event.key) { if (!focus) { focus = USERNAME_BOX; } if (focus == USERNAME_BOX) { buffer_put(username, kbd.event.key); } else if (focus == PASSWORD_BOX) { buffer_put(password, kbd.event.key); } } } else if (msg_type == 2) { if ((mou.command == YUTANI_MOUSE_EVENT_DOWN && mou.buttons & YUTANI_MOUSE_BUTTON_LEFT) || (mou.command == YUTANI_MOUSE_EVENT_CLICK)) { /* Determine if we were inside of a text box */ if (mou.new_x >= (int)lc.x + (int)username_box.x && mou.new_x <= (int)lc.x + (int)username_box.x + (int)username_box.width && mou.new_y >= (int)lc.y + (int)username_box.y && mou.new_y <= (int)lc.y + (int)username_box.y + (int)username_box.height) { /* Ensure this box is focused. */ focus = USERNAME_BOX; continue; } else if ( (int)mou.new_x >= (int)lc.x + (int)password_box.x && (int)mou.new_x <= (int)lc.x + (int)password_box.x + (int)password_box.width && (int)mou.new_y >= (int)lc.y + (int)password_box.y && (int)mou.new_y <= (int)lc.y + (int)password_box.y + (int)password_box.height) { /* Ensure this box is focused. */ focus = PASSWORD_BOX; continue; } else { focus = 0; continue; } } else { goto collect_events; } } } fprintf(stdout, "USER %s\n", username); fprintf(stdout, "PASS %s\n", password); fprintf(stdout, "AUTH\n"); char tmp[1024]; fgets(tmp, 1024, stdin); if (!strcmp(tmp,"FAIL\n")) { lc.show_error = 1; continue; } else if (!strcmp(tmp,"SUCC\n")) { fprintf(stderr,"Success!\n"); goto _success; } } } _success: yutani_close(y, wina); return 0; } ================================================ FILE: apps/glogin.c ================================================ /** * @brief Graphical login daemon. * * Launches graphical login windows and manages login sessions. * * @copyright * 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-2015 K. Lange */ #include #include #include #include #include #include #include #include #include #include #define TRACE_APP_NAME "glogin" int main (int argc, char ** argv) { if (getuid() != 0) { return 1; } /* Ensure a somewhat sane environment going in */ TRACE("Graphical login starting."); yutani_init(); setenv("USER", "root", 1); setenv("HOME", "/", 1); setenv("SHELL", "/bin/sh", 1); setenv("PATH", "/usr/bin:/bin", 0); setenv("WM_THEME", "fancy", 0); while (1) { char * username = NULL; char * password = NULL; int uid = -1; int com_pipe[2], rep_pipe[2]; pipe(com_pipe); pipe(rep_pipe); TRACE("Starting login client..."); pid_t _gui_login = fork(); if (!_gui_login) { dup2(com_pipe[1], STDOUT_FILENO); dup2(rep_pipe[0], STDIN_FILENO); close(com_pipe[0]); close(rep_pipe[1]); TRACE("In client..."); char * args[] = {"/bin/glogin-provider", NULL}; execvp(args[0], args); TRACE("Exec failure?"); exit(1); } close(com_pipe[1]); close(rep_pipe[0]); FILE * com = fdopen(com_pipe[0], "r"); FILE * rep = fdopen(rep_pipe[1], "w"); while (!feof(com)) { char buf[1024]; /* line length? */ char * cmd = fgets(buf, sizeof(buf), com); size_t r = strlen(cmd); if (cmd && r) { if (cmd[r-1] == '\n') { cmd[r-1] = '\0'; r--; } if (!strcmp(buf,"RESTART")) { TRACE("Client requested system restart, rebooting."); system("reboot"); } else if (!strcmp(buf,"Hello")) { TRACE("Hello received from client."); } else if (!strncmp(buf,"USER ",5)) { TRACE("Username received."); if (username) free(username); username = strdup(buf + 5); } else if (!strncmp(buf,"PASS ",5)) { TRACE("Password received."); if (password) free(password); password = strdup(buf + 5); } else if (!strcmp(buf,"AUTH")) { TRACE("Perform auth request, client wants answer."); if (!username || !password) { fprintf(rep, "FAIL\n"); } else { uid = toaru_auth_check_pass(username, password); if (uid < 0) { fprintf(rep, "FAIL\n"); fflush(rep); } else { fprintf(rep, "SUCC\n"); fflush(rep); break; } } } } } waitpid(_gui_login, NULL, 0); if (uid == -1) { TRACE("Not a valid session, returning login manager..."); continue; } TRACE("Starting session..."); pid_t _session_pid = fork(); if (!_session_pid) { toaru_set_credentials(uid); char * args[] = {"/bin/session", NULL}; execvp(args[0], args); exit(1); } waitpid(_session_pid, NULL, 0); TRACE("Session ended."); } return 0; } ================================================ FILE: apps/grep.c ================================================ /** * @brief grep - almost acceptable grep * * Based on the regex search matcher in bim. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2022-2026 K. Lange */ #include #include #include #include #include #include #include #include #include static int invert = 0; static int ignorecase = 0; static int quiet = 0; static int only_matching = 0; static int counts = 0; static int is_fgrep = 0; static int whole_lines = 0; static int list_files = 0; static int line_numbers = 0; static int use_color = 0; static int suppress_errors = 0; /** * This regex engine is adapted from bim. * * It has been modified to be more like POSIX BREs, but it's still pretty bad. * * While we can support * matches for square brackets, we don't support them * for groups. We don't support character classes (colons), repetition ranges, * and definitely not backrefs. */ struct MatchQualifier { int (*matchFunc)(struct MatchQualifier*,char,int); union { char matchChar; struct { char * start; char * end; } matchSquares; }; }; typedef enum { MATCH_OP_EQ, MATCH_OP_GE, MATCH_OP_LE, } match_op; /** * Helper for handling smart case sensitivity. */ static inline int match_char_internal(char a, char b, int ignorecase, match_op op) { if (ignorecase) { a = tolower(a); b = tolower(b); } switch (op) { /* Equality */ case MATCH_OP_EQ: return a == b; case MATCH_OP_GE: return a >= b; case MATCH_OP_LE: return a <= b; } return 0; } /* * Basic single-character matcher. */ int match_char(struct MatchQualifier * self, char b, int mode) { return match_char_internal(self->matchChar, b, mode, MATCH_OP_EQ); } /* * Match collections of characters. */ int match_squares(struct MatchQualifier * self, char c, int mode) { char * start = self->matchSquares.start; char * end = self->matchSquares.end; char * t = start; int good = 1; if (*t == '^') { t++; good = 0; } while (t != end) { char test = *t++; if (test == '\\' && *t && strchr("\\]",*t)) { test = *t++; } else if (test == '\\' && *t == 't') { test = '\t'; t++; } if (*t == '-') { t++; if (t == end) return 0; char right = *t++; if (right == '\\' && *t && strchr("\\]",*t)) { right = *t++; } else if (right == '\\' && *t == 't') { right = '\t'; t++; } if (match_char_internal(c,test,mode,MATCH_OP_GE) && match_char_internal(c,right,mode,MATCH_OP_LE)) return good; } else { if (match_char_internal(c,test,mode,MATCH_OP_EQ)) return good; } } return !good; } /* * Match any single character. */ int match_dot(struct MatchQualifier * self, char c, int mode) { return 1; } struct Line { int actual; char * text; }; int regex_matches(struct Line * line, int j, char * needle, int ignorecase, int *len, char **needleout) { int k = j; char * match = needle; if (*match == '^') { if (j != 0) return 0; match++; } while (k < line->actual + 1) { if (needleout && *match == ')') { *needleout = match + 1; if (len) *len = k - j; return 1; } if (*match == '\0') { if (needleout) return 0; if (len) *len = k - j; return 1; } if (*match == '$') { if (k != line->actual) return 0; match++; continue; } struct MatchQualifier matcher = {match_char, .matchChar=*match}; if (*match == '.') { matcher.matchFunc = match_dot; match++; } else if (*match == '\\' && strchr("$^/\\.[]*()",match[1]) != NULL) { matcher.matchChar = match[1]; match += 2; } else if (*match == '\\' && match[1] == 't') { matcher.matchChar = '\t'; match += 2; } else if (*match == '[') { char * s = match+1; char * e = s; while (*e && *e != ']') { if (*e == '\\' && e[1] == ']') e++; e++; } if (!*e) break; /* fail match on unterminated [] sequence */ match = e + 1; matcher.matchFunc = match_squares; matcher.matchSquares.start = s; matcher.matchSquares.end = e; } else if (*match == '(') { match++; int _len; char * newmatch; if (!regex_matches(line, k, match, ignorecase, &_len, &newmatch)) break; match = newmatch; k += _len; continue; } else { match++; } if (*match == '\\' && match[1] == '?') { /* Optional */ match += 2; if (matcher.matchFunc(&matcher, line->text[k], ignorecase)) { int _len; if (regex_matches(line,k+1,match,ignorecase,&_len, needleout)) { if (len) *len = _len + k + 1 - j; return 1; } } continue; } else if ((*match == '\\' && match[1] == '+') || *match == '*') { if (*match == '\\' /* && match[1] == '+' */) { /* Must match at least one */ match += 2; if (!matcher.matchFunc(&matcher, line->text[k], ignorecase)) break; k++; } else { match++; } int _j = k; while (_j < line->actual + 1) { if (_j < line->actual && !matcher.matchFunc(&matcher, line->text[_j], ignorecase)) break; _j++; } while (_j >= k) { int _len; if (regex_matches(line, _j, match, ignorecase, &_len, needleout)) { if (len) *len = _len + _j - j; return 1; } _j--; } return 0; } else { if (!matcher.matchFunc(&matcher, line->text[k], ignorecase)) break; k++; } } return 0; } /** * Determine if 'line' matches a particular pattern 'needle' using the current * global mode settings (is_fgrep, ignorecase), starting from a given line, and * store the resulting length of a match in 'len'. */ static int subsearch_matches(struct Line * line, int j, char * needle, int *len) { if (is_fgrep) { /* Does 'line' starting at 'j' match 'needle' */ const char *n = needle; for (; *n; ++n, ++j) if (j >= line->actual || !match_char_internal(*n, line->text[j], ignorecase, MATCH_OP_EQ)) return 0; *len = n - needle; return 1; } return regex_matches(line, j, needle, ignorecase, len, NULL); } int usage(char ** argv) { #define _I "\033[3m" #define _E "\033[0m\n" fprintf(stderr, "usage: %s [-cilnoqsvxF] PATTERN [FILE...]\n" "\n" "Search for PATTERN in each file.\n" "Take care that this grep's pattern engine is limited and not POSIX-compliant.\n" "\n" " Supported options:\n" " -c " _I "Instead of printing matches, print counts of matched lines." _E " -i " _I "Ignore case in input and pattern." _E " -l " _I "Instead of printing matches, print the names of files that.\n" " match. Takes precedence over most other options." _E " -n " _I "Prefix each printed match with the line number it appeared on." _E " -o " _I "Print only the matching parts of each line, separating\n" " each match with a line feed." _E " -q " _I "Exit immediately with 0 when a match (or, with -v,\n" " non-match) is found; do not print matches." _E " -s " _I "Suppress the output of errors that would normally go to stderr." _E " -v " _I "Invert match - print lines that do not match pattern." _E " -x " _I "PATTERN must match a whole line." _E " -F " _I "Treat PATTERN as a fixed string (acts as 'fgrep')." _E " --help " _I "Show this help message." _E " --color " _I "Wrap matches with escapes to highlight them in red.\n" " The use of color may be controlled as follows:" _E " --color=auto " _I "When the output is a TTY. Same as above." _E " --color=never " _I "Never. Overrides a previous option." _E " --color-always " _I "Regardless of whether the output is a TTY." _E "\n" " Supported regex syntax:\n" " [abc] " _I "Match one of a set of characters." _E " [a-z] " _I "Match one from a range of characters." _E " (abc) " _I "Match a group.\n" " This implementation does not support repeating a group match,\n" " so groups do not really do anything." _E " . " _I "Match any single character." _E " ^ " _I "Match the start of the line." _E " $ " _I "Match the end of the line." _E "\n" " Modifiers (can be combined with [], ., and single characters):\n" " * " _I "Match any number of occurances" _E " \\? " _I "Match zero or one time" _E " \\+ " _I "Match at least one occurance" _E "\n" " Some characters can be escaped in the pattern with \\.\n" " The regex engine is not Unicode-aware.\n", argv[0]); #undef _I #undef _E return 1; } /* * POSIX says "The basename() function may modify the string pointed to by path", * and ours definitely does in order to handle trailing slashes. We don't want that, * and we're probably not going to be dealing with trailing slashes because 'path' * here is our argv[0] which should name a binary and trailing slashes would be * wrong there. This "simple_basename" doesn't muck things up. */ static char * simple_basename(char * path) { char * s = path; char * c = path; do { while (*s == '/') { s++; if (!*s) return c; /* Ends in trailing slashes, shouldn't happen... */ } c = s; s = strchr(c,'/'); } while (s); return c; } int main(int argc, char ** argv) { int opt; while ((opt = getopt(argc, argv, "?hivqocFxlns-:")) != -1) { switch (opt) { case 'h': case '?': return usage(argv); case 'i': ignorecase = 1; break; case 'v': invert = 1; break; case 'q': quiet = 1; break; case 'o': only_matching = 1; break; case 'c': counts = 1; break; case 'F': is_fgrep = 1; break; case 'x': whole_lines = 1; break; case 'l': list_files = 1; break; case 'n': line_numbers = 1; break; case 's': suppress_errors = 1; break; case '-': if (!strcmp(optarg,"help")) { return usage(argv); } else if (!strcmp(optarg,"color=never")) { use_color = 0; /* Overrides previous instances of conflicting option */ } else if (!strcmp(optarg,"color") || !strcmp(optarg,"-color=auto")) { use_color = 1; /* treat 1 as maybe */ } else if (!strcmp(optarg,"color=always")) { use_color = 2; } break; } } /* Detect if we were invoke as 'fgrep'. */ if (!strcmp(simple_basename(argv[0]),"fgrep")) { is_fgrep = 1; } /* Require at least a PATTERN argument. */ if (optind == argc) return usage(argv); char * needle = argv[optind++]; int ret = 1; /* Normal exit status: 0 if something matched, 1 if not. */ int err = 0; /* Whether an error was encountered that should override the exit status to 2. */ /* We show additional messages for detected binaries only if we are on a TTY, * and "auto" color mode should only activate if we are on a TTY. */ int is_tty = isatty(STDOUT_FILENO); if (!is_tty && use_color == 1) use_color = 0; /* When multiple file arguments are provided, include the names of files as a prefix to matches. */ int showFilenames = (optind + 1 < argc); /* This buffer is allocated by 'getline' and reused between files. */ char * buf = NULL; size_t avail = 0; do { FILE * input = stdin; ssize_t lineLength = 0; int count = 0; /* Count of matching lines. */ int isbinary = 0; /* If we have detected any NUL characters in the input. */ int ln = 0; /* Current line number. */ /* If there are file arguments, use them, but treat - as standard input. */ if (optind < argc && strcmp(argv[optind],"-")) { input = fopen(argv[optind], "r"); if (!input) { if (!suppress_errors) fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); /* We continue to read other arguments but note this error to exit with 2 later. */ err = 1; optind++; continue; } } /* POSIX says we should print '(standard input)' instead of -, so make it happy. */ const char * filename = input == stdin ? "(standard input)" : argv[optind]; while ((lineLength = getline(&buf, &avail, input)) >= 0) { ln++; /* Ignore any trailing line feed. */ if (lineLength && buf[lineLength-1] == '\n') { lineLength--; } /* Prepare Line for regex engine. */ struct Line line = { lineLength, buf }; /* Scan for any NUL characters in this line to see if it is a binary. */ if (!isbinary) { for (ssize_t i = 0; i < lineLength; ++i) { if (buf[i] == '\0') { isbinary = 1; break; } } } if (!invert) { int lastMatch = 0; /* End of the last match. */ int matched = 0; /* Whether this line has matched yet. Useful when a degenerate match means lastMatch is still 0. */ for (int j = 0; j <= lineLength;) { int len; if (subsearch_matches(&line, j, needle, &len)) { if (whole_lines && len != lineLength) break; /* If quiet gets a match at all, ony any file, we immediately exit with 0, * even ignoring any errors we may have printed about previously. */ if (quiet) return 0; /* Increment count of matching lines only if this is the fist match */ if (!matched) count++; /* If anything matched, return code is 0 except for an error. */ ret = 0; matched = 1; /* If we are just listing matching files, we're done on the first match. */ if (list_files) goto _done; /* If we're counting matching lines, we're done with this line. */ if (counts) break; /* If this is a binary file, and we are not counting matched lines, * we're done here, similar to listing. */ if (isbinary) goto _done; if (!len && only_matching) { /* Don't try to print a degenerate match here. */ lastMatch = j = j + 1; continue; } if (only_matching || !lastMatch) { /* Prefix this match result as needed. For -o, every match * is prefixed. Otherwise, we only print the prefix before * the first match. */ if (showFilenames) fprintf(stdout, "%s:", filename); if (line_numbers) fprintf(stdout, "%d:", ln); } if (only_matching) { fprintf(stdout, "%.*s\n", len, buf + j); } else { /* Print from the end of the previous match up to the end of * the current match, with color when enabled. */ fprintf(stdout, "%.*s%s%.*s%s", j - lastMatch, buf + lastMatch, use_color ? "\033[1;31m" : "", len, buf + j, use_color ? "\033[0m" : ""); } /* Update lastMatch to point to the end of this match. */ lastMatch = j + len; /* Advance to that point, ensuring we skip to the next character * if this match was degenerate. */ j = lastMatch + !len; } else { j++; } /* When matching whole lines, we only check from index 0, so break now. */ if (whole_lines) break; } /* If we are counting matches, don't print anything and go to next line. */ if (counts) continue; /* If we weren't printing just the matching text and we had a match, there * may be more of the line left to print, and we must also print a line feed * ourselves (we ignore any line feed in the actual line, and we print one * even if the line didn't actually end in one). */ if (!only_matching && matched) fprintf(stdout, "%.*s\n", (int)(lineLength - lastMatch), buf + lastMatch); } else { /* The inverse matching case is a lot simpler as we don't have to deal with * coloring submatches. Try the pattern at every point in the line, and if * it ever matches, reject it. When un-matching whole lines, check only * from index 0 and only reject if the match length is the same as the line. */ int matched = 0; for (int j = 0; j <= lineLength; ++j) { int len; if (subsearch_matches(&line, j, needle, &len)) { if (whole_lines && len != lineLength) break; matched = 1; break; } if (whole_lines) break; } /* Do nothing on a matched line. */ if (matched) continue; /* In quiet mode, if anything un-matched, immediately exit with 0, * ignoring all the other arguments. */ if (quiet) return 0; /* Otherwise any un-matched line sets our return status to 0 like any * matched line does in the non-inverse case. */ ret = 0; /* Count un-matched lines. */ count++; /* If just listing file names, we're done with this file. */ if (list_files) goto _done; /* If we're counting un-matching lines, we're done with this one. */ if (counts) continue; /* And again, same as above, if this was a binary and we weren't * counting un-matched 'lines', we're now done with this file. */ if (isbinary) goto _done; /* -ov is a weird combo, but don't print anything. */ if (only_matching) continue; /* Print relevant prefixes. */ if (showFilenames) fprintf(stdout, "%s:", filename); if (line_numbers) fprintf(stdout, "%d:", ln); /* And finally, print the un-matched line with a line feed. */ fprintf(stdout, "%.*s\n", (int)lineLength, buf); } } if (lineLength < 0 && ferror(input)) { if (!suppress_errors) fprintf(stderr, "%s: %s: %s\n", argv[0], filename, strerror(errno)); err = 1; optind++; continue; } _done: (void)0; if (input != stdin) fclose(input); if (!quiet) { if (list_files) { if (count) puts(filename); } else if (counts) { if (showFilenames) fprintf(stdout, "%s:", filename); fprintf(stdout, "%d\n", count); } else if (count && isbinary && is_tty) { fprintf(stdout, "%s: %s: binary file matches\n", argv[0], filename); } } optind++; } while (optind < argc); return err ? 2 : ret; } ================================================ FILE: apps/groups.c ================================================ /** * @brief List group memberships. * @file apps/groups.c * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include int main(int argc, char ** argv) { /* First print our egid group */ struct passwd * p = getpwuid(getegid()); if (p) { fprintf(stdout, "%s ", p->pw_name); } /* Then get the group list. */ int groupCount = getgroups(0, NULL); if (groupCount) { gid_t * myGroups = malloc(sizeof(gid_t) * groupCount); groupCount = getgroups(groupCount, myGroups); for (int i = 0; i < groupCount; ++i) { p = getpwuid(myGroups[i]); if (p) { fprintf(stdout, "%s ", p->pw_name); } } } fprintf(stdout,"\n"); endpwent(); return 0; } ================================================ FILE: apps/gsudo.c ================================================ /** * @brief gsudo - graphical implementation of sudo * * probably even less secure than the original * * @copyright * 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-2017 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #define main __main_unused #include "sudo.c" #undef main #define FONT_SIZE_TITLE 18 #define FONT_SIZE_MAIN 13 #define FONT_SIZE_PASSWD 22 #define FONT_COLOR (rgb(0,0,0)) #define FONT_RED (rgb(250,0,0)) #define BUTTON_HEIGHT 28 #define BUTTON_WIDTH 120 #define BUTTON_PADDING 18 static yutani_t * yctx; static gfx_context_t * ctx; static yutani_window_t * window; static struct TT_Font * tt_font_thin; struct TTKButton _button_cancel = { 0, 0, BUTTON_WIDTH, BUTTON_HEIGHT, "Cancel", 0 }; struct TTKButton _button_authenticate = { 410 - BUTTON_WIDTH - BUTTON_PADDING, 260, BUTTON_WIDTH, BUTTON_HEIGHT, "Authenticate", 0 }; struct TTKButton * _down_button = NULL; static int in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) { if (me->new_y >= button->y && me->new_y < button->y + button->height) { if (me->new_x >= button->x && me->new_x < button->x + button->width) { return 1; } } return 0; } static int set_hilight(struct TTKButton * button, int hilight) { if (!button && (_button_cancel.hilight || _button_authenticate.hilight)) { _button_cancel.hilight = 0; _button_authenticate.hilight = 0; return 1; } else if (button && (button->hilight != hilight)) { _button_cancel.hilight = 0; _button_authenticate.hilight = 0; button->hilight = hilight; return 1; } return 0; } static void redraw(char * username, char * password, int fails, char * argv[]) { sprite_t * prompt = create_sprite(420, 320, ALPHA_EMBEDDED); gfx_context_t * myctx = init_graphics_sprite(prompt); draw_fill(myctx, rgba(0,0,0,0)); /* Draw rounded rectangle */ draw_rounded_rectangle(myctx, 10, 10, prompt->width - 20, prompt->height - 20, 10, rgba(0,0,0,200)); blur_context_box(myctx, 10); blur_context_box(myctx, 10); draw_rounded_rectangle(myctx, 10, 10, prompt->width - 20, prompt->height - 20, 10, rgb(239,238,232)); /* Draw prompt messages */ tt_set_size(tt_font_thin, FONT_SIZE_TITLE); tt_draw_string(myctx, tt_font_thin, 30, 30 + FONT_SIZE_TITLE, "Authentication Required", FONT_COLOR); tt_set_size(tt_font_thin, FONT_SIZE_MAIN); tt_draw_string(myctx, tt_font_thin, 30, 54 + FONT_SIZE_MAIN, "Authentication is required to run the application", FONT_COLOR); tt_draw_string(myctx, tt_font_thin, 30, 72 + FONT_SIZE_MAIN, argv[1], FONT_COLOR); char prompt_message[512]; sprintf(prompt_message, "Enter password for '%s'", username); tt_draw_string(myctx, tt_font_thin, 30, 100 + FONT_SIZE_MAIN, prompt_message, FONT_COLOR); if (fails) { sprintf(prompt_message, "Try again. %d failures.", fails); tt_draw_string(myctx, tt_font_thin, 30, 146 + FONT_SIZE_MAIN, prompt_message, FONT_RED); } struct gradient_definition edge = {30, 114, rgb(0,120,220), rgb(0,120,220)}; draw_rounded_rectangle_pattern(myctx, 30, 120, prompt->width - 70, 26, 4, gfx_vertical_gradient_pattern, &edge); draw_rounded_rectangle(myctx, 32, 122, prompt->width - 74, 22, 3, rgb(250,250,250)); char password_circles[512] = {0};; strcpy(password_circles, ""); for (unsigned int i = 0; i < strlen(password) && i < 512/4; ++i) { strcat(password_circles, "●"); } gfx_context_t * clipped = init_graphics_subregion(myctx, 32, 122, prompt->width - 74, 22); tt_set_size(tt_font_thin, FONT_SIZE_PASSWD); tt_draw_string(clipped, tt_font_thin, 1, FONT_SIZE_PASSWD - 5, password_circles, FONT_COLOR); free(clipped); draw_fill(ctx, rgba(0,0,0,200)); draw_sprite(ctx, prompt, (ctx->width - prompt->width) / 2, (ctx->height - prompt->height) / 2); _button_cancel.x = (410 - 2 * (BUTTON_WIDTH + BUTTON_PADDING)) + (ctx->width - prompt->width) / 2; _button_cancel.y = 260 + (ctx->height - prompt->height) / 2; _button_authenticate.x = (410 - (BUTTON_WIDTH + BUTTON_PADDING)) + (ctx->width - prompt->width) / 2; _button_authenticate.y = 260 + (ctx->height - prompt->height) / 2; ttk_button_draw(ctx, &_button_cancel); ttk_button_draw(ctx, &_button_authenticate); sprite_free(prompt); free(myctx); flip(ctx); yutani_flip(yctx, window); } static int graphical_callback(char * username, char * password, int fails, char * argv[]) { int i = 0; redraw(username, password, fails, argv); while (1) { yutani_msg_t * msg = yutani_poll(yctx); switch (msg->type) { case YUTANI_MSG_KEY_EVENT: { struct yutani_msg_key_event * ke = (void*)msg->data; if (ke->event.action == KEY_ACTION_DOWN) { if (ke->event.keycode == KEY_ESCAPE) { return 1; } if (ke->event.keycode == '\n') { return 0; } else if (ke->event.key == 8) { if (i > 0) i--; password[i] = '\0'; } else if (ke->event.key) { password[i] = ke->event.key; password[i+1] = '\0'; i++; } redraw(username, password, fails, argv); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)msg->data; int r = 0; if (me->wid == window->wid) { if (me->command == YUTANI_MOUSE_EVENT_DOWN) { if (in_button(&_button_cancel, me)) { r |= set_hilight(&_button_cancel, 2); _down_button = &_button_cancel; } else if (in_button(&_button_authenticate, me)) { r |= set_hilight(&_button_authenticate, 2); _down_button = &_button_authenticate; } } else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) { if (_down_button) { if (in_button(_down_button, me)) { /* Handle button presses */ if (_down_button == &_button_cancel) return 1; else if (_down_button == &_button_authenticate) return 0; _down_button->hilight = 0; } } _down_button = NULL; } if (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { if (in_button(&_button_cancel, me)) { r |= set_hilight(&_button_cancel, 1); } else if (in_button(&_button_authenticate, me)) { r |= set_hilight(&_button_authenticate, 1); } else { r |= set_hilight(NULL, 0); } } else if (_down_button) { if (in_button(_down_button, me)) { r |= set_hilight(_down_button, 2); } else { r |= set_hilight(NULL, 0); } } } if (r) redraw(username, password, fails, argv); } break; case YUTANI_MSG_WINDOW_FOCUS_CHANGE: { /* If we ever lose focus, try to get it back. */ struct yutani_msg_window_focus_change * fc = (void*)msg->data; if (fc->wid == window->wid && fc->focused == 0) { yutani_focus_window(yctx, window->wid); } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: return 1; } } } int main(int argc, char ** argv) { if (argc < 2) { return 1; } yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: could not connect to compositor\n", argv[0]); return 1; } int width = yctx->display_width; int height = yctx->display_height; window = yutani_window_create_flags(yctx, width, height, YUTANI_WINDOW_FLAG_DISALLOW_RESIZE | YUTANI_WINDOW_FLAG_DISALLOW_DRAG | YUTANI_WINDOW_FLAG_ALT_ANIMATION); yutani_window_move(yctx, window, 0, 0); yutani_set_stack(yctx, window, YUTANI_ZORDER_OVERLAY); tt_font_thin = tt_font_from_shm("sans-serif"); ctx = init_graphics_yutani_double_buffer(window); return sudo_loop(graphical_callback, argv); } ================================================ FILE: apps/gunzip.c ================================================ /** * @brief gunzip - decompress gzip-compressed payloads * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2020 K. Lange */ #include #include #include #include #include #include #include static int to_stdout = 0; static int keep = 0; static int force = 0; static uint8_t _get(struct inflate_context * ctx) { return fgetc(ctx->input_priv); } static void _write(struct inflate_context * ctx, unsigned int sym) { fputc(sym, ctx->output_priv); } static int usage(int argc, char * argv[]) { fprintf(stderr, "gunzip - decompress gzip-compressed payloads\n" "\n" "usage: %s [-ckf] name...\n" "\n" " -c \033[3mwrite to stdout; implies -k\033[0m\n" " -k \033[3mkeep original files unchanged\033[0m\n" " -f \033[3mforce decompression (eg. from tty,\033[0m\n" " \033[3mor to an existing file, etc.)\033[0m\n" "\n", argv[0]); return 1; } static int endswith(char * str, char * suffix) { size_t len_suffix = strlen(suffix); size_t len_str = strlen(str); if (len_str < len_suffix) return 0; return !memcmp(str+len_str-len_suffix,suffix,len_suffix); } static int decompress_one(char * argv[], char * file) { FILE * f = strcmp(file, "-") ? fopen(file, "r") : stdin; if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], file, strerror(errno)); return 1; } if (!force && isatty(fileno(f))) { fprintf(stderr, "%s: not decompressng from terminal; use -f to override\n", argv[0]); if (f != stdin) fclose(f); return 1; } struct inflate_context ctx; ctx.get_input = _get; ctx.write_output = _write; ctx.input_priv = f; ctx.ring = NULL; if (f == stdin || to_stdout) { ctx.output_priv = stdout; } else { char * tmp = strdup(file); if (endswith(file,".gz")) { tmp[strlen(tmp)-3] = '\0'; } else if (endswith(file,".z") || endswith(file,".Z")) { tmp[strlen(tmp)-2] = '\0'; } else if (endswith(file,".tgz")) { tmp[strlen(tmp)-2] = 'a'; tmp[strlen(tmp)-1] = 'r'; } else { free(tmp); fprintf(stderr, "%s: %s: unreocognized suffix, ignoring\n", argv[0], file); fclose(f); return 1; } ctx.output_priv = fopen(tmp,force ? "w" : "wx"); if (!ctx.output_priv) { fprintf(stderr, "%s: %s: %s\n", argv[0], tmp, strerror(errno)); free(tmp); fclose(f); return 1; } free(tmp); } if (gzip_decompress(&ctx)) { fprintf(stderr, "%s: %s: unspecified error from inflate\n", argv[0], file); if (ctx.output_priv != stdout) fclose(ctx.output_priv); if (f != stdin) fclose(f); return 1; } fflush(ctx.output_priv); if (f != stdin) fclose(f); if (ctx.output_priv != stdout) fclose(ctx.output_priv); if (f != stdin && !keep) unlink(file); /* Just ignore errors, whatever. */ return 0; } int main(int argc, char * argv[]) { int opt; while ((opt = getopt(argc, argv, "?ckf")) != -1) { switch (opt) { case 'c': to_stdout = 1; keep = 1; break; case 'k': keep = 1; break; case 'f': force = 1; break; default: case '?': return usage(argc, argv); } } /* No argument, read from stdin */ if (optind >= argc) { return decompress_one(argv, "-"); } else { int ret = 0; for (int i = optind; i < argc; ++i) { ret |= decompress_one(argv, argv[i]); } return ret; } } ================================================ FILE: apps/head.c ================================================ /** * @brief head - Print the first `n` lines of a file. * * @copyright * 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 */ #include #include #include #include #include #include int main(int argc, char * argv[]) { int n = 10; int opt; int print_names = 0; int retval = 0; while ((opt = getopt(argc, argv, "n:")) != -1) { switch (opt) { case 'n': n = atoi(optarg); break; default: return 1; } } if (argc > optind + 1) { /* Multiple files */ print_names = 1; } if (argc == optind) { /* This is silly, but should work due to reasons. */ argv[optind] = "-"; argc++; } for (int i = optind; i < argc; ++i) { FILE * f = (!strcmp(argv[i],"-")) ? stdin : fopen(argv[i],"r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); retval = 1; continue; } if (print_names) { fprintf(stdout, "==> %s <==\n", (f == stdin) ? "standard input" : argv[i]); } int line = 1; while (!feof(f)) { int c = fgetc(f); if (c >= 0) { fputc(c, stdout); if (c == '\n') { line++; if (line > n) break; } } } if (f != stdin) { fclose(f); } } return retval; } ================================================ FILE: apps/hello.c ================================================ /** * @brief hello - Prints "Hello, world." * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011~2018 K. Lange */ #include int main(int argc, char * argv[]) { puts("Hello, world."); return 0; } ================================================ FILE: apps/help-browser.c ================================================ /** * @brief help-browser - Display documentation. * * This is a work-in-progress reimplementation of the help browser * from mainline ToaruOS. It is currently incomplete. * * Eventually, this should be a rich text document browser, almost * akin to a web browser. Right now it just says "Hello, world." * * @copyright * 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-2020 K. Lange */ #include #include #include #include #include #include #include #include #include #define APPLICATION_TITLE "Help Browser" #define HELP_DIR "/usr/share/help" static yutani_t * yctx; static yutani_window_t * main_window; static gfx_context_t * ctx; static int application_running = 1; static gfx_context_t * contents = NULL; static sprite_t * contents_sprite = NULL; static int contents_width = 0; static char * current_topic = NULL; static int scroll_offset; /* Markup Renderer { */ #define BASE_X 2 #define BASE_Y 2 #define LINE_HEIGHT 20 #define HEAD_HEIGHT 28 static int cursor_y = 0; static int cursor_x = 0; static list_t * state = NULL; static int current_state = 0; static struct TT_Font * tt_font_thin = NULL; static struct TT_Font * tt_font_bold = NULL; static struct TT_Font * tt_font_oblique = NULL; static struct TT_Font * tt_font_bold_oblique = NULL; static struct TT_Font * tt_font_mono = NULL; struct Char { char c; /* TODO: unicode */ char state; }; //static list_t * lines = NULL; static list_t * buffer = NULL; static struct TT_Font * state_to_font(int current_state) { if (current_state & (1 << 3)) { return tt_font_mono; } if (current_state & (1 << 0 | 1 << 2) ) { if (current_state & (1 << 1)) { return tt_font_bold_oblique; } return tt_font_bold; } else if (current_state & (1 << 1)) { return tt_font_oblique; } return tt_font_thin; } static int current_size(void) { if (current_state & (1 << 2)) { return 22; } return 13; } static int buffer_width(list_t * buffer) { int out = 0; foreach(node, buffer) { struct Char * c = node->value; char tmp[2] = {c->c, '\0'}; tt_set_size(state_to_font(c->state), current_size()); out += tt_string_width(state_to_font(c->state), tmp); } return out; } static int draw_buffer(list_t * buffer) { int x = 0; while (buffer->length) { node_t * node = list_dequeue(buffer); struct Char * c = node->value; char tmp[2] = { c->c, '\0' }; tt_set_size(state_to_font(c->state), current_size()); if (contents) { x += tt_draw_string(contents, state_to_font(c->state), cursor_x + x, cursor_y + current_size(), tmp, 0xFF000000); } else { x += tt_string_width(state_to_font(c->state), tmp); } free(c); free(node); } x += 4; return x; } static int current_line_height(void) { if (current_state & (1 << 2)) { return HEAD_HEIGHT; } else { return LINE_HEIGHT; } } static void write_buffer(void) { if (buffer_width(buffer) + cursor_x > contents_width) { cursor_x = BASE_X; cursor_y += current_line_height(); } cursor_x += draw_buffer(buffer); } static int parser_open(struct markup_state * self, void * user, struct markup_tag * tag) { if (!strcmp(tag->name, "b")) { list_insert(state, (void*)(uintptr_t)current_state); current_state |= (1 << 0); } else if (!strcmp(tag->name, "i")) { list_insert(state, (void*)(uintptr_t)current_state); current_state |= (1 << 1); } else if (!strcmp(tag->name, "h1")) { list_insert(state, (void*)(uintptr_t)current_state); current_state |= (1 << 2); } else if (!strcmp(tag->name, "mono")) { list_insert(state, (void*)(uintptr_t)current_state); current_state |= (1 << 3); } else if (!strcmp(tag->name, "br")) { write_buffer(); cursor_x = BASE_X; cursor_y += current_line_height(); } markup_free_tag(tag); return 0; } static int parser_close(struct markup_state * self, void * user, char * tag_name) { if (!strcmp(tag_name, "b")) { node_t * nstate = list_pop(state); current_state = (int)(uintptr_t)nstate->value; free(nstate); } else if (!strcmp(tag_name, "i")) { node_t * nstate = list_pop(state); current_state = (int)(uintptr_t)nstate->value; free(nstate); } else if (!strcmp(tag_name, "mono")) { node_t * nstate = list_pop(state); current_state = (int)(uintptr_t)nstate->value; free(nstate); } else if (!strcmp(tag_name, "h1")) { write_buffer(); cursor_x = BASE_X; cursor_y += current_line_height(); node_t * nstate = list_pop(state); current_state = (int)(uintptr_t)nstate->value; free(nstate); } return 0; } static int parser_data(struct markup_state * self, void * user, char * data) { char * c = data; while (*c) { if (*c == ' ' && !(current_state & (1 << 3))) { if (buffer->length) { write_buffer(); } } else if (*c == '\n') { if (buffer->length) { write_buffer(); } if (current_state & (1 << 3)) { cursor_x = BASE_X; cursor_y += current_line_height(); } } else { int chr = *c; if (*c == '&') { if (c[1] == 'l' && c[2] == 't' && c[3] == ';') { c += 3; chr = '<'; } else if (c[1] == 'g' && c[2] == 't' && c[3] == ';') { c += 3; chr = '>'; } } struct Char * ch = malloc(sizeof(struct Char)); ch->c = chr; ch->state = current_state; list_insert(buffer, ch); } c++; } return 0; } /* } End Markup Renderer */ static struct menu_bar menu_bar = {0}; static struct menu_bar_entries menu_entries[] = { {"File", "file"}, {"Go", "go"}, {"Help", "help"}, {NULL, NULL}, }; static void _menu_action_exit(struct MenuEntry * entry) { application_running = 0; } static void reinitialize_contents(void) { if (contents) { free(contents); } if (contents_sprite) { sprite_free(contents_sprite); } struct decor_bounds bounds; decor_get_bounds(main_window, &bounds); contents = NULL; contents_width = main_window->width - bounds.width; { struct markup_state * parser = markup_init(NULL, parser_open, parser_close, parser_data); cursor_y = BASE_Y; cursor_x = BASE_X; state = list_create(); buffer = list_create(); char * str = current_topic; while (*str) { if (markup_parse(parser, *str++)) { fprintf(stderr,"There was an error.\n"); return; } } markup_finish(parser); write_buffer(); list_free(state); free(state); free(buffer); } contents_sprite = create_sprite(contents_width, cursor_y + current_size() + 20, ALPHA_EMBEDDED); contents = init_graphics_sprite(contents_sprite); draw_fill(contents, rgb(255,255,255)); { struct markup_state * parser = markup_init(NULL, parser_open, parser_close, parser_data); cursor_y = BASE_Y; cursor_x = BASE_X; state = list_create(); buffer = list_create(); char * str = current_topic; while (*str) { if (markup_parse(parser, *str++)) { fprintf(stderr,"There was an error.\n"); return; } } markup_finish(parser); write_buffer(); list_free(state); free(state); free(buffer); } } static void redraw_window(void) { draw_fill(ctx, rgb(255,255,255)); render_decorations(main_window, ctx, APPLICATION_TITLE); struct decor_bounds bounds; decor_get_bounds(main_window, &bounds); menu_bar.x = bounds.left_width; menu_bar.y = bounds.top_height; menu_bar.width = ctx->width - bounds.width; menu_bar.window = main_window; menu_bar_render(&menu_bar, ctx); gfx_clear_clip(ctx); gfx_add_clip(ctx, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT, ctx->width - bounds.width, ctx->height - MENU_BAR_HEIGHT - bounds.height); draw_sprite(ctx, contents_sprite, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT - scroll_offset); gfx_clear_clip(ctx); gfx_add_clip(ctx, 0, 0, ctx->width, ctx->height); flip(ctx); yutani_flip(yctx, main_window); } static void resize_finish(int w, int h) { int height_changed = (main_window->width != (unsigned int)w); yutani_window_resize_accept(yctx, main_window, w, h); reinit_graphics_yutani(ctx, main_window); if (height_changed) { reinitialize_contents(); } redraw_window(); yutani_window_resize_done(yctx, main_window); yutani_flip(yctx, main_window); } static char * generate_index(void) { list_t * components = list_create(); list_insert(components, strdup("

Topics

\n")); /* Open /usr/share/help */ DIR * dirp = opendir("/usr/share/help"); if (dirp) { for (struct dirent * ent = readdir(dirp); ent; ent = readdir(dirp)) { if (ent->d_name[0] == '.') continue; /* TODO: Extract the actual heading... */ char tmp[1024]; snprintf(tmp, 1024, " » %s
\n", ent->d_name); list_insert(components, strdup(tmp)); } closedir(dirp); } size_t totalSize = 0; foreach(node, components) { char * s = node->value; totalSize += strlen(s); } char * out = malloc(totalSize + 1); char * ptr = out; foreach(node, components) { char * s = node->value; size_t len = strlen(s); memcpy(ptr, s, len); ptr += len; free(s); } *ptr = '\0'; list_free(components); return out; } static void navigate(const char * t) { if (current_topic) free(current_topic); /* Is this a special file? */ if (strstr(t, "special:") == t) { const char * pageName = t + 8; if (!strcmp(pageName, "contents")) { /* Oh boy... */ current_topic = generate_index(); } else { current_topic = strdup("File not found."); } } else { char file_path[1024]; if (t[0] == '/') { sprintf(file_path, "%s", t); } else { sprintf(file_path, "%s/%s", HELP_DIR, t); } FILE * f = fopen(file_path, "r"); if (!f) { current_topic = strdup("File not found."); } else { fseek(f, 0, SEEK_END); size_t size = ftell(f); fseek(f, 0, SEEK_SET); current_topic = malloc(size+1); fread(current_topic, 1, size, f); current_topic[size] = '\0'; fclose(f); } } reinitialize_contents(); redraw_window(); } #define SCROLL_AMOUNT 120 static void _scroll_up(void) { scroll_offset -= SCROLL_AMOUNT; if (scroll_offset < 0) { scroll_offset = 0; } } static void _scroll_down(void) { struct decor_bounds bounds; decor_get_bounds(main_window, &bounds); int available_height = main_window->height - bounds.height - MENU_BAR_HEIGHT; if (available_height > contents->height) { scroll_offset = 0; } else { scroll_offset += SCROLL_AMOUNT; if (scroll_offset > contents->height - available_height) { scroll_offset = contents->height - available_height; } } } static void _menu_action_navigate(struct MenuEntry * entry) { /* go to entry->action */ struct MenuEntry_Normal * _entry = (void*)entry; navigate(_entry->action); } #if 0 static void _menu_action_back(struct MenuEntry * entry) { /* go back */ } static void _menu_action_forward(struct MenuEntry * entry) { /* go forward */ } #endif static void _menu_action_about(struct MenuEntry * entry) { /* Show About dialog */ char about_cmd[1024] = "\0"; strcat(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\" "); char coords[100]; sprintf(coords, "%d %d &", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2); strcat(about_cmd, coords); system(about_cmd); redraw_window(); } static void redraw_window_callback(struct menu_bar * self) { (void)self; redraw_window(); } int main(int argc, char * argv[]) { yctx = yutani_init(); init_decorations(); main_window = yutani_window_create(yctx, 640, 480); yutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2); ctx = init_graphics_yutani_double_buffer(main_window); tt_font_thin = tt_font_from_shm("sans-serif"); tt_font_bold = tt_font_from_shm("sans-serif.bold"); tt_font_oblique = tt_font_from_shm("sans-serif.italic"); tt_font_bold_oblique = tt_font_from_shm("sans-serif.bolditalic"); tt_font_mono = tt_font_from_shm("monospace"); yutani_window_advertise_icon(yctx, main_window, APPLICATION_TITLE, "help"); menu_bar.entries = menu_entries; menu_bar.redraw_callback = redraw_window_callback; menu_bar.set = menu_set_create(); struct MenuList * m = menu_create(); /* File */ menu_insert(m, menu_create_normal("exit",NULL,"Exit", _menu_action_exit)); menu_set_insert(menu_bar.set, "file", m); m = menu_create(); /* Go */ menu_insert(m, menu_create_normal("home","0_index.trt","Home",_menu_action_navigate)); menu_insert(m, menu_create_normal("bookmark","special:contents","Topics",_menu_action_navigate)); #if 0 /* TODO: History */ menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal("back",NULL,"Back",_menu_action_back)); menu_insert(m, menu_create_normal("forward",NULL,"Forward",_menu_action_forward)); #endif menu_set_insert(menu_bar.set, "go", m); m = menu_create(); menu_insert(m, menu_create_normal("help","help-browser.trt","Contents",_menu_action_navigate)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal("star",NULL,"About " APPLICATION_TITLE,_menu_action_about)); menu_set_insert(menu_bar.set, "help", m); if (argc > 1) { navigate(argv[1]); } else { navigate("0_index.trt"); } while (application_running) { yutani_msg_t * m = yutani_poll(yctx); while (m) { if (menu_process_event(yctx, m)) { redraw_window(); } 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->wid == main_window->wid) { switch (ke->event.keycode) { case 'f': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[0]); } break; case 'g': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[1]); } break; case 'h': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[2]); } break; case 'q': _menu_action_exit(NULL); break; } } } 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*)(uintptr_t)wf->wid); if (win == main_window) { win->focused = wf->focused; redraw_window(); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; if (wr->wid == main_window->wid) { resize_finish(wr->width, wr->height); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; yutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid); if (win == main_window) { int result = decor_handle_event(yctx, m); switch (result) { case DECOR_CLOSE: _menu_action_exit(NULL); break; case DECOR_RIGHT: /* right click in decoration, show appropriate menu */ decor_show_default_menu(main_window, main_window->x + me->new_x, main_window->y + me->new_y); break; default: /* Other actions */ break; } /* Menu bar */ menu_bar_mouse_event(yctx, main_window, &menu_bar, me, me->new_x, me->new_y); struct decor_bounds bounds; decor_get_bounds(main_window, &bounds); if (me->new_x >= 0 && me->new_x <= (int)main_window->width && me->new_y > bounds.top_height + MENU_BAR_HEIGHT && me->new_y < (int)main_window->height) { if (me->buttons & YUTANI_MOUSE_SCROLL_UP) { /* Scroll up */ _scroll_up(); redraw_window(); } else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) { _scroll_down(); redraw_window(); } } } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: _menu_action_exit(NULL); break; default: break; } free(m); m = yutani_poll_async(yctx); } } } ================================================ FILE: apps/hexify.c ================================================ /** * @brief hexify - Convert binary to hex. * * This is based on the output of xxd. * * @copyright * 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 */ #include #include #include #include #include #include void print_line(unsigned char * buf, size_t width, size_t sizer, size_t offset) { fprintf(stdout, "%08zx: ", sizer); for (size_t i = 0; i < width; ) { if (i >= offset) { fprintf(stdout, " "); } else { fprintf(stdout, "%02x", buf[i]); } i++; if (i == width) break; /* in case of odd width */ if (i >= offset) { fprintf(stdout, " "); } else { fprintf(stdout, "%02x ", buf[i]); } i++; } fprintf(stdout, " "); for (size_t i = 0; i < width; i++) { if (i >= offset) { fprintf(stdout, " "); } else { if (isprint(buf[i])) { fprintf(stdout, "%c", buf[i]); } else { fprintf(stdout, "."); } } } fprintf(stdout, "\n"); } static int stoih(size_t w, char c[w], size_t *out) { *out = 0; for (size_t i = 0; i < w; ++i) { (*out) <<= 4; if (c[i] >= '0' && c[i] <= '9') { *out |= (c[i] - '0'); } else if (c[i] >= 'A' && c[i] <= 'F') { *out |= (c[i] - 'A' + 0xA); } else if (c[i] >= 'a' && c[i] <= 'f') { *out |= (c[i] - 'a' + 0xA); } else { *out = 0; return 1; } } return 0; } static int usage(char * argv[]) { fprintf(stderr, "heixfy - convert to and from hexadecimal representation\n" "\n" "usage: %s [-w width] [-d] [file]\n" "\n" " -w width \033[3mdisplay 'width' bytes per line\033[0m\n" " \033[3m(default is 16, max is 256)\033[0m\n" " -d \033[3mconvert output from hexify back to binary data\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); return 1; } int main(int argc, char * argv[]) { size_t width = 16; /* TODO make configurable */ int opt; int direction = 0; while ((opt = getopt(argc, argv, "?w:d")) != -1) { switch (opt) { default: case '?': return usage(argv); case 'w': width = strtoul(optarg, NULL, 0); if (width == 0) width = 16; if (width > 256) { fprintf(stderr, "%s: invalid width\n", argv[0]); return 1; } break; case 'd': direction = 1; break; } } FILE * f; char * name; if (optind < argc) { f = fopen(argv[optind], "r"); name = argv[optind]; if (!f) goto _fail; } else { name = "[stdin]"; f = stdin; } if (direction == 0) { /* Convert to hexadecimal */ size_t sizer = 0; size_t offset = 0; unsigned char buf[width]; while (!feof(f)) { size_t r = fread(buf+offset, 1, width-offset, f); if (r == 0 && ferror(f)) goto _fail; offset += r; if (offset == width) { print_line(buf, width, sizer, offset); offset = 0; sizer += width; } } if (offset != 0) { print_line(buf, width, sizer, offset); } } else { /* Convert from hexify's output format */ size_t eoffset = 0; size_t lineno = 1; while (!feof(f)) { /* Read offset */ char offset_bytes[8]; size_t r = fread(&offset_bytes, 1, 8, f); if (r == 0 && ferror(f)) goto _fail; /* Convert offset for verification */ size_t offset; if (stoih(8, offset_bytes, &offset)) { fprintf(stderr, "%s: %s: syntax error (bad offset) on line %zu\n", argv[0], name, lineno); fprintf(stderr, "offset bytes: %8s\n", offset_bytes); return 1; } if (offset != eoffset) { fprintf(stderr, "%s: %s: offset mismatch on line %zu\n", argv[0], name, lineno); fprintf(stderr, "expected 0x%zx, got 0x%zx\n", offset, eoffset); return 1; } /* Read ": " */ char tmp[2]; r = fread(&tmp, 1, 2, f); if (r == 0 && ferror(f)) goto _fail; if (tmp[0] != ':' || tmp[1] != ' ') { fprintf(stderr, "%s: %s: syntax error (unexpected characters after offset) on line %zu\n", argv[0], name, lineno); return 1; } /* Read [width] characters */ for (size_t i = 0; i < width; ) { size_t byte = 0; for (size_t j = 0; i < width && j < 2; ++j, ++i) { r = fread(&tmp, 1, 2, f); if (r == 0 && ferror(f)) goto _fail; if (tmp[0] == ' ' && tmp[1] == ' ') { /* done; return */ return 0; } if (stoih(2, tmp, &byte)) { fprintf(stderr, "%s: %s: syntax error (bad byte) on line %zu\n", argv[0], name, lineno); fprintf(stderr, "byte bytes: %2s\n", tmp); return 1; } fwrite(&byte, 1, 1, stdout); } /* Read space */ r = fread(&tmp, 1, 1, f); if (r == 0 && ferror(f)) goto _fail; if (tmp[0] != ' ') { fprintf(stderr, "%s: %s: syntax error (unexpected characters after byte) on line %zu\n", argv[0], name, lineno); fprintf(stderr, "unexpected character: %c\n", tmp[0]); return 1; } } r = fread(&tmp, 1, 1, f); if (r == 0 && ferror(f)) goto _fail; if (tmp[0] != ' ') { fprintf(stderr, "%s: %s: syntax error (unexpected characters after bytes) on line %zu\n", argv[0], name, lineno); } /* Read correct number of characters, plus line feed */ char tmp2[width+2]; r = fread(&tmp2, 1, width+1, f); if (r == 0 && ferror(f)) goto _fail; tmp2[width+1] = '\0'; if (tmp2[width] != '\n') { fprintf(stderr, "%s: %s: syntax error: expected end of line, got garbage on line %zu\n", argv[0], name, lineno); fprintf(stderr, "eol data: %s\n", tmp2); } lineno++; eoffset += width; } } return 0; _fail: fprintf(stderr, "%s: %s: %s\n", argv[0], name, strerror(errno)); return 1; } ================================================ FILE: apps/highlight-source.krk ================================================ #!/bin/kuroko import fileio, syntax.highlighter, kuroko let code with fileio.open(kuroko.argv[-1]) as f: code = f.read() let highlighter = syntax.highlighter.KurokoHighlighter(code) highlighter.highlight() syntax.highlighter.toTerminal(highlighter.process()) ================================================ FILE: apps/hostname.c ================================================ /** * @brief hostname - Prints or sets the system hostname. * * @copyright * 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 */ #include #include #include int main(int argc, char * argv[]) { if ((argc > 1 && argv[1][0] == '-') || (argc < 2)) { char tmp[256] = {0}; gethostname(tmp, 255); printf("%s\n", tmp); return 0; } else { if (geteuid() != 0) { fprintf(stderr,"Must be root to set hostname.\n"); return 1; } else { sethostname(argv[1], strlen(argv[1])); FILE * file = fopen("/etc/hostname", "w"); if (!file) { return 1; } else { fprintf(file, "%s\n", argv[1]); fclose(file); return 0; } } } return 0; } ================================================ FILE: apps/ifconfig.c ================================================ /** * @file apps/ifconfig.c * @brief Network interface configuration tool. * * Manipulates and enumerates network interfaces. * * All of the APIs used in this tool are temporary and subject to change. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include extern char * _argv_0; static void ip_ntoa(const uint32_t src_addr, char * out) { snprintf(out, 16, "%d.%d.%d.%d", (src_addr & 0xFF000000) >> 24, (src_addr & 0xFF0000) >> 16, (src_addr & 0xFF00) >> 8, (src_addr & 0xFF)); } static char * flagsToStr(uint32_t flags) { static char out[1024] = {0}; char * o = out; #define FLAG(f) if (flags & IFF_ ## f) { \ if (o != out) o += sprintf(o,","); \ o += sprintf(o,"%s",#f); } \ FLAG(UP) FLAG(BROADCAST) FLAG(DEBUG) FLAG(LOOPBACK) FLAG(RUNNING) FLAG(MULTICAST) return out; } static int print_human_readable_size(char * _out, size_t s) { size_t count = 5; char * prefix = "PTGMK"; for (; count > 0 && *prefix; count--, prefix++) { size_t base = 1UL << (count * 10); if (s >= base) { size_t t = s / base; return sprintf(_out, "%zu.%1zu %cB", t, (s - t * base) / (base / 10), *prefix); } } return sprintf(_out, "%d B", (int)s); } static int open_netdev(const char * if_name) { char if_path[100]; snprintf(if_path, 100, "/dev/net/%s", if_name); return open(if_path, O_RDONLY); } static int print_interface(const char * if_name) { int netdev = open_netdev(if_name); if (netdev < 0) { perror(_argv_0); return 1; } uint32_t flags = 0; ioctl(netdev, SIOCGIFFLAGS, &flags); uint32_t mtu = 0; ioctl(netdev, SIOCGIFMTU, &mtu); fprintf(stdout,"%s: flags=%d<%s> mtu %d\n", if_name, flags, flagsToStr(flags), mtu); /* Get IPv4 address */ uint32_t ip_addr = 0; if (!ioctl(netdev, SIOCGIFADDR, &ip_addr)) { char ip_str[16]; ip_ntoa(ntohl(ip_addr), ip_str); fprintf(stdout," inet %s", ip_str); /* Netmask ? */ uint32_t netmask = 0; if (!ioctl(netdev, SIOCGIFNETMASK, &netmask)) { ip_ntoa(ntohl(netmask), ip_str); fprintf(stdout, " netmask %s", ip_str); uint32_t bcast = (ip_addr & netmask) | (~netmask); ip_ntoa(ntohl(bcast), ip_str); fprintf(stdout, " broadcast %s", ip_str); } fprintf(stdout,"\n"); } uint8_t ip6_addr[16]; if (!ioctl(netdev, SIOCGIFADDR6, &ip6_addr)) { /* TODO inet6 address to nice string */ fprintf(stdout," inet6 %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", ip6_addr[0], ip6_addr[1], ip6_addr[2], ip6_addr[3], ip6_addr[4], ip6_addr[5], ip6_addr[6], ip6_addr[7], ip6_addr[8], ip6_addr[9], ip6_addr[10], ip6_addr[11], ip6_addr[12], ip6_addr[13], ip6_addr[14], ip6_addr[15]); } /* Get ethernet address */ uint8_t mac_addr[6]; if (!ioctl(netdev, SIOCGIFHWADDR, &mac_addr)) { fprintf(stdout," ether %02x:%02x:%02x:%02x:%02x:%02x\n", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); } netif_counters_t counts; if (!ioctl(netdev, SIOCGIFCOUNTS, &counts)) { char _buf[100]; print_human_readable_size(_buf, counts.rx_bytes); fprintf(stdout," RX packets %zu bytes %zu (%s)\n", counts.rx_count, counts.rx_bytes, _buf); print_human_readable_size(_buf, counts.tx_bytes); fprintf(stdout," TX packets %zu bytes %zu (%s)\n", counts.tx_count, counts.tx_bytes, _buf); } /* TODO stats */ fprintf(stdout,"\n"); return 0; } static int print_all_interfaces(void) { int retval = 0; /* Read /dev/net for interfaces */ DIR * d = opendir("/dev/net"); if (!d) { fprintf(stderr, "%s: no network?\n", _argv_0); return 1; } struct dirent * ent; while ((ent = readdir(d))) { if (ent->d_name[0] == '.') continue; /* Retrieve data for the interface and print the results. */ if (print_interface(ent->d_name)) { retval = 1; } } closedir(d); return retval; } static int maybe_address(const char * s) { /* Our inet_addr is not great, let's help it a little... */ int dots = 0; for (;*s;s++) { if (*s == '.') { dots++; if (dots > 3) return 0; continue; } if (!isdigit(*s)) return 0; } return dots == 3; } static int parse_address(const char * cmd, const char * addr, in_addr_t * out) { if (!addr) { fprintf(stderr, "%s: %s: expected argument\n", _argv_0, cmd); return 1; } if (!maybe_address(addr)) { fprintf(stderr, "%s: %s: '%s' doesn't look like a valid address\n", _argv_0, cmd, addr); return 1; } *out = inet_addr(addr); return 0; } static int _set_address(int netdev, const char * cmd, const char * arg, const char * ioctlstr, unsigned long ioctltype) { int status; in_addr_t ip; if ((status = parse_address(cmd, arg, &ip))) return status; if ((status = ioctl(netdev, ioctltype, &ip))) { perror(ioctlstr); return status; } return 0; } #define set_address(cmd, arg, itype) _set_address(netdev, cmd, arg, #itype, itype) #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; } int main(int argc, char * argv[]) { /* Figure out what we're trying to do. */ if (argc < 2) return print_all_interfaces(); /* Handle (ignore) some common commands */ if (!strcmp(argv[1], "up") || !strcmp(argv[1],"down")) { fprintf(stderr, "%s: 'up' and 'down' commands are unsupported\n", argv[0]); return 1; } /* If there is an interface name and nothing else, print and be done with it. */ if (argc == 2) return print_interface(argv[1]); /* All other options here require a leading interface. */ int netdev = open_netdev(argv[1]); if (netdev < 0) { perror(argv[0]); return 1; } /* Now let's figure out what we want to do with remaining options */ int collected_address = 0; for (int i = 2; i < argc; ++i) { /* Is this argument an address? */ if (maybe_address(argv[i])) { /* Try to set IPv4 address */ if (collected_address) { fprintf(stderr, "%s: expected at most one bare address, but found a second\n", argv[0]); return 1; } if (set_address("inet", argv[i], SIOCSIFADDR)) return 1; collected_address = 1; } else { command_with_address("netmask", SIOCSIFNETMASK); command_with_address("gw", SIOCSIFGATEWAY); command_with_address("gateway", SIOCSIFGATEWAY); command_with_address("inet", SIOCSIFADDR); fprintf(stderr, "%s: '%s' is not an understood command\n", argv[0], argv[i]); return 1; } } return 0; } ================================================ FILE: apps/imgviewer.c ================================================ /** * @brief imgviewer - Display bitmaps in a graphical window. * * This is probably the 4th time I've (re)written a version of * this application... This uses the libtoaru_graphics sprite * functionality to load images, so it will support whatever * that ends up supporting - which at the time of writing is * just bitmaps of various types. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include /* Pointer to graphics memory */ static yutani_t * yctx; static yutani_window_t * window = NULL; static gfx_context_t * ctx = NULL; static int decor_left_width = 0; static int decor_top_height = 0; static int decor_right_width = 0; static int decor_bottom_height = 0; static int decor_width = 0; static int decor_height = 0; int left = 40; int top = 40; int width = 300; int height = 300; int current_scale = 100; sprite_t img = {0}; #define APPLICATION_TITLE "Image Viewer" static char window_title[1024] = APPLICATION_TITLE; void usage(char * argv[]) { printf( "Image Viewer - Shows images.\n" "\n" "usage: %s \033[3mimage\033[0m\n" "\n" " -? --help \033[3mShow this help message.\033[0m\n", argv[0]); } static void decors() { if (current_scale != 100) { char tmp[1100]; snprintf(tmp, 1100, "%s [%d%%]", window_title, current_scale); render_decorations(window, ctx, tmp); } else { render_decorations(window, ctx, window_title); } } void redraw() { uint32_t dark = rgb(107,107,107); uint32_t light = rgb(147,147,147); uint32_t black = rgb(0,0,0); int calc_width = img.width * (current_scale / 100.0); int calc_height = img.height * (current_scale / 100.0); int image_left = width / 2 - calc_width / 2; int image_right = image_left + calc_width; /* TODO scaling */ int image_top = height / 2 - calc_height / 2; int image_bot = image_top + calc_height; for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { uint32_t color = (x < image_left || x >= image_right || y < image_top || y >= image_bot) ? black : ((((y / 10) % 2 == 0) ^ ((x / 10) % 2 == 0)) ? dark : light); GFX(ctx,x+decor_left_width,y+decor_top_height) = color; } } if (current_scale != 100) { draw_sprite_scaled(ctx, &img, decor_left_width + image_left, decor_top_height + image_top, calc_width, calc_height); } else { draw_sprite(ctx, &img, decor_left_width + image_left, decor_top_height + image_top); } decors(); flip(ctx); } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, window, w, h); reinit_graphics_yutani(ctx, window); struct decor_bounds bounds; decor_get_bounds(window, &bounds); decor_left_width = bounds.left_width; decor_top_height = bounds.top_height; decor_right_width = bounds.right_width; decor_bottom_height = bounds.bottom_height; decor_width = bounds.width; decor_height = bounds.height; width = w - decor_left_width - decor_right_width; height = h - decor_top_height - decor_bottom_height; redraw(); yutani_window_resize_done(yctx, window); yutani_flip(yctx, window); } static int one_fifth(int scale) { int out = scale * 0.05; if (out) return out; return 1; } int main(int argc, char * argv[]) { static struct option long_opts[] = { {"help", no_argument, 0, '?'}, {0,0,0,0} }; if (argc > 1) { /* Read some arguments */ int index, c; while ((c = getopt_long(argc, argv, "h", long_opts, &index)) != -1) { if (!c) { if (long_opts[index].flag == 0) { c = long_opts[index].val; } } switch (c) { case 'h': usage(argv); exit(0); break; default: break; } } } if (optind >= argc) { usage(argv); return 1; } 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); decor_left_width = bounds.left_width; decor_top_height = bounds.top_height; decor_right_width = bounds.right_width; decor_bottom_height = bounds.bottom_height; decor_width = bounds.width; decor_height = bounds.height; int status = load_sprite(&img, argv[optind]); if (status) { fprintf(stderr, "%s: failed to open image %s\n", argv[0], argv[optind]); return 1; } width = img.width < 300 ? 300 : img.width; height = img.height < 300 ? 300 : img.height; window = yutani_window_create(yctx, width + decor_width, height + decor_height); yutani_window_move(yctx, window, left, top); snprintf(window_title, 1023, "%s - " APPLICATION_TITLE, basename(argv[1])); yutani_window_advertise_icon(yctx, window, window_title, "image"); ctx = init_graphics_yutani_double_buffer(window); redraw(); yutani_flip(yctx, window); int playing = 1; while (playing) { yutani_msg_t * m = yutani_poll(yctx); while (m) { if (menu_process_event(yctx, m)) { /* just decorations should be fine */ decors(); flip(ctx); yutani_flip(yctx, window); } 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*)(uintptr_t)wf->wid); if (win && win == window) { win->focused = wf->focused; decors(); flip(ctx); yutani_flip(yctx, window); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; 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; } if (me->wid == window->wid) { if (me->buttons & YUTANI_MOUSE_SCROLL_UP) { current_scale = current_scale + one_fifth(current_scale); redraw(); yutani_flip(yctx, window); } else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) { current_scale = current_scale - one_fifth(current_scale); redraw(); yutani_flip(yctx, window); } } } 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; } ================================================ FILE: apps/init.c ================================================ /** * @brief init - First process. * * `init` calls startup scripts and then waits for them to complete. * It also waits for orphaned proceses so they can be collected. * * `init` itself is statically-linked, so minimizing libc dependencies * is worthwhile as it reduces to the total size of init itself, which * remains in memory throughout the entire lifetime of the OS. * * Startup scripts for init are stored in /etc/startup.d and are run * in sorted alphabetical order. It is generally recommended that these * startup scripts be named with numbers at the front to ensure easy * ordering. This system of running a set of scripts on startup is * somewhat similar to how sysvinit worked, but no claims of * compatibility are made. * * Startup scripts can be any executable binary. Shell scripts are * generally used to allow easy editing, but you could also use * a binary (even a dynamically linked one) as a startup script. * `init` will wait for each startup script (that is, it will wait for * the original process it started to exit) before running the next one. * So if you wish to run daemons, be sure to fork them off and then * exit so that the rest of the startup process can continue. * * When the last startup script finishes, `init` will reboot the system. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #define INITD_PATH "/etc/startup.d" /* Initialize fd 0, 1, 2 */ void set_console(void) { syscall_open("/dev/null", 0, 0); syscall_open("/dev/null", 1, 0); syscall_open("/dev/null", 1, 0); } /* Run a startup script and wait for it to finish */ int start_options(char * args[]) { /* Fork child to run script */ int cpid = syscall_fork(); /* Child process... */ if (!cpid) { /* Pass environment from init to child */ syscall_execve(args[0], args, environ); /* exec failed, exit this subprocess */ syscall_exit(0); } /* Wait for the child process to finish */ int pid = 0; do { /* * Wait, ignoring kernel threads * (which also end up as children to init) */ pid = waitpid(-1, NULL, WNOKERN); if (pid == -1 && errno == ECHILD) { /* There are no more children */ break; } if (pid == cpid) { /* The child process finished */ break; } /* Continue while no error (or error was "interrupted") */ } while ((pid > 0) || (pid == -1 && errno == EINTR)); return cpid; } int main(int argc, char * argv[]) { /* Initialize stdin/out/err */ set_console(); /* Get directory listing for /etc/startup.d */ int initd_dir = syscall_open(INITD_PATH, 0, 0); if (initd_dir < 0) { /* No init scripts; try to start getty as a fallback */ start_options((char *[]){"/bin/getty",NULL}); } else { int count = 0, i = 0, ret = 0; /* Figure out how many entries we have with a dry run */ do { struct dirent ent; ret = syscall_readdir(initd_dir, ++count, &ent); } while (ret > 0); /* Read each directory entry */ struct dirent entries[count]; do { syscall_readdir(initd_dir, i, &entries[i]); i++; } while (i < count); /* Sort the directory entries */ int comparator(const void * c1, const void * c2) { const struct dirent * d1 = c1; const struct dirent * d2 = c2; return strcmp(d1->d_name, d2->d_name); } qsort(entries, count, sizeof(struct dirent), comparator); /* Run scripts */ for (int i = 0; i < count; ++i) { if (entries[i].d_name[0] != '.') { char path[256]; sprintf(path, "/etc/startup.d/%s", entries[i].d_name); start_options((char *[]){path, NULL}); } } } /* Self-explanatory */ syscall_reboot(); return 0; } ================================================ FILE: apps/insmod.c ================================================ /** * @brief insmod - Load kernel module * * @copyright * 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 K. Lange */ #include #include #include #include int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "Usage: %s [ARGS...]\n", argv[0]); return 1; } int status = sysfunc(TOARU_SYS_FUNC_INSMOD, &argv[1]); if (status != 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[1], strerror(errno)); return status; } return 0; } ================================================ FILE: apps/irc.c ================================================ /** * @brief irc - Internet Relay Chat client * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define _ITALIC "\033[3m" #define _END "\033[0m\n" #define VERSION_STRING "0.3.0" /* Theming */ #define TIME_FMT "%02d:%02d:%02d" #define TIME_ARGS hr, min, sec static char * nick = "toaru-user"; static char * host = NULL; static char * pass = NULL; static unsigned short port = 6667; static char * channel = NULL; static int sock_fd; static FILE * sock_r; static FILE * sock_w; struct color_pair { int fg; int bg; }; static void show_usage(int argc, char * argv[]) { fprintf(stderr, "irc - Terminal IRC client.\n" "\n" "usage: %s [-h] [-p port] [-n nick] host\n" "\n" " -p port " _ITALIC "Specify port to connect to" _END " -P pass " _ITALIC "Password for server connection" _END " -n nick " _ITALIC "Specify a nick to use" _END " -h " _ITALIC "Print this help message" _END "\n", argv[0]); exit(1); } static struct termios old; static void set_unbuffered() { tcgetattr(fileno(stdin), &old); struct termios new = old; new.c_lflag &= (~ICANON & ~ECHO); tcsetattr(fileno(stdin), TCSAFLUSH, &new); } static void set_buffered() { tcsetattr(fileno(stdin), TCSAFLUSH, &old); } static int user_color(char * user) { int i = 0; while (*user) { i += *user; user++; } i = i % 5; switch (i) { case 0: return 2; case 1: return 3; case 2: return 4; case 3: return 6; case 4: return 10; } return 0; } static int color_pairs[] = { 15, 0, 4, 2, 9, 1, 5, 3, 11, 10, 6, 14, 12, 13, 8, 7 }; static struct color_pair irc_color_to_pair(int fg, int bg) { int _fg = 0; int _bg = 0; if (fg == -1) { _fg = -1; } else { _fg = color_pairs[fg % 16]; } if (bg == -1) { _bg = -1; } else { _bg = color_pairs[bg % 16]; } return (struct color_pair){_fg, _bg}; } static void get_time(int * h, int * m, int * s) { time_t rawtime; time(&rawtime); struct tm *tm_struct = localtime(&rawtime); *h = tm_struct->tm_hour; *m = tm_struct->tm_min; *s = tm_struct->tm_sec; } static void print_color(struct color_pair t) { fprintf(stdout, "\033["); if (t.fg == -1) { fprintf(stdout,"39"); } else if (t.fg > 15) { /* TODO */ } else if (t.fg > 7) { fprintf(stdout,"9%d", t.fg - 8); } else { fprintf(stdout,"3%d", t.fg); } fprintf(stdout, ";"); if (t.bg == -1) { fprintf(stdout, "49"); } else if (t.bg > 15) { /* TODO */ } else if (t.bg > 7) { fprintf(stdout,"10%d", t.bg - 8); } else { fprintf(stdout,"4%d", t.bg); } fprintf(stdout, "m"); fflush(stdout); } static void WRITE(const char * fmt, ...) { int bold_on = 0; int italic_on = 0; va_list args; va_start(args, fmt); char * tmp; vasprintf(&tmp, fmt, args); va_end(args); struct winsize w; ioctl(0, TIOCGWINSZ, &w); fprintf(stdout,"\033[%d;1H\033[K", w.ws_row); int line_feed_pending = 0; char * c = tmp; while (*c) { if (*c == '\n') { if (line_feed_pending) { /* Print line feed */ fprintf(stdout, "\n"); } line_feed_pending = 1; c++; continue; } else { if (line_feed_pending) { line_feed_pending = 0; /* Print line feed */ fprintf(stdout, "\n"); } } if (*c == 0x03) { c++; int i = -1; int j = -1; if (*c >= '0' && *c <= '9') { i = (*c - '0'); c++; } if (*c >= '0' && *c <= '9') { i *= 10; i += (*c - '0'); c++; } if (*c == ',') { c++; if (*c >= '0' && *c <= '9') { j = (*c - '0'); c++; } if (*c >= '0' && *c <= '9') { j *= 10; j = (*c - '0'); c++; } } struct color_pair t = irc_color_to_pair(i, j); print_color(t); continue; } if (*c == 0x02) { if (bold_on) { fprintf(stdout,"\033[22m"); bold_on = 0; } else { fprintf(stdout,"\033[1m"); bold_on = 1; } c++; continue; } if (*c == 0x16) { if (italic_on) { fprintf(stdout,"\033[23m"); italic_on = 0; } else { fprintf(stdout,"\033[3m"); italic_on = 1; } c++; continue; } if (*c == 0x0f) { fprintf(stdout, "\033[0m"); c++; bold_on = 0; italic_on = 0; continue; } fprintf(stdout, "%c", *c); c++; } if (line_feed_pending) { fprintf(stdout, "\033[0m\033[K\n"); } fflush(stdout); free(tmp); } static void handle(char * line) { char * c = line; while (c < line + strlen(line)) { char * e = strstr(c, "\r\n"); if (e > line + strlen(line)) { break; } if (!e) { /* Write c */ WRITE(c); goto next; } *e = '\0'; if (strstr(c, "PING") == c) { char * t = strstr(c, ":"); fprintf(sock_w, "PONG %s\r\n", t); fflush(sock_w); goto next; } char * user, * command, * channel, * message; user = c; if (user[0] == ':') { user++; } command = strstr(user, " "); if (!command) { WRITE("%s\n", user); goto next; } command[0] = '\0'; command++; channel = strstr(command, " "); if (!channel) { WRITE("%s %s\n", user, command); goto next; } channel[0] = '\0'; channel++; message = strstr(channel, " "); if (message) { message[0] = '\0'; message++; if (message[0] == ':') { message++; } } int hr, min, sec; get_time(&hr, &min, &sec); if (!strcmp(command, "PRIVMSG")) { if (!message) continue; char * t = strstr(user, "!"); if (t) { t[0] = '\0'; } t = strstr(user, "@"); if (t) { t[0] = '\0'; } if (strstr(message, "\001ACTION ") == message) { message = message + 8; char * x = strstr(message, "\001"); if (x) *x = '\0'; WRITE(TIME_FMT " \002* \003%d%s\003\002 %s\n", TIME_ARGS, user_color(user), user, message); } else { WRITE(TIME_FMT " \00314<\003%d%s\00314>\003 %s\n", TIME_ARGS, user_color(user), user, message); } } else if (!strcmp(command, "332")) { if (!message) { continue; } /* Topic */ } else if (!strcmp(command, "JOIN")) { char * t = strstr(user, "!"); if (t) { t[0] = '\0'; } t = strstr(user, "@"); if (t) { t[0] = '\0'; } if (channel[0] == ':') { channel++; } WRITE(TIME_FMT " \00312-\003!\00312-\00311 %s\003 has joined \002%s\n", TIME_ARGS, user, channel); } else if (!strcmp(command, "PART")) { char * t = strstr(user, "!"); if (t) { t[0] = '\0'; } t = strstr(user, "@"); if (t) { t[0] = '\0'; } if (channel[0] == ':') { channel++; } WRITE(TIME_FMT " \00312-\003!\00312\003-\00310 %s\003 has left \002%s\n", TIME_ARGS, user, channel); } else if (!strcmp(command, "372")) { WRITE(TIME_FMT " \00314%s\003 %s\n", TIME_ARGS, user, message ? message : ""); } else if (!strcmp(command, "376")) { /* End of MOTD */ WRITE(TIME_FMT " \00314%s (end of MOTD)\n", TIME_ARGS, user); } else { WRITE(TIME_FMT " \00310%s %s %s %s\n", TIME_ARGS, user, command, channel, message ? message : ""); } next: if (!e) break; c = e + 2; } } static void redraw_buffer(char * buf) { struct winsize w; ioctl(0, TIOCGWINSZ, &w); char tmp[1024]; size_t left = snprintf(tmp,1024," [%s] ", channel ? channel : "(status)"); size_t avail = w.ws_col - left - 1; size_t buflen = strlen(buf); char * from = buf; if (buflen >= avail) { from = buf + (buflen - avail); } fprintf(stdout,"\033[%d;1H%s%s\033[K\033[?25h", w.ws_row, tmp, from); fflush(stdout); } void handle_input(char * buf) { fflush(stdout); if (strstr(buf, "/help") == buf) { WRITE("[help] help text goes here\n"); } else if (strstr(buf, "/quit") == buf) { char * m = strstr(buf, " "); if (m) m++; fprintf(sock_w, "QUIT :%s\r\n", m ? m : "https://github.com/klange/toaruos"); fflush(sock_w); fprintf(stderr,"\033[0m\n"); set_buffered(); exit(0); } else if (strstr(buf,"/part") == buf) { if (!channel) { fprintf(stderr, "Not in a channel.\n"); return; } char * m = strstr(buf, " "); if (m) m++; fprintf(sock_w, "PART %s%s%s\r\n", channel, m ? " :" : "", m ? m : ""); fflush(sock_w); free(channel); channel = NULL; } else if (strstr(buf,"/join ") == buf) { char * m = strstr(buf, " "); if (m) m++; fprintf(sock_w, "JOIN %s\r\n", m); fflush(sock_w); channel = strdup(m); } else if (strstr(buf, "/") == buf) { WRITE("[system] Unknown command: %s\n", buf); } else { int hr, min, sec; get_time(&hr, &min, &sec); WRITE("%02d:%02d:%02d \00314<\003\002%s\002\00314>\003 %s\n", hr, min, sec, nick, buf); fprintf(sock_w, "PRIVMSG %s :%s\r\n", channel, buf); } redraw_buffer(""); } int main(int argc, char * argv[]) { /* Option parsing */ int c; while ((c = getopt(argc, argv, "?hp:n:P:")) != -1) { switch (c) { case 'n': nick = optarg; break; case 'P': pass = optarg; break; case 'p': port = atoi(optarg); break; case 'h': case '?': default: show_usage(argc, argv); break; } } if (optind >= argc) { show_usage(argc, argv); } host = argv[optind]; /* Connect */ { sock_fd = socket(AF_INET, SOCK_STREAM, 0); fprintf(stderr, "Looking up host...\n"); struct hostent * remote = gethostbyname(host); if (!remote) { perror("gethostbyname"); return 1; } struct sockaddr_in addr; addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, remote->h_addr, remote->h_length); addr.sin_port = htons(port); fprintf(stderr, "Connecting...\n"); if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) { perror("connect"); return 1; } sock_r = fdopen(sock_fd, "r"); sock_w = fdopen(sock_fd, "w"); } set_unbuffered(); fprintf(stdout, " - Toaru IRC v %s - \n", VERSION_STRING); fprintf(stdout, " Copyright 2015-2018 K. Lange\n"); fprintf(stdout, " https://toaruos.org - https://github.com/klange/toaruos\n"); fprintf(stdout, " \n"); fprintf(stdout, " For help, type /help\n"); if (pass) { fprintf(sock_w, "PASS %s\r\n", pass); } fprintf(sock_w, "NICK %s\r\nUSER %s * 0 :%s\r\n", nick, nick, nick); fflush(sock_w); int fds[] = {sock_fd, STDIN_FILENO, sock_fd}; char net_buf[2048]; memset(net_buf, 0, 2048); int net_buf_p = 0; char buf[1024] = {0}; int buf_p = 0; while (1) { int index = fswait2(2,fds,200); if (index == 1) { /* stdin */ int c = fgetc(stdin); if (c < 0) { continue; } if (c == 0x08 || c == 0x7F) { /* Remove from buffer */ if (buf_p) { buf[buf_p-1] = '\0'; buf_p--; redraw_buffer(buf); } } else if (c == '\n') { /* Send buffer */ handle_input(buf); memset(buf, 0, 1024); buf_p = 0; } else { /* Append buffer, or check special keys */ buf[buf_p] = c; buf_p++; redraw_buffer(buf); } } else if (index == 0) { /* network */ do { int c = fgetc(sock_r); if (c < 0) continue; net_buf[net_buf_p] = c; net_buf_p++; if (c == '\n' || net_buf_p == 2046) { handle(net_buf); net_buf_p = 0; memset(net_buf, 0, 2048); redraw_buffer(buf); } } while (!_fwouldblock(sock_r)); } else { /* timer */ } } } ================================================ FILE: apps/json-test.c ================================================ /** * @brief Test suite for the JSON library. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2019-2020 K. Lange */ #include #include #include #include #include #include typedef struct JSON_Value Value; int main(int argc, char * argv[]) { { Value * result = json_parse("\"foo bar baz\""); assert(result && result->type == JSON_TYPE_STRING); assert(strcmp(result->string, "foo bar baz") == 0); } { Value * result = json_parse("\"foo \\nbar baz\""); assert(result && result->type == JSON_TYPE_STRING); assert(strcmp(result->string, "foo \nbar baz") == 0); } { Value * result = json_parse("-123"); assert(result && result->type == JSON_TYPE_NUMBER); assert(fabs(result->number - (-123.0)) < 0.0001); } { Value * result = json_parse("2e3"); assert(result && result->type == JSON_TYPE_NUMBER); assert(fabs(result->number - (2000.0)) < 0.0001); } { Value * result = json_parse("0.124"); assert(result && result->type == JSON_TYPE_NUMBER); assert(fabs(result->number - (0.124)) < 0.0001); } { Value * result = json_parse("[ 1, 2, 3 ]"); assert(result && result->type == JSON_TYPE_ARRAY); assert(result->array->length == 3); assert(fabs(((Value *)list_dequeue(result->array)->value)->number - 1.0) < 0.0001); assert(fabs(((Value *)list_dequeue(result->array)->value)->number - 2.0) < 0.0001); assert(fabs(((Value *)list_dequeue(result->array)->value)->number - 3.0) < 0.0001); } { Value * result = json_parse("true"); assert(result && result->type == JSON_TYPE_BOOL); assert(result->boolean == 1); } { Value * result = json_parse("false"); assert(result && result->type == JSON_TYPE_BOOL); assert(result->boolean == 0); } { Value * result = json_parse("null"); assert(result && result->type == JSON_TYPE_NULL); } { Value * result = json_parse("torbs"); assert(!result); } { Value * result = json_parse("{\"foo\": \"bar\", \"bix\": 123}"); assert(result && result->type == JSON_TYPE_OBJECT); hashmap_t * hash = result->object; assert(hashmap_get(hash, "foo")); assert(((Value *)hashmap_get(hash, "foo"))->type == JSON_TYPE_STRING); assert(strcmp(((Value *)hashmap_get(hash, "foo"))->string, "bar") == 0); assert(((Value *)hashmap_get(hash, "bix"))->type == JSON_TYPE_NUMBER); assert(fabs(((Value *)hashmap_get(hash, "bix"))->number - 123.0) < 0.00001); } return 0; } ================================================ FILE: apps/julia.c ================================================ /** * @brief julia - Julia Fractal Generator * * Displays Julia fractals in a window. Use the keyboard * to navigate, switch palettes, and change parameters. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GFX_(xpt, ypt) (GFX(ctx,xpt+decor_left_width,ypt+decor_top_height)) static char * app_name = "Julia Fractals"; static char * app_desc = "Julia fractal generator"; static char * app_icon = "julia"; /* Pointer to graphics memory */ static yutani_t * yctx; static yutani_window_t * window = NULL; static gfx_context_t * ctx = NULL; static int decor_left_width = 0; static int decor_top_height = 0; static int decor_right_width = 0; static int decor_bottom_height = 0; static int decor_width = 0; static int decor_height = 0; /* Julia fractals elements */ double conx = -0.752; /* real part of c */ double cony = 0.117; /* imag part of c */ double expx = 0.0; double expy = 0.0; double expz = 1.0; /* scale */ double Maxx = 2; /* X bounds */ double Minx = -2; double Maxy = 1; /* Y bounds */ double Miny = -1; double pixcorx; /* Internal values */ double pixcory; double rotation = 4.1888; /* Blue */ int maxiter = 1000; /* Iteration levels */ int explore_mode = 0; uint32_t * palette = NULL; static uint32_t hsv_to_rgb(float h, float s, float v) { float c = v * s; float hp = fmod(h, 2 * M_PI); float x = c * (1.0 - fabs(fmod(hp / 1.0472, 2) - 1.0)); float m = v - c; float rp, gp, bp; if (hp <= 1.0472) { rp = c; gp = x; bp = 0; } else if (hp <= 2.0944) { rp = x; gp = c; bp = 0; } else if (hp <= 3.1416) { rp = 0; gp = c; bp = x; } else if (hp <= 4.1888) { rp = 0; gp = x; bp = c; } else if (hp <= 5.2360) { rp = x; gp = 0; bp = c; } else { rp = c; gp = 0; bp = x; } return rgb((rp + m) * 255, (gp + m) * 255, (bp + m) * 255); } static uint32_t hue_palette(int k) { double ratio = (double)k / (double)maxiter; double hue = sin(ratio * M_PI / 2.0); return hsv_to_rgb(4.18879 * hue + rotation, 1.0, 1.0); } static uint32_t rhue_palette(int k) { double ratio = (double)k / (double)maxiter; double hue = sin(ratio * M_PI / 2.0); return hsv_to_rgb(-4.18879 * hue + rotation, 1.0, 1.0); } static uint32_t bnw_palette(int k) { return rgb(255 * k / maxiter, 255 * k / maxiter, 255 * k / maxiter); } static uint32_t mix(uint32_t base, uint32_t mixer, float ratio) { return rgb( _RED(base) * (1.0 - ratio) + _RED(mixer) * (ratio), _GRE(base) * (1.0 - ratio) + _GRE(mixer) * (ratio), _BLU(base) * (1.0 - ratio) + _BLU(mixer) * (ratio)); } static uint32_t wiki_palette(int k) { double ratio = (double)k / (double)maxiter; for (int i = 0; i < 100; ++i) { if (ratio <= 0.025) return mix(rgb(14,21,101), rgb(40,100,200), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; if (ratio <= 0.025) return mix(rgb(40,100,200), rgb(90,200,225), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; if (ratio <= 0.025) return mix(rgb(90,200,225), rgb(255,255,255), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; if (ratio <= 0.025) return mix(rgb(255,255,255), rgb(255,255,100), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; if (ratio <= 0.025) return mix(rgb(255,255,100), rgb(255,255,0), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; if (ratio <= 0.025) return mix(rgb(255,255,0), rgb(255,120,0), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; if (ratio <= 0.025) return mix(rgb(255,120,0), rgb(255,0,0), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; if (ratio <= 0.025) return mix(rgb(255,0,0), rgb(0,0,0), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; if (i < 99) { if (ratio <= 0.025) return mix(rgb(0,0,0), rgb(14,21,101), ratio / 0.025); ratio -= 0.025; ratio /= 0.975; } } return rgb(0,0,0); } static uint32_t (*palette_funcs[])(int) = { wiki_palette, hue_palette, rhue_palette, bnw_palette, }; static int current_palette = 0; static void initialize_palette(void) { if (!palette) { palette = malloc(sizeof(uint32_t) * (maxiter + 1)); } for (int k = 0; k < maxiter; ++k) { palette[k] = palette_funcs[current_palette](k); } palette[maxiter] = rgb(0,0,0); } static void next_palette(void) { current_palette = (current_palette + 1) % (sizeof(palette_funcs) / sizeof(*palette_funcs)); initialize_palette(); } int left = 40; int top = 40; int width = 300; int height = 300; static uint32_t julia(int xpt, int ypt) { long double x = (xpt * pixcorx + Minx) * expz + expx; long double y = (Maxy - ypt * pixcory) * expz + expy; long double xnew = 0; long double ynew = 0; int k = 0; for (k = 0; k < maxiter; k++) { xnew = x * x - y * y + conx; ynew = 2 * x * y + cony; x = xnew; y = ynew; if ((x * x + y * y) > 4.0) break; } return palette[k]; } static uint32_t mandelbrot(int xpt, int ypt) { long double x0 = (xpt * pixcorx + Minx) * expz + expx; long double y0 = (Maxy - ypt * pixcory) * expz + expy; long double x = 0; long double y = 0; long double xnew = 0; long double ynew = 0; int k = 0; for (k = 0; k < maxiter; k++) { xnew = x * x - y * y + x0; ynew = 2 * x * y + y0; x = xnew; y = ynew; if ((x * x + y * y) > 4.0) break; } return palette[k]; } uint32_t (*function)(int,int) = julia; #define T_I "\033[3m" #define T_N "\033[0m" void usage(char * argv[]) { printf( "%s.\n" "\n" "usage: %s [-i " T_I "iterations" T_N "] [-x " T_I "minx" T_N "]\n" " [-X " T_I "maxx" T_N "] [-c " T_I "real" T_N "] [-C " T_I "imag" T_N "]\n" " [-W " T_I "width" T_N "] [-H " T_I "height" T_N "] [-h]\n" "\n" " -i --iterations " T_I "Number of iterations to run" T_N "\n" " -x --center-x " T_I "Center X" T_N "\n" " -y --center-y " T_I "Center Y" T_N "\n" " -c --creal " T_I "Real component of c" T_N "\n" " -C --cimag " T_I "Imaginary component of c" T_N "\n" " -r --rotate " T_I "Hue rotation for color mapping" T_N "\n" " -W --width " T_I "Window width" T_N "\n" " -H --height " T_I "Window height" T_N "\n" " -h --help " T_I "Show this help message." T_N "\n", app_desc, argv[0]); } static void decors() { render_decorations(window, ctx, app_name); } static void do_line(gfx_context_t * ctx, int j) { for (int i = 0; i < width; ++i) { GFX_(i,j) = function(i,j); } memcpy(&GFXR(ctx,0,decor_top_height+j),&GFX(ctx,0,decor_top_height+j),ctx->stride); yutani_flip_region(yctx, window, decor_left_width, decor_top_height + j, width, 1); } static int step_res; static int step_n; static int step_y; static int step_i; static int processing = 0; static clock_t time_before; #define START_POINT -4 void step_once(void); void start_processing(void) { double _x = Maxx - Minx; double _y = _x / width * height; Miny = 0 - _y / 2; Maxy = _y / 2; pixcorx = (Maxx - Minx) / width; pixcory = (Maxy - Miny) / height; step_n = START_POINT; step_y = 0; step_i = 0; step_res = 64; processing = 1; draw_fill(ctx, rgb(0,0,0)); decors(); time_before = clock(); step_once(); } void draw_label(void) { clock_t time_after = clock(); char description[100]; if (explore_mode) { snprintf(description, 100, "x=%g y=%g, zoom=%g×, %ld ms%s", expx, expy, 1.0/expz, (time_after - time_before) / 1000, step_n == 0 ? "*" : ""); } else { snprintf(description, 100, "c = %g + %gi, %ld ms%s", conx, cony, (time_after - time_before) / 1000, step_n == 0 ? "*" : ""); } /* Set up a clip box */ gfx_context_t * tmp = init_graphics_subregion(ctx, decor_left_width, decor_top_height, width, height); /* Create a sprite to draw into */ sprite_t * stmp = create_sprite(width, height, ALPHA_EMBEDDED); gfx_context_t * sctx = init_graphics_sprite(stmp); /* Draw shadow */ draw_fill(sctx, rgba(0,0,0,0)); markup_draw_string(sctx, 2, height - 2, description, rgb(0,0,0)); blur_context_box(sctx, 2); blur_context_box(sctx, 2); /* Paint it twice */ draw_sprite(tmp, stmp, 0, 0); draw_sprite(tmp, stmp, 0, 0); /* Free the sprite part */ free(sctx); sprite_free(stmp); /* Now draw the white text */ markup_draw_string(tmp, 2, height - 2, description, rgb(255,255,255)); /* Free clip space */ free(tmp); flip(ctx); yutani_flip(yctx,window); } void step_once(void) { if (step_n < 0 && step_y > height) { flip(ctx); yutani_flip(yctx, window); step_res /= 2; step_y = 0; step_i = 0; step_n++; } if (step_n >= height) { processing = 0; draw_label(); return; } if (step_n == 0) { draw_label(); } if (step_n < 0) { for (int x = 0, i = 0; x < width; x += step_res, i++) { if ((step_n != START_POINT) && (step_i & 1) == 0 && (i & 1) == 0) continue; uint32_t c = function(x,step_y); for (int _y = 0; _y < step_res && _y + step_y < height; _y++) { for (int _x = 0; _x < step_res && _x + x < width; _x++) { GFX_(_x+x,_y+step_y) = c; } } } step_i += 1; step_y += step_res; } else if (step_n % 2) { do_line(ctx,height/2 + step_n/2); step_n++; } else { do_line(ctx,height/2 - step_n/2 - 1); step_n++; } } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, window, w, h); reinit_graphics_yutani(ctx, window); struct decor_bounds bounds; decor_get_bounds(window, &bounds); decor_left_width = bounds.left_width; decor_top_height = bounds.top_height; decor_right_width = bounds.right_width; decor_bottom_height = bounds.bottom_height; decor_width = bounds.width; decor_height = bounds.height; width = w - decor_left_width - decor_right_width; height = h - decor_top_height - decor_bottom_height; start_processing(); yutani_window_resize_done(yctx, window); yutani_flip(yctx, window); } static double shift_amount = 0.001; static double pan_amount = 0.1; static double zoom_amount = 2.0; static double amount(struct yutani_msg_key_event * ke, double basis) { if (ke->event.modifiers & (KEY_MOD_LEFT_SHIFT | KEY_MOD_RIGHT_SHIFT)) basis *= 10.0; if (ke->event.modifiers & (KEY_MOD_LEFT_CTRL | KEY_MOD_RIGHT_CTRL)) basis *= 5.0; return basis; } int main(int argc, char * argv[]) { if (!strcmp(basename(argv[0]),"mandelbrot")) { function = mandelbrot; app_name = "Mandelbrot Explorer"; app_desc = "Mandelbrot set plotter"; app_icon = "mandelbrot"; explore_mode = 1; expx = -0.75; } static struct option long_opts[] = { {"iterations", required_argument, 0, 'i'}, {"center-x", required_argument, 0, 'x'}, {"center-y", required_argument, 0, 'y'}, {"creal", required_argument, 0, 'c'}, {"cimag", required_argument, 0, 'C'}, {"rotate", required_argument, 0, 'r'}, {"width", required_argument, 0, 'W'}, {"height", required_argument, 0, 'H'}, {"help", no_argument, 0, 'h'}, {0,0,0,0} }; if (argc > 1) { /* Read some arguments */ int index, c; while ((c = getopt_long(argc, argv, "ni:x:X:c:C:W:H:h", long_opts, &index)) != -1) { if (!c) { if (long_opts[index].flag == 0) { c = long_opts[index].val; } } switch (c) { case 'i': maxiter = atoi(optarg); if (maxiter < 10) maxiter = 10; if (maxiter > 1000) maxiter = 1000; break; case 'x': expx = atof(optarg); break; case 'y': expy = atof(optarg); break; case 'c': conx = atof(optarg); break; case 'C': cony = atof(optarg); break; case 'r': rotation = atof(optarg); break; case 'W': width = atoi(optarg); break; case 'H': height = atoi(optarg); break; case 'h': usage(argv); exit(0); break; default: break; } } } 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); decor_left_width = bounds.left_width; decor_top_height = bounds.top_height; decor_right_width = bounds.right_width; decor_bottom_height = bounds.bottom_height; decor_width = bounds.width; decor_height = bounds.height; window = yutani_window_create(yctx, width + decor_width, height + decor_height); yutani_window_move(yctx, window, left, top); yutani_window_advertise_icon(yctx, window, app_name, app_icon); ctx = init_graphics_yutani_double_buffer(window); initialize_palette(); start_processing(); int playing = 1; int needs_redraw = 0; while (playing) { if (processing && !yutani_query(yctx)) { step_once(); continue; } yutani_msg_t * m = yutani_poll(yctx); while (m) { if (menu_process_event(yctx, m)) { /* just decorations should be fine */ decors(); flip(ctx); yutani_flip(yctx, window); } switch (m->type) { case YUTANI_MSG_KEY_EVENT: { struct yutani_msg_key_event * ke = (void*)m->data; if (ke->event.action == KEY_ACTION_DOWN) { if (explore_mode) { switch (ke->event.keycode) { case KEY_ARROW_LEFT: expx -= amount(ke, pan_amount) * expz; needs_redraw = 1; break; case KEY_ARROW_RIGHT: expx += amount(ke, pan_amount) * expz; needs_redraw = 1; break; case KEY_ARROW_UP: expy += amount(ke, pan_amount) * expz; needs_redraw = 1; break; case KEY_ARROW_DOWN: expy -= amount(ke, pan_amount) * expz; needs_redraw = 1; break; case KEY_PAGE_UP: expz /= amount(ke, zoom_amount); needs_redraw = 1; break; case KEY_PAGE_DOWN: expz *= amount(ke, zoom_amount); needs_redraw = 1; break; case 'q': playing = 0; break; case 'p': next_palette(); needs_redraw = 1; break; case 'e': explore_mode = 0; needs_redraw = 1; break; } } else { switch (ke->event.keycode) { case 'q': playing = 0; break; case KEY_ARROW_LEFT: conx -= amount(ke, shift_amount); needs_redraw = 1; break; case KEY_ARROW_RIGHT: conx += amount(ke, shift_amount); needs_redraw = 1; break; case KEY_ARROW_UP: cony += amount(ke, shift_amount); needs_redraw = 1; break; case KEY_ARROW_DOWN: cony -= amount(ke, shift_amount); needs_redraw = 1; break; case 'p': next_palette(); needs_redraw = 1; break; case 'e': explore_mode = 1; needs_redraw = 1; break; } } } } 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*)(uintptr_t)wf->wid); if (win && win == window) { win->focused = wf->focused; decors(); flip(ctx); yutani_flip(yctx, window); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; 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); } if (needs_redraw) { start_processing(); needs_redraw = 0; } } yutani_close(yctx, window); return 0; } ================================================ FILE: apps/kbd-test.c ================================================ /** * @brief Keyboard test tool * * XXX This probably doesn't work anymore. It uses the VGA text mode * region but it doesn't map it; legacy interfaces in toaru32 * just mapped this accessible to userspace... * * @copyright * 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 */ #include #include #include #include static unsigned short * textmemptr = (unsigned short *)0xB8000; static void placech(unsigned char c, int x, int y, int attr) { unsigned short *where; unsigned att = attr << 8; where = textmemptr + (y * 80 + x); *where = c | att; } static void clear_screen(void) { for (int y = 0; y < 24; ++y) { for (int x = 0; x < 80; ++x) { placech(' ', x, y, 0); /* Clear */ } } } #define BUF_SIZE 4096 static char keys[256] = {0}; static void redraw(void) { int i = 0; for (int c = 'a'; c <= 'z'; ++c, i += 2) { placech(c, i, 0, keys[c] ? 0x2 : 0x7); } } static void print_scancode(unsigned int sc) { char buf[10]; sprintf(buf, "%d", sc); int i = 0; for (char * c = buf; *c; ++c, ++i) { placech(*c, i, 1, 0x7); } for (; i < 4; ++i) { placech(' ', i, 1, 0x7); } } int main(int argc, char * argv[]) { clear_screen(); int kfd = open("/dev/kbd", O_RDONLY); key_event_t event; key_event_state_t kbd_state = {0}; while (1) { unsigned char buf[BUF_SIZE]; int r = read(kfd, buf, BUF_SIZE); for (int i = 0; i < r; ++i) { kbd_scancode(&kbd_state, buf[i], &event); if (event.keycode >= 'a' && event.keycode < 'z') { keys[event.keycode] = (event.action == KEY_ACTION_DOWN); } print_scancode(buf[i]); } redraw(); } } ================================================ FILE: apps/kcmdline.c ================================================ /** * @brief kcmdline - Parse /proc/cmdline usefully. * * Parses /proc/cmdline and provides an interface for querying * whether an argument was present, and its value if applicable. * * Also converts ASCII field separators to spaces so that cmdline * arguments can have spaces in them. * * Useful for shell scripts. * * TODO: This should probably be a library we can use in other * applications... * * @copyright * 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 */ #include #include #include #include #include #include "../kernel/misc/args.c" void show_usage(int argc, char * argv[]) { printf( "kcmdline - query the kernel command line\n" "\n" "usage: %s -g ARG...\n" " %s -q ARG...\n" "\n" " -g \033[3mprint the value for the requested argument\033[0m\n" " -q \033[3mquery whether the requested argument is present (0 = yes)\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0], argv[0]); } int main(int argc, char * argv[]) { char * cmdline = args_from_procfs(); /* Figure out what we're doing */ int opt; while ((opt = getopt(argc, argv, "?g:q:s")) != -1) { switch (opt) { case 'g': if (hashmap_has(kernel_args_map, optarg)) { char * tmp = (char*)hashmap_get(kernel_args_map, optarg); if (!tmp) { printf("%s\n", optarg); /* special case = present but not set should yield name of variable */ } else { printf("%s\n", tmp); } return 0; } else { return 1; } case 'q': return !hashmap_has(kernel_args_map,optarg); case 's': return strlen(cmdline); case '?': show_usage(argc, argv); return 1; } } fprintf(stdout, "%s\n", cmdline); } ================================================ FILE: apps/kill.c ================================================ /** * @brief kill - Send a signal to a process * * Supports signal names like any mature `kill` should. * * @copyright * 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 */ #include #include #include #include #include #include #include struct sig_def { int sig; const char * name; }; struct sig_def signals[] = { {SIGHUP,"HUP"}, {SIGINT,"INT"}, {SIGQUIT,"QUIT"}, {SIGILL,"ILL"}, {SIGTRAP,"TRAP"}, {SIGABRT,"ABRT"}, {SIGEMT,"EMT"}, {SIGFPE,"FPE"}, {SIGKILL,"KILL"}, {SIGBUS,"BUS"}, {SIGSEGV,"SEGV"}, {SIGSYS,"SYS"}, {SIGPIPE,"PIPE"}, {SIGALRM,"ALRM"}, {SIGTERM,"TERM"}, {SIGUSR1,"USR1"}, {SIGUSR2,"USR2"}, {SIGCHLD,"CHLD"}, {SIGPWR,"PWR"}, {SIGWINCH,"WINCH"}, {SIGURG,"URG"}, {SIGPOLL,"POLL"}, {SIGSTOP,"STOP"}, {SIGTSTP,"TSTP"}, {SIGCONT,"CONT"}, {SIGTTIN,"TTIN"}, {SIGTTOUT,"TTOUT"}, {SIGVTALRM,"VTALRM"}, {SIGPROF,"PROF"}, {SIGXCPU,"XCPU"}, {SIGXFSZ,"XFSZ"}, {SIGWAITING,"WAITING"}, {SIGDIAF,"DIAF"}, {SIGHATE,"HATE"}, {SIGWINEVENT,"WINEVENT"}, {SIGCAT,"CAT"}, {0,NULL}, }; void usage(char * argv[]) { printf( "%s - send a signal to another process\n" "\n" "usage: %s [-\033[3mx\033[0m] \033[3mprocess\033[0m\n" "\n" " -h --help \033[3mShow this help message.\033[0m\n" " -\033[3mx\033[0m \033[3mSignal number to send\033[0m\n" "\n", argv[0], argv[0]); } int main(int argc, char * argv[]) { int signum = SIGKILL; int pid = 0; int i = 1; if (argc < 2) { usage(argv); return 1; } if (argv[1][0] == '-') { signum = -1; if (strlen(argv[1]+1) > 3 && strstr(argv[1]+1,"SIG") == (argv[1]+1)) { struct sig_def * s = signals; while (s->name) { if (!strcmp(argv[1]+4,s->name)) { signum = s->sig; break; } s++; } } else { if (!isdigit(argv[1][1])) { struct sig_def * s = signals; while (s->name) { if (!strcmp(argv[1]+1,s->name)) { signum = s->sig; break; } s++; } } else { signum = atoi(argv[1]+1); } } if (signum == -1) { fprintf(stderr,"%s: %s: invalid signal specification\n",argv[0],argv[1]+1); return 1; } i++; } if (i == argc) { usage(argv); return 1; } int retval = 0; for (; i < argc; ++i) { pid = atoi(argv[i]); if (pid) { if (kill(pid, signum) < 0) { fprintf(stderr, "%s: (%d) %s\n", argv[0], pid, strerror(errno)); retval = 1; } } else { fprintf(stderr, "%s: invalid pid (%s)\n", argv[0], argv[i]); retval = 1; } } return retval; } ================================================ FILE: apps/killall.c ================================================ /** * @brief killall - Send signals to processes matching name * * Find processes by name and send them signals. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include typedef struct process { int pid; int ppid; int tgid; char name[100]; char path[200]; } p_t; #define LINE_LEN 4096 p_t * build_entry(struct dirent * dent) { char tmp[300]; FILE * f; char line[LINE_LEN]; sprintf(tmp, "/proc/%s/status", dent->d_name); f = fopen(tmp, "r"); p_t * proc = malloc(sizeof(p_t)); while (fgets(line, LINE_LEN, f) != NULL) { char * n = strstr(line,"\n"); if (n) { *n = '\0'; } char * tab = strstr(line,"\t"); if (tab) { *tab = '\0'; tab++; } if (strstr(line, "Pid:") == line) { proc->pid = atoi(tab); } else if (strstr(line, "PPid:") == line) { proc->ppid = atoi(tab); } else if (strstr(line, "Tgid:") == line) { proc->tgid = atoi(tab); } else if (strstr(line, "Name:") == line) { strcpy(proc->name, tab); } else if (strstr(line, "Path:") == line) { strcpy(proc->path, tab); } } if (strstr(proc->name,"python") == proc->name) { char * name = proc->path + strlen(proc->path) - 1; while (1) { if (*name == '/') { name++; break; } if (name == proc->name) break; name--; } memcpy(proc->name, name, strlen(name)+1); } if (proc->tgid != proc->pid) { char tmp[100] = {0}; sprintf(tmp, "{%s}", proc->name); memcpy(proc->name, tmp, strlen(tmp)+1); } fclose(f); return proc; } void show_usage(int argc, char * argv[]) { printf( "killall - send signal to processes with given name\n" "\n" "usage: %s [-s SIG] name\n" "\n" " -s \033[3msignal to send\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); } struct sig_def { int sig; const char * name; }; struct sig_def signals[] = { {SIGHUP,"HUP"}, {SIGINT,"INT"}, {SIGQUIT,"QUIT"}, {SIGILL,"ILL"}, {SIGTRAP,"TRAP"}, {SIGABRT,"ABRT"}, {SIGEMT,"EMT"}, {SIGFPE,"FPE"}, {SIGKILL,"KILL"}, {SIGBUS,"BUS"}, {SIGSEGV,"SEGV"}, {SIGSYS,"SYS"}, {SIGPIPE,"PIPE"}, {SIGALRM,"ALRM"}, {SIGTERM,"TERM"}, {SIGUSR1,"USR1"}, {SIGUSR2,"USR2"}, {SIGCHLD,"CHLD"}, {SIGPWR,"PWR"}, {SIGWINCH,"WINCH"}, {SIGURG,"URG"}, {SIGPOLL,"POLL"}, {SIGSTOP,"STOP"}, {SIGTSTP,"TSTP"}, {SIGCONT,"CONT"}, {SIGTTIN,"TTIN"}, {SIGTTOUT,"TTOUT"}, {SIGVTALRM,"VTALRM"}, {SIGPROF,"PROF"}, {SIGXCPU,"XCPU"}, {SIGXFSZ,"XFSZ"}, {SIGWAITING,"WAITING"}, {SIGDIAF,"DIAF"}, {SIGHATE,"HATE"}, {SIGWINEVENT,"WINEVENT"}, {SIGCAT,"CAT"}, {0,NULL}, }; int main (int argc, char * argv[]) { int signum = SIGTERM; int c; while ((c = getopt(argc, argv, "s:?")) != -1) { switch (c) { case 's': { signum = -1; if (strlen(optarg) > 3 && strstr(optarg,"SIG") == (optarg)) { struct sig_def * s = signals; while (s->name) { if (!strcmp(optarg+3,s->name)) { signum = s->sig; break; } s++; } } else { if (optarg[0] < '0' || optarg[0] > '9') { struct sig_def * s = signals; while (s->name) { if (!strcmp(optarg,s->name)) { signum = s->sig; break; } s++; } } else { signum = atoi(optarg); } } if (signum == -1) { fprintf(stderr,"%s: %s: invalid signal specification\n",argv[0],optarg); return 1; } } break; case '?': show_usage(argc, argv); return 0; } } if (optind >= argc) { show_usage(argc, argv); return 1; } int killed_something = 0; int retval = 0; for (int i = optind; i < argc; ++i) { /* Open the directory */ DIR * dirp = opendir("/proc"); struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') { p_t * proc = build_entry(ent); if (!strcmp(proc->name, argv[i])) { if (kill(proc->pid, signum) < 0) { fprintf(stderr, "%s(%d) %s\n", argv[i], proc->pid, strerror(errno)); } else { killed_something = 1; } } free(proc); } ent = readdir(dirp); } closedir(dirp); if (!killed_something) { fprintf(stderr, "%s: no process found\n", argv[i]); retval = 1; } } return retval; } ================================================ FILE: apps/krk_test_noise.krk ================================================ #!/bin/kuroko import os import gc if 'KUROKO_TEST_ENV' in os.environ: return 0 from time import sleep from fileio import open, stdin from threading import Thread let d = {} let stop = False for y in range(0x40): for x in range(0x40): d[(y,x)] = 0 class NoisePainter(Thread): def run(self): let myRando = open('/dev/urandom','rb') while not stop: let bytes = myRando.read(3) let x = bytes[0] & 0x3F let y = bytes[1] & 0x3F d[(y,x)] = bytes[2] let painters = [NoisePainter() for i in range(5)] gc.pause() for painter in painters: painter.start() def drawScreen(): print("\[[H",end="") for y in range(0x20): for x in range(0x40): let top = d[(y*2,x)] let bottom = d[(y*2+1,x)] print("\[[38","2",top,top,top,"48","2",bottom,bottom,bottom,sep=";",end="m▀") print("\[[0m") for i in range(5): drawScreen() stop = True for painter in painters: painter.join() gc.resume() drawScreen() ================================================ FILE: apps/krk_yutani_test.krk ================================================ #!/bin/kuroko from _yutani2 import (YutaniCtx, Font, rgb, rgb, MenuBar, decor_get_bounds, decor_render, MenuList, MenuEntry, MenuEntrySeparator, Message, decor_handle_event, decor_show_default_menu, MenuEntryCustom) from yutani_mainloop import Window, yctx as y, AsyncMainloop, Task, sleep import math import random let mainloop = AsyncMainloop() def close_enough(me): return me.command == 2 and math.sqrt((me.new_x - me.old_x) ** 2 + (me.new_y - me.old_y) **2) < 10 class Button: ''' Basic button widget based on draw_button ''' def __init__(self, window, x, y, width, height, title, callback): self.ctx = window self.x = x self.y = y self.width = width self.height = height self.title = title self.state = 0 self.callback = asyncCallback def __contains__(self, coord): if not isinstance(cord, tuple): return False def focus_enter(self): self.state = 1 return True def focus_leave(self): self.state = 0 return True def mouse_down(self, msg): self.state = 2 return True def mouse_up(self, msg): # TODO check if old_x, old_y is in button self.callback(self) self.state = 0 return True def draw(self): draw_button(self.ctx, self.x, self.y, self.width, self.height, self.title, self.state) class MyMenuWidget(MenuEntryCustom): def __init__(self): super().__init__() self.height = 30 self.rwidth = 148 self.dejavu = Font("sans-serif", 13) self.x = 0 self.y = 0 def render(self, ctx, offset): self.offset = offset if self.hilight: 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) self.dejavu.draw_string(ctx, f"{self.x=},{self.y=}", 2, offset + 13) else: self.dejavu.draw_string(ctx, f"{offset=}", 2, offset + 13) def activate(self, flags): print("Activated by keyboard!") def click_within(self, evt): if evt.command == 0: return True if evt.command == 2: if \ evt.new_x >= 0 and evt.new_x < self.width and \ evt.new_y >= self.offset and evt.new_y < self.offset + self.height and \ evt.old_x >= 0 and evt.old_x < self.width and \ evt.old_y >= self.offset and evt.old_y < self.offset + self.height: return True else: print(f'{evt.old_x=} {evt.old_y=} {evt.new_x=} {evt.old_y=} {self.offset=} {self.width=} {self.height=}') async def async_thingy(self): print("Schedule async callback") await sleep(2) print("Finish async callback") def mouse_event(self, evt): self.x = evt.new_x self.y = evt.new_y if self.click_within(evt): Task(self.async_thingy()) print("Clicked!") return True class MyWindow(Window): def __init__(self): super().__init__(640, 480, title="Hello", doublebuffer=True) self.bgc = rgb(255,255,255) self.dejavu = Font("sans-serif", 13) self.mb = MenuBar((("File",'file'),("Help",'help'))) let _menu_File = MenuList() _menu_File.insert(MenuEntry("Test", lambda menu: print("hello, world"))) _menu_File.insert(MenuEntrySeparator()) _menu_File.insert(MenuEntry("Quit", lambda menu: self.close())) self.mb.insert('file', _menu_File) let _menu_Help = MenuList() let _menu_Help_help = MenuEntry("Help",lambda menu: print("oh no!")) _menu_Help.insert(_menu_Help_help) _menu_Help.insert(MyMenuWidget()) _menu_Help.insert(MyMenuWidget()) self.mb.insert('help', _menu_Help) self.mb.callback = lambda x: self.draw() def draw(self): self.fill(self.bgc) decor_render(self) let bounds = decor_get_bounds(self) self.mb.place(bounds['left_width'],bounds['top_height'],self.width-bounds['width'],self) self.mb.render(self) self.dejavu.draw_string(self,"Hello, world.",20,120) self.dejavu.draw_string(self,f"{self.x}, {self.y}",20,140) self.flip() def mouse_event(self, msg): let decResponse = decor_handle_event(msg) if decResponse == 2: print("Close me?") self.close() return True else if decResponse == 5: decor_show_default_menu(self, self.x + msg.new_x, self.y + msg.new_y) self.mb.mouse_event(self, msg) def interp(a,b,p): return a * (1.0 - p) + b * p def interp_color(a,b,p): return rgb(*(int(self.interp(l,r,p)) for l,r in zip(a,b))) async def animate(self): let mySentinel = object() self.animator = mySentinel let start = (255 * random.random(),255 * random.random(),255 * random.random()) let end = (255,255,255) for i in range(31): await sleep(0.033) if self.animator is not mySentinel: break self.bgc = self.interp_color(start,end,i/30) self.draw() def keyboard_event(self, msg): print(msg.keycode) if msg.keycode == 102 and msg.action == 1: Task(self.animate()) def close(self): super().close() mainloop.exit() def window_moved(self, msg): self.draw() def menu_close(self): self.draw() let w = MyWindow() w.move(200,200) w.draw() mainloop.menu_closed_callback = w.menu_close mainloop.run() ================================================ FILE: apps/live-session.c ================================================ /** * @brief live-session - Run live CD user session. * * Launches the general session manager as 'local', waits for the * session to end, then launches the login manager. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #define TRACE_APP_NAME "live-session" int main(int argc, char * argv[]) { int pid; if (geteuid() != 0) { return 1; } int _session_pid = fork(); if (!_session_pid) { toaru_set_credentials(1000); char * args[] = {"/bin/session", NULL}; execvp(args[0], args); return 1; } /* Dummy session for live-session prevents compositor from killing itself * when the main session dies the first time. */ yutani_init(); do { pid = wait(NULL); } while ((pid > 0 && pid != _session_pid) || (pid == -1 && errno == EINTR)); TRACE("Live session has ended, launching graphical login."); int _glogin_pid = fork(); if (!_glogin_pid) { char * args[] = {"/bin/glogin",NULL}; execvp(args[0],args); system("reboot"); } do { pid = wait(NULL); } while ((pid > 0 && pid != _glogin_pid) || (pid == -1 && errno == EINTR)); return 0; } ================================================ FILE: apps/ln.c ================================================ /** * @brief Make symlinks * * @copyright * 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 Mike Gerow * 2018 K. Lange */ #include #include #include #include #include static const char usage[] = "Usage: %s [-s] TARGET NAME\n" " -s: Create a symbolic link.\n" " -h: Print this help message and exit.\n"; extern int link(const char *old, const char *new); int main(int argc, char * argv[]) { int symlink_flag = 0; int c; while ((c = getopt(argc, argv, "sh")) != -1) { switch (c) { case 's': symlink_flag = 1; break; case 'h': fprintf(stdout, usage, argv[0]); exit(EXIT_SUCCESS); default: fprintf(stderr, usage, argv[0]); exit(EXIT_FAILURE); } } if (argc - optind < 2) { fprintf(stderr, usage, argv[0]); exit(EXIT_FAILURE); } char * target = argv[optind]; char * name = argv[optind + 1]; if (symlink_flag) { if(symlink(target, name) < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], name, strerror(errno)); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } if (link(target, name) < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], name, strerror(errno)); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } ================================================ FILE: apps/login-loop.c ================================================ /** * @brief Repeatedly invoke `login` in a loop. * * This is more closely related to the 'getty' command in Linux * than our actual 'getty' command as it is also where we process * and display /etc/issue. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct colorNames { const char * name; const char * output; } ColorNames[] = { {"black", "\033[30m"}, {"blue", "\033[34m"}, {"bold", "\033[1m"}, {"brown", "\033[33m"}, {"cyan", "\033[36"}, {"darkgray", "\033[90m"}, {"gray", "\033[37m"}, {"green", "\033[32m"}, {"lightblue", "\033[94m"}, {"lightcyan", "\033[96m"}, {"lightgray", "\033[97m"}, {"lightgreen", "\033[92m"}, {"lightmagenta", "\033[95m"}, {"lightred", "\033[91m"}, {"magenta", "\033[35m"}, {"red", "\033[31m"}, {"reset", "\033[0m"}, {"reverse", "\033[7m"}, {"yellow", "\033[93m"}, {NULL, NULL}, }; char * get_arg(FILE * f) { static char buf[32]; int n = fgetc(f); if (n == '{') { int count = 0; do { int x = fgetc(f); if (x == '}') break; if (x < 0) break; buf[count++] = x; buf[count] = '\0'; } while (count < 31); if (count) return buf; } return NULL; } char * get_ipv4_address(char * arg) { if (arg) { char if_path[1024]; snprintf(if_path, 300, "/dev/net/%s", arg); int netdev = open(if_path, O_RDWR); if (netdev >= 0) { uint32_t ip_addr = 0; if (!ioctl(netdev, SIOCGIFADDR, &ip_addr)) { return inet_ntoa((struct in_addr){ntohl(ip_addr)}); } } } else { /* Read /dev/net for interfaces */ DIR * d = opendir("/dev/net"); if (d) { struct dirent * ent; while ((ent = readdir(d))) { if (ent->d_name[0] == '.') continue; closedir(d); return get_ipv4_address(ent->d_name); } closedir(d); } } return "127.0.0.1"; } void print_issue(void) { printf("\033[H\033[2J\n"); FILE * f = fopen("/etc/issue","r"); if (!f) return; /* Parse and display /etc/issue with support * for some escape sequences that fill in * dynamic information... */ struct utsname u; uname(&u); struct tm * timeinfo; struct timeval now; gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); static char buf[1024]; while (!feof(f)) { int c = fgetc(f); if (c < 0) break; /* Probably EOF */ if (c == '\\') { int next = fgetc(f); switch (next) { case '\n': /* A linefeed we should quietly skip. */ continue; case '\\': /* A literal backslash */ printf("\\"); continue; /* Various things from uname */ case 'n': printf("%s", u.nodename); continue; case 's': printf("%s", u.sysname); continue; case 'r': printf("%s", u.release); continue; case 'm': printf("%s", u.machine); continue; case 'v': printf("%s", u.version); continue; /* Various other useful things */ case '4': printf("%s", get_ipv4_address(get_arg(f))); continue; case 'l': printf("%s", ttyname(STDIN_FILENO)); continue; case 't': strftime(buf,1024,"%T %Z",timeinfo); printf("%s", buf); continue; case 'd': strftime(buf,1024,"%a %b %d %Y",timeinfo); printf("%s", buf); continue; /* Formatting stuff listed in Linux's getty(8) manpage */ case 'e': { char * arg = get_arg(f); if (arg) { for (struct colorNames * cn = ColorNames; cn->name; cn++) { if (!strcmp(arg,cn->name)) { printf("%s", cn->output); break; } } } else { printf("\033"); } continue; } } } else { printf("%c", c); } } fclose(f); } int main(int argc, char * argv[]) { while (1) { print_issue(); pid_t f = fork(); if (!f) { char * args[] = { "login", NULL }; execvp(args[0], args); } else { int result, status; do { result = waitpid(f, &status, 0); } while (result < 0); if (WEXITSTATUS(status) == 2) break; } } return 1; } ================================================ FILE: apps/login.c ================================================ /** * @brief TTY login prompt * * Provides the user with a login prompt and starts their session. * * @copyright * 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-2014 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LINE_LEN 1024 uint32_t child = 0; void sig_pass(int sig) { /* Pass onto the shell */ if (child) { kill(child, sig); } /* Else, ignore */ } void sig_segv(int sig) { printf("Segmentation fault.\n"); exit(127 + sig); /* no return */ } int main(int argc, char ** argv) { char * user = NULL; int uid; pid_t pid, f; int opt; while ((opt = getopt(argc, argv, "f:")) != -1) { switch (opt) { case 'f': user = optarg; break; } } if (user) { struct passwd * pw = getpwnam(user); if (pw) { uid = pw->pw_uid; goto do_fork; } else { fprintf(stderr, "%s: no such user\n", argv[0]); return 1; } } signal(SIGINT, sig_pass); signal(SIGWINCH, sig_pass); signal(SIGSEGV, sig_segv); while (1) { char username[1024] = {0}; char password[1024] = {0}; /* TODO: gethostname() */ char _hostname[256]; gethostname(_hostname, 255); fprintf(stdout, "%s login: ", _hostname); fflush(stdout); char * r = fgets(username, 1024, stdin); if (!r) { clearerr(stdin); fprintf(stderr, "\n"); sleep(2); fprintf(stderr, "\nLogin failed.\n"); continue; } username[strlen(username)-1] = '\0'; if (!strcmp(username, "reboot")) { /* Quick hack so vga text mode login can exit */ system("reboot"); } if (!strcmp(username, "disconnect")) { return 2; } fprintf(stdout, "password: "); fflush(stdout); /* Disable echo */ struct termios old, new; tcgetattr(fileno(stdin), &old); new = old; new.c_lflag &= (~ECHO); tcsetattr(fileno(stdin), TCSAFLUSH, &new); r = fgets(password, 1024, stdin); if (!r) { clearerr(stdin); tcsetattr(fileno(stdin), TCSAFLUSH, &old); fprintf(stderr, "\n"); sleep(2); fprintf(stderr, "\nLogin failed.\n"); continue; } password[strlen(password)-1] = '\0'; tcsetattr(fileno(stdin), TCSAFLUSH, &old); fprintf(stdout, "\n"); uid = toaru_auth_check_pass(username, password); if (uid < 0) { sleep(2); fprintf(stdout, "\nLogin failed.\n"); continue; } break; } system("cat /etc/motd"); do_fork: pid = getpid(); f = fork(); if (getpid() != pid) { ioctl(STDIN_FILENO, IOCTLTTYLOGIN, &uid); setsid(); ioctl(STDIN_FILENO, TIOCSCTTY, &(int){1}); tcsetpgrp(STDIN_FILENO, getpid()); toaru_set_credentials(uid); char * args[] = { getenv("SHELL"), NULL }; execvp(args[0], args); return 1; } else { child = f; int result; do { result = waitpid(f, NULL, 0); } while (result < 0); } child = 0; return 0; } ================================================ FILE: apps/logname.c ================================================ /** * @brief Display the user's name, as returned by getlogin() * * @copyright * 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 */ #include #include int main(int argc, char ** argv) { char * name = getlogin(); if (!name) { fprintf(stderr, "%s: failed to determine login name\n", argv[0]); return 1; } fprintf(stdout, "%s\n", name); return 0; } ================================================ FILE: apps/ls.c ================================================ /** * @brief List files * * Lists files in a directory, with nice color * output like any modern ls should have. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TRACE_APP_NAME "ls" //#include "lib/trace.h" #define TRACE(...) #include #define MIN_COL_SPACING 2 #define EXE_COLOR "1;32" #define DIR_COLOR "1;34" #define SYMLINK_COLOR "1;36" #define REG_COLOR "0" #define MEDIA_COLOR "" #define SYM_COLOR "" #define BROKEN_COLOR "1;" #define DEVICE_COLOR "1;33;40" #define SETUID_COLOR "37;41" #define DEFAULT_TERM_WIDTH 0 #define DEFAULT_TERM_HEIGHT 0 #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define LINE_LEN 4096 static int human_readable = 0; static int stdout_is_tty = 1; static int this_year = 0; static int show_hidden = 0; static int long_mode = 0; static int print_dir = 0; static int term_width = DEFAULT_TERM_WIDTH; static int term_height = DEFAULT_TERM_HEIGHT; struct tfile { char * name; struct stat statbuf; char * link; struct stat statbufl; }; static const char * color_str(struct stat * sb) { if (S_ISDIR(sb->st_mode)) { /* Directory */ return DIR_COLOR; } else if (S_ISLNK(sb->st_mode)) { /* Symbolic Link */ return SYMLINK_COLOR; } else if (sb->st_mode & S_ISUID) { /* setuid - sudo, etc. */ return SETUID_COLOR; } else if (sb->st_mode & 0111) { /* Executable */ return EXE_COLOR; } else if (S_ISBLK(sb->st_mode) || S_ISCHR(sb->st_mode) || S_ISFIFO(sb->st_mode)) { /* Device file */ return DEVICE_COLOR; } else { /* Regular file */ return REG_COLOR; } } static int filecmp(const void * c1, const void * c2) { const struct tfile * d1 = *(const struct tfile **)c1; const struct tfile * d2 = *(const struct tfile **)c2; int a = S_ISDIR(d1->statbuf.st_mode); int b = S_ISDIR(d2->statbuf.st_mode); if (a == b) return strcmp(d1->name, d2->name); else if (a < b) return -1; else if (a > b) return 1; return 0; /* impossible ? */ } static int filecmp_notypesort(const void * c1, const void * c2) { const struct tfile * d1 = *(const struct tfile **)c1; const struct tfile * d2 = *(const struct tfile **)c2; return strcmp(d1->name, d2->name); } static void print_entry(struct tfile * file, int colwidth) { const char * ansi_color_str = color_str(&file->statbuf); /* Print the file name */ if (stdout_is_tty) { printf("\033[%sm%s\033[0m", ansi_color_str, file->name); } else { printf("%s", file->name); } /* Pad the rest of the column */ for (int rem = colwidth - strlen(file->name); rem > 0; rem--) { printf(" "); } } static int print_username(char * _out, int uid) { TRACE("getpwuid"); struct passwd * p = getpwuid(uid); int out = 0; if (p) { TRACE("p is set"); out = sprintf(_out, "%s", p->pw_name); } else { TRACE("p is not set"); out = sprintf(_out, "%d", uid); } endpwent(); return out; } static int print_human_readable_size(char * _out, size_t s) { if (s >= 1<<20) { size_t t = s / (1 << 20); return sprintf(_out, "%d.%1dM", (int)t, (int)(s - t * (1 << 20)) / ((1 << 20) / 10)); } else if (s >= 1<<10) { size_t t = s / (1 << 10); return sprintf(_out, "%d.%1dK", (int)t, (int)(s - t * (1 << 10)) / ((1 << 10) / 10)); } else { return sprintf(_out, "%d", (int)s); } } static void update_column_widths(int * widths, struct tfile * file) { char tmp[256]; int n; /* Links */ TRACE("links"); n = sprintf(tmp, "%d", file->statbuf.st_nlink); if (n > widths[0]) widths[0] = n; /* User */ TRACE("user"); n = print_username(tmp, file->statbuf.st_uid); if (n > widths[1]) widths[1] = n; /* Group */ TRACE("group"); n = print_username(tmp, file->statbuf.st_gid); if (n > widths[2]) widths[2] = n; /* File size */ TRACE("file size"); if (human_readable) { n = print_human_readable_size(tmp, file->statbuf.st_size); } else { n = sprintf(tmp, "%d", (int)file->statbuf.st_size); } if (n > widths[3]) widths[3] = n; } static void print_entry_long(int * widths, struct tfile * file) { const char * ansi_color_str = color_str(&file->statbuf); /* file permissions */ if (S_ISLNK(file->statbuf.st_mode)) { printf("l"); } else if (S_ISCHR(file->statbuf.st_mode)) { printf("c"); } else if (S_ISBLK(file->statbuf.st_mode)) { printf("b"); } else if (S_ISDIR(file->statbuf.st_mode)) { printf("d"); } else { printf("-"); } printf( (file->statbuf.st_mode & S_IRUSR) ? "r" : "-"); printf( (file->statbuf.st_mode & S_IWUSR) ? "w" : "-"); if (file->statbuf.st_mode & S_ISUID) { printf("s"); } else { printf( (file->statbuf.st_mode & S_IXUSR) ? "x" : "-"); } printf( (file->statbuf.st_mode & S_IRGRP) ? "r" : "-"); printf( (file->statbuf.st_mode & S_IWGRP) ? "w" : "-"); printf( (file->statbuf.st_mode & S_IXGRP) ? "x" : "-"); printf( (file->statbuf.st_mode & S_IROTH) ? "r" : "-"); printf( (file->statbuf.st_mode & S_IWOTH) ? "w" : "-"); printf( (file->statbuf.st_mode & S_IXOTH) ? "x" : "-"); printf( " %*d ", widths[0], file->statbuf.st_nlink); /* number of links, not supported */ char tmp[100]; print_username(tmp, file->statbuf.st_uid); printf("%-*s ", widths[1], tmp); print_username(tmp, file->statbuf.st_gid); printf("%-*s ", widths[2], tmp); if (human_readable) { print_human_readable_size(tmp, file->statbuf.st_size); printf("%*s ", widths[3], tmp); } else { printf("%*d ", widths[3], (int)file->statbuf.st_size); } char time_buf[80]; struct tm * timeinfo = localtime((time_t*)&file->statbuf.st_mtime); if (timeinfo->tm_year == this_year) { strftime(time_buf, 80, "%b %d %H:%M", timeinfo); } else { strftime(time_buf, 80, "%b %d %Y", timeinfo); } printf("%s ", time_buf); /* Print the file name */ if (stdout_is_tty) { printf("\033[%sm%s\033[0m", ansi_color_str, file->name); if (S_ISLNK(file->statbuf.st_mode)) { const char * s = color_str(&file->statbufl); printf(" -> \033[%sm%s\033[0m", s, file->link); } } else { printf("%s", file->name); if (S_ISLNK(file->statbuf.st_mode)) { printf(" -> %s", file->link); } } printf("\n"); } static void show_usage(int argc, char * argv[]) { printf( "ls - list files\n" "\n" "usage: %s [-lha] [path]\n" "\n" " -a \033[3mlist all files (including . files)\033[0m\n" " -l \033[3muse a long listing format\033[0m\n" " -h \033[3mhuman-readable file sizes\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); } static void display_tfiles(struct tfile ** ents_array, int numents) { if (long_mode) { TRACE("long mode display, column lengths"); int widths[4] = {0,0,0,0}; for (int i = 0; i < numents; i++) { update_column_widths(widths, ents_array[i]); } TRACE("actual printing"); for (int i = 0; i < numents; i++) { print_entry_long(widths, ents_array[i]); } } else { /* Determine the gridding dimensions */ int ent_max_len = 0; for (int i = 0; i < numents; i++) { ent_max_len = MAX(ent_max_len, (int)strlen(ents_array[i]->name)); } int col_ext = ent_max_len + MIN_COL_SPACING; int cols = ((term_width - ent_max_len) / col_ext) + 1; /* Print the entries */ for (int i = 0; i < numents;) { /* Columns */ print_entry(ents_array[i++], ent_max_len); for (int j = 0; (i < numents) && (j < (cols-1)); j++) { printf(" "); print_entry(ents_array[i++], ent_max_len); } printf("\n"); } } } static int display_dir(char * p) { /* Open the directory */ DIR * dirp = opendir(p); if (dirp == NULL) { return 2; } if (print_dir) { printf("%s:\n", p); } /* Read the entries in the directory */ list_t * ents_list = list_create(); TRACE("reading entries"); struct dirent * ent = readdir(dirp); while (ent != NULL) { if (show_hidden || (ent->d_name[0] != '.')) { struct tfile * f = malloc(sizeof(struct tfile)); f->name = strdup(ent->d_name); char tmp[strlen(p)+strlen(ent->d_name)+2]; sprintf(tmp, "%s/%s", p, ent->d_name); lstat(tmp, &f->statbuf); if (S_ISLNK(f->statbuf.st_mode)) { stat(tmp, &f->statbufl); f->link = malloc(4096); readlink(tmp, f->link, 4096); } list_insert(ents_list, (void *)f); } ent = readdir(dirp); } closedir(dirp); TRACE("copying"); /* Now, copy those entries into an array (for sorting) */ if (!ents_list->length) return 0; struct tfile ** file_arr = malloc(sizeof(struct tfile *) * ents_list->length); int index = 0; foreach(node, ents_list) { file_arr[index++] = (struct tfile *)node->value; } list_free(ents_list); TRACE("sorting"); qsort(file_arr, index, sizeof(struct tfile *), filecmp_notypesort); TRACE("displaying"); display_tfiles(file_arr, index); free(file_arr); return 0; } int main (int argc, char * argv[]) { /* Parse arguments */ char * p = "."; if (argc > 1) { int c; while ((c = getopt(argc, argv, "ahl?")) != -1) { switch (c) { case 'a': show_hidden = 1; break; case 'h': human_readable = 1; break; case 'l': long_mode = 1; break; case '?': show_usage(argc, argv); return 0; } } if (optind < argc) { p = argv[optind]; } if (optind + 1 < argc) { print_dir = 1; } } stdout_is_tty = isatty(STDOUT_FILENO); if (long_mode) { struct tm * timeinfo; struct timeval now; gettimeofday(&now, NULL); //time(NULL); timeinfo = localtime((time_t *)&now.tv_sec); this_year = timeinfo->tm_year; } if (stdout_is_tty) { TRACE("getting display size"); struct winsize w; ioctl(1, TIOCGWINSZ, &w); term_width = w.ws_col; term_height = w.ws_row; term_width -= 1; /* And this just helps clean up our math */ } int out = 0; if (argc == 1 || optind == argc) { TRACE("no file to look up"); if (display_dir(p) == 2) { fprintf(stderr, "%s: %s: %s\n", argv[0], p, strerror(errno)); } } else { list_t * files = list_create(); while (p) { struct tfile * f = malloc(sizeof(struct tfile)); f->name = p; int t = lstat(p, &f->statbuf); if (t < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], p, strerror(errno)); free(f); out = 2; } else { if (S_ISLNK(f->statbuf.st_mode)) { stat(p, &f->statbufl); f->link = malloc(4096); readlink(p, f->link, 4096); } list_insert(files, f); } optind++; if (optind >= argc) p = NULL; else p = argv[optind]; } if (!files->length) { /* No valid entries */ return out; } struct tfile ** file_arr = malloc(sizeof(struct tfile *) * files->length); int index = 0; foreach(node, files) { file_arr[index++] = (struct tfile *)node->value; } list_free(files); qsort(file_arr, index, sizeof(struct tfile *), filecmp); int first_directory = index; for (int i = 0; i < index; ++i) { if (S_ISDIR(file_arr[i]->statbuf.st_mode)) { first_directory = i; break; } } if (first_directory) { display_tfiles(file_arr, first_directory); } for (int i = first_directory; i < index; ++i) { if (i != 0) { printf("\n"); } if (display_dir(file_arr[i]->name) == 2) { fprintf(stderr, "%s: %s: %s\n", argv[0], file_arr[i]->name, strerror(errno)); } } } return out; } ================================================ FILE: apps/lspci.c ================================================ /** * lspci - Print information about connected PCI devices. * * @copyright * 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 */ #include #include #include #include #include #include struct device_class { uint16_t id; char * name; } _pci_classes[] = { {0x0101, "IDE interface"}, {0x0102, "Floppy disk controller"}, {0x0105, "ATA controller"}, {0x0106, "SATA controller"}, {0x0200, "Ethernet controller"}, {0x0280, "Network controller"}, {0x0300, "VGA compatible controller"}, {0x0380, "Display controller"}, {0x0401, "Multimedia audio controller"}, {0x0403, "Audio device"}, {0x0480, "Multimedia controller"}, {0x0600, "Host bridge"}, {0x0601, "ISA bridge"}, {0x0604, "PCI bridge"}, {0x0680, "Bridge"}, {0x0780, "Communication controller"}, {0x0805, "SD Host controller"}, {0x0880, "System peripheral"}, {0x0900, "Keyboard"}, {0x0980, "Input Device"}, {0x0c00, "FireWire controller"}, {0x0c03, "USB controller"}, {0x0c05, "SMBus controller"}, {0x1180, "Signal processing controller"}, }; struct { uint16_t id; const char * name; } _pci_vendors[] = { {0x1013, "Cirrus Logic"}, {0x1022, "AMD"}, {0x106b, "Apple, Inc."}, {0x10de, "NVIDIA Corp."}, {0x1180, "Ricoh Ct. Ltd."}, {0x1234, "Bochs/QEMU"}, {0x1274, "Ensoniq"}, {0x15ad, "VMWare"}, {0x1912, "Renesas Electronics Corp."}, /* Formerly "Renesas Technology Corp." */ {0x1af4, "Red Hat, Inc."}, /* virtio */ {0x1b36, "Red Hat, Inc."}, {0x8086, "Intel Corporation"}, {0x80EE, "VirtualBox"}, }; struct { uint16_t ven_id; uint16_t dev_id; const char * name; } _pci_devices[] = { {0x1013, 0x00b8, "CLGD 54xx VGA Adapter"}, {0x1022, 0x2000, "PCNet Ethernet Controller (pcnet)"}, {0x106b, 0x003f, "OHCI Controller"}, {0x10de, 0x0a6c, "Quadro NVS 3100M"}, /* Ricoh */ {0x1180, 0xe822, "MMC/SD Host Controller"}, {0x1180, 0xe230, "R5U2xx Memory Stick Host Controller"}, {0x1180, 0xe832, "R5C832 PCIe IEEE 1394 Controller"}, {0x1234, 0x1111, "VGA BIOS Graphics Extensions"}, {0x1274, 0x1371, "Creative Labs CT2518 (ensoniq audio)"}, /* VMWare */ {0x15ad, 0x0740, "VM Communication Interface"}, {0x15ad, 0x0405, "SVGA II Adapter"}, {0x15ad, 0x0790, "PCI bridge"}, {0x15ad, 0x07a0, "PCI Express Root Port"}, /* Renesas */ {0x1912, 0x0015, "uPD720202 USB 3.0 Host Controller"}, /* Red Hat */ {0x1af4, 0x1000, "Virtio Network Device"}, {0x1af4, 0x1052, "Virtio Input Device"}, {0x1b36, 0x0008, "QEMU PCIe Host Bridge"}, {0x1b36, 0x000d, "QEMU XHCI Host Controller"}, /* Intel */ {0x8086, 0x0044, "DRAM Controller"}, {0x8086, 0x0045, "PCI Express x16 Root Port"}, {0x8086, 0x0046, "Gen 5 HD Graphics"}, {0x8086, 0x1004, "82543GC Gigabit Ethernet Controller (e1000)"}, {0x8086, 0x100e, "82540EM Gigabit Ethernet Controller (e1000)"}, {0x8086, 0x100f, "82545EM Gigabit Ethernet Controller (e1000)"}, {0x8086, 0x10d3, "82574L Gigabit Ethernet Controller (e1000e)"}, {0x8086, 0x10ea, "82577LM Gigabit Ethernet Controller (e1000)"}, {0x8086, 0x1237, "PCI & Memory"}, {0x8086, 0x2415, "82801AA AC'97 Audio Controller"}, {0x8086, 0x2448, "82801 Mobile PCI Bridge"}, {0x8086, 0x2668, "ICH6 HD Audio Controller"}, {0x8086, 0x29c0, "DRAM Controller"}, {0x8086, 0x2918, "ICH9 LPC Interface Controller"}, {0x8086, 0x2922, "ICH9 6-port SATA Controller"}, {0x8086, 0x2930, "ICH9 SMBus Controller"}, {0x8086, 0x3b07, "QM57 Chipset LPC Interface Controller"}, {0x8086, 0x3b2f, "ICH10 6-port SATA AHCI Controller"}, {0x8086, 0x3b30, "ICH10 SMBus Controller"}, {0x8086, 0x3b32, "ICH10 Thermal Subsystem"}, {0x8086, 0x3b34, "ICH10 USB 2.0 Enhanced Host Controller"}, {0x8086, 0x3b3c, "ICH10 USB 2.0 Enhanced Host Controller"}, {0x8086, 0x3b42, "ICH10 PCI Express Root Port 1"}, {0x8086, 0x3b44, "ICH10 PCI Express Root Port 2"}, {0x8086, 0x3b46, "ICH10 PCI Express Root Port 3"}, {0x8086, 0x3b48, "ICH10 PCI Express Root Port 4"}, {0x8086, 0x3b4a, "ICH10 PCI Express Root Port 5"}, {0x8086, 0x3b4c, "ICH10 PCI Express Root Port 6"}, {0x8086, 0x3b4e, "ICH10 PCI Express Root Port 7"}, {0x8086, 0x3b50, "ICH10 PCI Express Root Port 8"}, {0x8086, 0x3b56, "ICH10 HD Audio Controller"}, {0x8086, 0x3b64, "ICH10 HECI Controller"}, {0x8086, 0x422b, "Centrino Ultimate-N 6300"}, {0x8086, 0x7000, "PCI-to-ISA Bridge"}, {0x8086, 0x7010, "IDE Interface"}, {0x8086, 0x7110, "PIIX4 ISA"}, {0x8086, 0x7111, "PIIX4 IDE"}, {0x8086, 0x7113, "Power Management Controller"}, {0x8086, 0x7190, "Host Bridge"}, {0x8086, 0x7191, "AGP Bridge"}, /* VirtualBox */ {0x80EE, 0xBEEF, "Bochs/QEMU-compatible Graphics Adapter"}, {0x80EE, 0xCAFE, "Guest Additions Device"}, }; const char * pci_class_lookup(unsigned short class_id) { for (unsigned int i = 0; i < sizeof(_pci_classes)/sizeof(_pci_classes[0]); ++i) { if (_pci_classes[i].id == class_id) { return _pci_classes[i].name; } } return "(unknown)"; } const char * pci_vendor_lookup(unsigned short vendor_id) { for (unsigned int i = 0; i < sizeof(_pci_vendors)/sizeof(_pci_vendors[0]); ++i) { if (_pci_vendors[i].id == vendor_id) { return _pci_vendors[i].name; } } return NULL; } const char * pci_device_lookup(unsigned short vendor_id, unsigned short device_id) { for (unsigned int i = 0; i < sizeof(_pci_devices)/sizeof(_pci_devices[0]); ++i) { if (_pci_devices[i].ven_id == vendor_id && _pci_devices[i].dev_id == device_id) { return _pci_devices[i].name; } } return NULL; } static void show_usage(char * argv[]) { fprintf(stderr, "lspci - show information about PCI devices\n" "\n" "usage: %s [-n]\n" "\n" " -n \033[3mshow numeric device codes\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); } int main(int argc, char * argv[]) { int numeric = 0; int opt; char * query = NULL; while ((opt = getopt(argc, argv, "nq:?")) != -1) { switch (opt) { case '?': show_usage(argv); return 0; case 'n': numeric = 1; break; case 'q': query = optarg; break; } } FILE * f = fopen("/proc/pci","r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], "/proc/pci", strerror(errno)); return 1; } while (!feof(f)) { char line[1024]; fgets(line, 1024, f); if (line[0] == ' ' || line[0] == '\n') { /* Skip; don't care about this information */ continue; } /* Read bus, etc. verbatim */ char * device_bus = line; /* Read device class */ char * device_class = strstr(line," ("); if (!device_class) { fprintf(stderr, "%s: parse error - expected (\n", argv[0]); return 1; } *device_class = '\0'; device_class++; /* space */ device_class++; /* ( */ char * device_vendor = strstr(device_class, ", "); if (!device_vendor) { fprintf(stderr, "%s: parse error - expected ,\n", argv[0]); return 1; } *device_vendor = '\0'; device_vendor++; /* comma */ device_vendor++; /* space */ char * device_code = strstr(device_vendor, ":"); if (!device_code) { fprintf(stderr, "%s: parse error - expected :\n", argv[0]); return 1; } *device_code = '\0'; device_code++; /* colon */ char * last_paren = strstr(device_code, ")"); if (!last_paren) { fprintf(stderr, "%s: parse error - expected )\n", argv[0]); return 1; } *last_paren = '\0'; if (query) { unsigned short vendor_id = strtoul(device_vendor, NULL, 16); unsigned short device_id = strtoul(device_code, NULL, 16); char * start = query; while (start) { char * colon = strchr(start, ':'); if (!colon) return 2; /* Invalid query string */ *colon = '\0'; char * comma = strchr(colon+1, ','); if (comma) *comma = '\0'; unsigned long query_man = strtoul(start,NULL,16); unsigned long query_dev = strtoul(colon+1,NULL,16); if (query_man == vendor_id && query_dev == device_id) return 0; *colon = ':'; if (comma) { *comma = ','; start = comma + 1; } else { start = NULL; } } } else if (numeric) { fprintf(stdout, "%s %s: %s:%s\n", device_bus, device_class, device_vendor, device_code); } else { unsigned short class_id = strtoul(device_class, NULL, 16); unsigned short vendor_id = strtoul(device_vendor, NULL, 16); unsigned short device_id = strtoul(device_code, NULL, 16); const char * class_name = pci_class_lookup(class_id); const char * vendor_name = pci_vendor_lookup(vendor_id); const char * device_name = pci_device_lookup(vendor_id, device_id); if (!vendor_name && !device_name) { fprintf(stdout, "%s %s: %s:%s\n", device_bus, class_name, device_vendor, device_code); } else if (!vendor_name) { fprintf(stdout, "%s %s: %s %s\n", device_bus, class_name, device_vendor, device_name); } else if (!device_name) { fprintf(stdout, "%s %s: %s %s\n", device_bus, class_name, vendor_name, device_code); } else { fprintf(stdout, "%s %s: %s %s\n", device_bus, class_name, vendor_name, device_name); } } } if (query) return 1; return 0; } ================================================ FILE: apps/maybe-pdfviewer.krk ================================================ #!/bin/kuroko import os import kuroko try: os.stat('/usr/bin/pdfviewer') os.execl('/usr/bin/pdfviewer','pdfviewer',kuroko.argv[1]) except OSError: 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: os.system('terminal gsudo sh -c "msk update; msk install mupdf"') os.execl('/usr/bin/pdfviewer','pdfviewer',kuroko.argv[1]) ================================================ FILE: apps/migrate.c ================================================ /** * @brief migrate - Relocate root filesystem to tmpfs * * Run as part of system startup to move the ext2 root ramdisk * into a flexible in-memory temporary filesystem, which allows * file creation and editing and is much faster than the using * the ext2 driver against the static in-memory ramdisk. * * Based on the original Python implementation. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include #define TRACE_APP_NAME "migrate" #define TRACE_(...) do { \ if (_splash) { char _trace_tmp[512]; sprintf(_trace_tmp, __VA_ARGS__); fprintf(_splash, ":%s", _trace_tmp); fflush(_splash); } \ if (_debug) { TRACE(__VA_ARGS__); } \ } while (0) #define CHUNK_SIZE 4096 static int _debug = 0; static FILE * _splash = NULL; void copy_link(char * source, char * dest, int mode, int uid, int gid) { //fprintf(stderr, "need to copy link %s to %s\n", source, dest); char tmp[1024]; readlink(source, tmp, 1024); symlink(tmp, dest); //chmod(dest, mode); /* links don't have modes */ chown(dest, uid, gid); } void copy_file(char * source, char * dest, int mode,int uid, int gid) { //fprintf(stderr, "need to copy file %s to %s %x\n", source, dest, mode); //TRACE_("Copying %s...", dest); int d_fd = open(dest, O_WRONLY | O_CREAT, mode); int s_fd = open(source, O_RDONLY); ssize_t length; length = lseek(s_fd, 0, SEEK_END); lseek(s_fd, 0, SEEK_SET); //fprintf(stderr, "%d bytes to copy\n", length); char buf[CHUNK_SIZE]; while (length > 0) { size_t r = read(s_fd, buf, length < CHUNK_SIZE ? length : CHUNK_SIZE); //fprintf(stderr, "copying %d bytes from %s to %s\n", r, source, dest); write(d_fd, buf, r); length -= r; //fprintf(stderr, "%d bytes remaining\n", length); } close(s_fd); close(d_fd); chown(dest, uid, gid); chmod(dest, mode); } void copy_directory(char * source, char * dest, int mode, int uid, int gid) { DIR * dirp = opendir(source); if (dirp == NULL) { fprintf(stderr, "Failed to copy directory %s\n", source); return; } TRACE_("Copying %s/...", dest); //fprintf(stderr, "Creating %s\n", dest); if (!strcmp(dest, "/")) { dest = ""; } else { mkdir(dest, mode); } struct dirent * ent = readdir(dirp); while (ent != NULL) { if (!strcmp(ent->d_name,".") || !strcmp(ent->d_name,"..")) { //fprintf(stderr, "Skipping %s\n", ent->d_name); ent = readdir(dirp); continue; } //fprintf(stderr, "not skipping %s/%s → %s/%s\n", source, ent->d_name, dest, ent->d_name); struct stat statbuf; char tmp[strlen(source)+strlen(ent->d_name)+2]; sprintf(tmp, "%s/%s", source, ent->d_name); char tmp2[strlen(dest)+strlen(ent->d_name)+2]; sprintf(tmp2, "%s/%s", dest, ent->d_name); //fprintf(stderr,"%s → %s\n", tmp, tmp2); lstat(tmp,&statbuf); if (S_ISLNK(statbuf.st_mode)) { copy_link(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid); } else if (S_ISDIR(statbuf.st_mode)) { copy_directory(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid); } else if (S_ISREG(statbuf.st_mode)) { copy_file(tmp, tmp2, statbuf.st_mode & 07777, statbuf.st_uid, statbuf.st_gid); } else { fprintf(stderr, " %s is not any of the required file types?\n", tmp); } ent = readdir(dirp); } closedir(dirp); chown(dest, uid, gid); } void free_ramdisk(char * path) { int fd = open(path, O_RDONLY); ioctl(fd, 0x4001, NULL); close(fd); } #include "../kernel/misc/args.c" static hashmap_t * get_cmdline(void) { char * results = args_from_procfs(); if (results) free(results); return kernel_args_map; } static int root_is_tmpfs(void) { FILE *f = fopen("/proc/mounts", "r"); if (!f) return 0; char *line = NULL; size_t len = 0; int found = 0; while (getline(&line, &len, f) != -1) { if (strstr(line, "/ tmpfs ") == line) { found = 1; break; } } free(line); fclose(f); return found; } int main(int argc, char * argv[]) { hashmap_t * cmdline = get_cmdline(); if (root_is_tmpfs()) { fprintf(stderr, "You have already migrated the filesystem.\n"); return 1; } if (hashmap_has(cmdline, "logtoserial")) { _debug = 1; } _splash = fopen("/dev/pex/splash","r+"); if (hashmap_has(cmdline, "root")) { TRACE_("Original root was %s", (char*)hashmap_get(cmdline, "root")); } else if (hashmap_has(cmdline,"init") && !strcmp(hashmap_get(cmdline,"init"),"/dev/ram0")) { TRACE_("Init is ram0, so this is probably a netboot image, going to assume root is /tmp/netboot.img"); hashmap_set(cmdline,"root","/tmp/netboot.img"); } else { TRACE_("Fatal: Don't know how to boot this. No root set.\n"); return 1; } char * root = hashmap_get(cmdline,"root"); char * start = hashmap_get(cmdline,"_start"); if (!start) { start = ""; } char * root_type = hashmap_get(cmdline,"root_type"); if (!root_type) { root_type = "tar"; } char tmp[1024]; TRACE_("Remounting root to /dev/base"); sprintf(tmp, "mount %s %s /dev/base", root_type, root); system(tmp); TRACE_("Mounting tmpfs to /"); system("mount tmpfs x,755 /"); TRACE_("Migrating root..."); copy_directory("/dev/base","/",0660,0,0); system("mount tmpfs x,755 /dev/base"); if (strstr(root, "/dev/ram") != NULL) { char * tmp = strdup(root); char * c = strchr(tmp, ','); if (c) { *c = '\0'; } TRACE_("Freeing ramdisk at %s", tmp); free_ramdisk(tmp); free(tmp); } return 0; } ================================================ FILE: apps/mines.krk ================================================ #!/bin/kuroko ''' Minesweeper game Originally written in Python and ported to Kuroko. Visual design is based on the Gnome "Mines". ''' import math import random from _yutani2 import (Font, rgb, decor_get_bounds, decor_render, decor_handle_event, decor_show_default_menu, MenuBar, MenuEntry, MenuEntrySeparator, MenuList, MenuEntrySubmenu) from yutani_mainloop import Window, AsyncMainloop let app_version = '2.0.0' let mainloop = AsyncMainloop() def frgb(r,g,b): '''RGB from float triplet''' return rgb(int(255 * r),int(255 * g), int(255 * b)) def hsv_to_rgb(h,s,v): '''HSV (radians and floats) to RGB (255/255/255) conversion.''' let c = v * s let hp = math.fmod(h, 2 * math.pi) let x = c * (1.0 - abs(math.fmod(hp / 1.0472, 2) - 1.0)) let m = v - c let rp, gp, bp if hp <= 1.0472: rp = c; gp = x; bp = 0 else if hp <= 2.0944: rp = x; gp = c; bp = 0 else if hp <= 3.1416: rp = 0; gp = c; bp = x else if hp <= 4.1888: rp = 0; gp = x; bp = c else if hp <= 5.2360: rp = x; gp = 0; bp = c else: rp = c; gp = 0; bp = x return rgb(int((rp + m) * 255), int((gp + m) * 255), int((bp + m) * 255)) def good_color(cnt): '''Use neighbor count to pick a good color.''' if cnt == 0: return frgb(0.6,0.6,0.6) let x = cnt / 8 let h = 1.95 - x * 3.145 return hsv_to_rgb(h,0.4,0.7) class MineButton: def __init__(self, action, r, c, is_mine, neighbor_mines): self.row, self.col = r, c self.is_mine = is_mine self.font = Font("sans-serif",13) self.width = None self.revealed = False self.mines = neighbor_mines self.flagged = False self.hilight = 0 self.action = action self.text = "" def focus_enter(self): self.hilight = 1 def focus_leave(self): self.hilight = 0 def reveal(self): if self.revealed: return self.revealed = True if self.is_mine: self.text = "✸" # U+2738 else if self.mines == 0: self.text = "" else: self.text = str(self.mines) def set_flagged(self): self.flagged = not self.flagged def draw(self, window, x, y, w, h): self.font.size = int(h * 0.75) if self.width != w: self.x, self.y, self.width, self.height = x, y, w, h let color = rgb(255,255,255) if self.revealed and self.is_mine: color = frgb(0.4,0.4,0.4) else if self.revealed: color = good_color(self.mines) else if self.hilight == 1: color = frgb(0.7,0.7,0.7) else if self.hilight == 2: color = frgb(0.3,0.3,0.3) window.rect(x+1, y+1, w-2, h-2, color, radius=3) let text = self.text if not self.revealed and self.flagged: text = '⚑' # U+2691 if text: self.font.draw_string(window,text,x+self.width//2-self.font.width(text)//2,int(y+self.height * 0.75)) def randrange(size): return int(size * random.random()) class MinesWindow(Window): base_width = 400 base_height = 440 def __init__(self): let bounds = decor_get_bounds() super().__init__(self.base_width + bounds['width'], self.base_height + bounds['height'], title="Mines", icon="mines", doublebuffer=True) self.move(100,100) self.button_width = {} self.button_height = 0 self.subtitleText = 'Hello, world.' self.subtitleFont = Font('sans-serif.bold',17) self.mb = MenuBar((("Game",'game'),("Help",'help'))) let _menu_Game = MenuList() _menu_Game.insert(MenuEntrySubmenu('New Game', icon='new', action='new-game')) _menu_Game.insert(MenuEntrySeparator()) _menu_Game.insert(MenuEntry('Exit', lambda menu: self.close(), icon='exit')) self.mb.insert('game',_menu_Game) let _menu_Help = MenuList() _menu_Help.insert(MenuEntry("About Mines", self.launch_about, icon='star')) self.mb.insert('help',_menu_Help) let _menu_New_Game = MenuList() _menu_New_Game.insert(MenuEntry("9×9, 10 mines", lambda menu: self.basic_game())) _menu_New_Game.insert(MenuEntry("16×16, 40 mines", lambda menu: self.new_game((16,40)))) _menu_New_Game.insert(MenuEntry("20×20, 90 mines", lambda menu: self.new_game((20,90)))) self.mb.insert('new-game',_menu_New_Game) self.mb.callback = lambda x: self.draw() self.hover_widget = None self.down_button = None self.top_height = bounds['top_height'] + 24 + 40 self.modifiers = 0 self.basic_game() def launch_about(self, menu): import os 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" &') def basic_game(self): self.new_game((9,10)) def new_game(self, action): self.first_click = True self.field_size, self.mine_count = action self.subtitleText = f'There are {self.mine_count} mines.' self.mines = [] let i = 0 while len(self.mines) < self.mine_count: let x, y = randrange(self.field_size), randrange(self.field_size) i += 1 if (x, y) not in self.mines: i = 0 self.mines.append((x,y)) if i > 50: print("Board generation failed") return def check_neighbors(r, c): let n = [] if r > 0: if c > 0: n.append((r-1,c-1)) n.append((r-1,c)) if c < self.field_size - 1: n.append((r-1,c+1)) if r < self.field_size - 1: if c > 0: n.append((r+1,c-1)) n.append((r+1,c)) if c < self.field_size - 1: n.append((r+1,c+1)) if c > 0: n.append((r,c-1)) if c < self.field_size - 1: n.append((r,c+1)) return n def check_neighbor_buttons(r,c): return [self.buttons[x][y] for x, y in check_neighbors(r,c)] def mine_func(b): let button = b if self.first_click: let i = 0 while button.is_mine or button.mines: if i > 30: print("Failed to generate board") return self.new_game(action) button = self.buttons[button.row][button.col] i += 1 self.first_click = False if button.flagged: return if button.is_mine and not button.revealed: self.subtitleText = "You lose." for row in self.buttons: for b in row: b.reveal() self.draw() return else: if not button.revealed: button.reveal() if button.mines == 0: let n = [x for x in check_neighbor_buttons(button.row,button.col) if not x.revealed] while n: b = n.pop() b.reveal() if b.mines == 0: n.extend([x for x in check_neighbor_buttons(b.row,b.col) if not x.revealed and not x in n]) self.check_win() self.buttons = [] for row in range(self.field_size): let r = [] for col in range(self.field_size): let is_mine = (row,col) in self.mines let neighbor_mines = len([x for x in check_neighbors(row,col) if x in self.mines]) r.append(MineButton(mine_func, row, col, is_mine, neighbor_mines)) self.buttons.append(r) def check_win(self): let buttons = [] for row in self.buttons: buttons.extend(row) let n_flagged = len([x for x in buttons if x.flagged and not x.revealed]) let n_revealed = len([x for x in buttons if x.revealed]) if n_flagged == self.mine_count and n_revealed + n_flagged == self.field_size ** 2: self.subtitleText = "You win." for b in buttons: b.reveal() def draw(self): self.fill(rgb(204,204,204)) let bounds = decor_get_bounds(self) self.subtitleFont.draw_string(self,self.subtitleText, (self.width - self.subtitleFont.width(self.subtitleText)) // 2, bounds['top_height'] + 24 + 25) let offset_x = bounds['left_width'] let offset_y = self.top_height self.button_height = (self.height - self.top_height - bounds['bottom_height']) // len(self.buttons) let i = 0 for row in self.buttons: self.button_width[i] = (self.width - bounds['width']) // len(row) for button in row: if button: button.draw(self,offset_x,offset_y,self.button_width[i],self.button_height) offset_x += self.button_width[i] offset_x = bounds['left_width'] offset_y += self.button_height i += 1 self.mb.place(bounds['left_width'],bounds['top_height'],self.width-bounds['width'],self) self.mb.render(self) decor_render(self) self.flip() def flag(self, button): button.set_flagged() self.check_win() self.draw() def mouse_event(self, msg): let decResponse = decor_handle_event(msg) if decResponse == 2: self.close() return True else if decResponse == 5: decor_show_default_menu(self, self.x + msg.new_x, self.y + msg.new_y) let bounds = decor_get_bounds(self) let x, y = msg.new_x, msg.new_y let w, h = self.width, self.height if x >= 0 and x < w and y < self.top_height: if self.hover_widget: self.hover_widget.focus_leave() self.hover_widget = None self.draw() self.mb.mouse_event(self, msg) return let redraw = False if self.down_button: if msg.command == 2 or msg.command == 0: # RAISE or CLICK if not (msg.buttons & 1): # BUTTON_LEFT if x >= self.down_button.x and \ x < self.down_button.x + self.down_button.width and \ y >= self.down_button.y and \ y < self.down_button.y + self.down_button.height: self.down_button.focus_enter() if self.modifiers & 1: # LEFT_CTRL self.flag(self.down_button) else: self.down_button.action(self.down_button) self.down_button = None redraw = True else: self.down_button.focus_leave() self.down_button = None redraw = True else: # TOOD decors, menubar if y >= self.top_height and y < h and x >= bounds['left_width'] and x < w: let button let xh = self.button_height * len(self.buttons) let row = ((y - self.top_height) * len(self.buttons)) // xh if row < len(self.buttons): let xw = self.button_width[row] * len(self.buttons[row]) let col = ((x - bounds['left_width']) * len(self.buttons[row])) // xw if col < len(self.buttons[row]): button = self.buttons[row][col] else: button = None else: button = None if button is not self.hover_widget: if button: button.focus_enter() redraw = True if self.hover_widget: self.hover_widget.focus_leave() redraw = True self.hover_widget = button if msg.command == 3: # DOWN if button: button.hilight = 2 self.down_button = button redraw = True else: if self.hover_widget: self.hover_widget.focus_leave() redraw = True self.hover_widget = None if redraw: self.draw() def keyboard_event(self, msg): self.modifiers = msg.modifiers def close(self): super().close() mainloop.exit() let window = MinesWindow() window.draw() mainloop.run() ================================================ FILE: apps/misaka-test.c ================================================ /** * @file apps/misaka-test.c * @brief Test app for Misaka with a bunch of random stuff. * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include static void demo_runKurokoSnippet(void) { krk_initVM(0); krk_startModule("__main__"); krk_interpret("import kuroko\nprint('Kuroko',kuroko.version)\n", ""); krk_freeVM(); } static void demo_drawWallpaper(void) { /* Set up a wrapper context for the framebuffer */ gfx_context_t * ctx = init_graphics_fullscreen(); /* Load the wallpaper. */ sprite_t wallpaper = { 0 }; load_sprite(&wallpaper, "/usr/share/wallpaper.jpg"); wallpaper.alpha = ALPHA_EMBEDDED; printf("wallpaper sprite info: %d x %d\n", wallpaper.width, wallpaper.height); draw_sprite_scaled(ctx, &wallpaper, 0, 0, 1440, 900); flip(ctx); //blur_context_box(&ctx, 10); } int main(int argc, char * argv[]) { demo_drawWallpaper(); demo_runKurokoSnippet(); //execve("/bin/kuroko",(char*[]){"kuroko",NULL},(char*[]){NULL}); char * args[] = { "/bin/sh", "-c", "sleep 2; echo hi; echo glorp", NULL, }; pid_t pid = fork(); if (!pid) { printf("returned from fork in child\n"); execvp(args[0], args); exit(1); } else { printf("returned from fork with pid = %d\n", pid); int status; waitpid(pid, &status, 0); printf("done with waitpid, looping\n"); while (1) { sched_yield(); } return WEXITSTATUS(status); } return 0; } ================================================ FILE: apps/mixerctl.c ================================================ /** * @brief Control audio mixer knobs * * @copyright * 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 Mike Gerow */ #include #include #include #include #include #include static char usage[] = "%s - Control audio mixer settings.\n" "\n" "Usage %s [-d device_id] -l\n" " %s [-d device_id] [-k knob_id] -r\n" " %s [-d device_id] [-k knob_id] -w knob_value\n" " %s -h\n" "\n" " -d: \033[3mDevice id to address. Defaults to the main sound device.\033[0m\n" " -l: \033[3mList the knobs on a device.\033[0m\n" " -k: \033[3mKnob id to address. Defaults to the device's master knob.\033[0m\n" " -r: \033[3mPerform a read on the given device's knob. Defaults to the device's\n" " master knob.\033[0m\n" " -w: \033[3mPerform a write on the given device's knob. The value should be a\n" " float from 0.0 to 1.0.\033[0m\n" " -h: \033[3mPrint this help message and exit.\033[0m\n"; int main(int argc, char * argv[]) { uint32_t device_id = SND_DEVICE_MAIN; uint32_t knob_id = SND_KNOB_MASTER; uint8_t list_flag = 0; uint8_t read_flag = 0; uint8_t write_flag = 0; double write_value = 0.0; int c; while ((c = getopt(argc, argv, "d:lk:rw:h?")) != -1) { switch (c) { case 'd': device_id = atoi(optarg); break; case 'l': list_flag = 1; break; case 'k': knob_id = atoi(optarg); break; case 'r': read_flag = 1; break; case 'w': write_flag = 1; write_value = atof(optarg); if (write_value < 0.0 || write_value > 1.0) { fprintf(stderr, "argument -w value must be between 0.0 and 1.0\n"); exit(EXIT_FAILURE); } break; case 'h': case '?': default: fprintf(stderr, usage, argv[0], argv[0], argv[0], argv[0], argv[0]); exit(EXIT_FAILURE); } } int mixer = open("/dev/mixer", O_RDONLY); if (mixer < 1) { //perror("open"); exit(EXIT_FAILURE); } if (list_flag) { snd_knob_list_t list = {0}; list.device = device_id; if (ioctl(mixer, SND_MIXER_GET_KNOBS, &list) < 0) { perror("ioctl"); exit(EXIT_FAILURE); } for (uint32_t i = 0; i < list.num; i++) { snd_knob_info_t info = {0}; info.device = device_id; info.id = list.ids[i]; if (ioctl(mixer, SND_MIXER_GET_KNOB_INFO, &info) < 0) { perror("ioctl"); exit(EXIT_FAILURE); } fprintf(stdout, "%d: %s\n", (unsigned int)info.id, info.name); } exit(EXIT_SUCCESS); } if (read_flag) { snd_knob_value_t value = {0}; value.device = device_id; value.id = knob_id; if (ioctl(mixer, SND_MIXER_READ_KNOB, &value) < 0) { perror("ioctl"); exit(EXIT_FAILURE); } double double_val = (double)value.val / SND_KNOB_MAX_VALUE; fprintf(stdout, "%f\n", double_val); exit(EXIT_FAILURE); } if (write_flag) { snd_knob_value_t value = {0}; value.device = device_id; value.id = knob_id; value.val = (uint32_t)(write_value * SND_KNOB_MAX_VALUE); if (ioctl(mixer, SND_MIXER_WRITE_KNOB, &value) < 0) { perror("ioctl"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } fprintf(stderr, "No operation specified.\n"); exit(EXIT_FAILURE); } ================================================ FILE: apps/mkdir.c ================================================ /** * @brief Create directories * * @copyright * 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-2014 K. Lange */ #include #include #include #include #include #include #include #include int makedir(const char * dir, int mask, int parents) { if (!parents) return mkdir(dir,mask); char * tmp = strdup(dir); char * c = tmp; while ((c = strchr(c+1,'/'))) { *c = '\0'; if (mkdir(tmp,mask) < 0) { if (errno == EEXIST) { *c = '/'; continue; } else { return -1; } } *c = '/'; continue; } return mkdir(tmp, mask); } int main(int argc, char ** argv) { int retval = 0; int parents = 0; int opt; while ((opt = getopt(argc, argv, "m:p")) != -1) { switch (opt) { case 'm': fprintf(stderr, "%s: -m unsupported\n", argv[0]); return 1; case 'p': parents = 1; break; } } if (optind == argc) { fprintf(stderr, "%s: expected argument\n", argv[0]); return 1; } for (int i = optind; i < argc; ++i) { if (makedir(argv[i], 0777, parents) < 0) { if (parents && errno == EEXIST) continue; fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); retval = 1; } } return retval; } ================================================ FILE: apps/mktemp.c ================================================ /** * @brief mktemp - create a temporary directory and print its name * * @copyright * 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 */ #include #include #include #include #include #include #include int main(int argc, char * argv[]) { int opt; int dry_run = 0; int quiet = 0; int directory = 0; while ((opt = getopt(argc,argv,"duq")) != -1) { switch (opt) { case 'd': directory = 1; break; case 'u': dry_run = 1; break; case 'q': quiet = 1; break; } } char * template; int i = optind; if (i == argc) { template = strdup("/tmp/tmp.XXXXXX"); } else { template = strdup(argv[i]); } char * result = mktemp(template); if (!result) { fprintf(stderr, "%s: %s\n", argv[0], strerror(errno)); return 1; } if (!quiet) { fprintf(stdout, "%s\n", result); } if (!dry_run) { if (directory) { if (mkdir(result,0777) < 0) { fprintf(stderr, "%s: mkdir: %s: %s\n", argv[0], result, strerror(errno)); return 1; } } else { FILE * f = fopen(result,"w"); if (!f) { fprintf(stderr, "%s: open: %s: %s\n", argv[0], result, strerror(errno)); return 1; } } } return 0; } ================================================ FILE: apps/more.c ================================================ /** * @brief Print piped input or files one screenful at a time. * * @copyright * 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-2018 K. Lange */ #include #include #include #include #include #include #include #include #include static int term_width = 80; static int term_height = 25; static int term_x = 0; static int term_yish = 1; static int to_eight(uint32_t codepoint, char * out) { memset(out, 0x00, 7); if (codepoint < 0x0080) { out[0] = (char)codepoint; } else if (codepoint < 0x0800) { out[0] = 0xC0 | (codepoint >> 6); out[1] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x10000) { out[0] = 0xE0 | (codepoint >> 12); out[1] = 0x80 | ((codepoint >> 6) & 0x3F); out[2] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x200000) { out[0] = 0xF0 | (codepoint >> 18); out[1] = 0x80 | ((codepoint >> 12) & 0x3F); out[2] = 0x80 | ((codepoint >> 6) & 0x3F); out[3] = 0x80 | ((codepoint) & 0x3F); } else if (codepoint < 0x4000000) { out[0] = 0xF8 | (codepoint >> 24); out[1] = 0x80 | (codepoint >> 18); out[2] = 0x80 | ((codepoint >> 12) & 0x3F); out[3] = 0x80 | ((codepoint >> 6) & 0x3F); out[4] = 0x80 | ((codepoint) & 0x3F); } else { out[0] = 0xF8 | (codepoint >> 30); out[1] = 0x80 | ((codepoint >> 24) & 0x3F); out[2] = 0x80 | ((codepoint >> 18) & 0x3F); out[3] = 0x80 | ((codepoint >> 12) & 0x3F); out[4] = 0x80 | ((codepoint >> 6) & 0x3F); out[5] = 0x80 | ((codepoint) & 0x3F); } return strlen(out); } static void char_draw(int c) { if (c == '\t') { int count = 8 - (term_x % 8); for (int i = 0; i < count; ++i) { printf(" "); } } else if (c < 32 || c == 0x7F) { printf("\033[7m^%c\033[0m", (c < 32) ? ('@' + c) : '?'); } else if (c > 0x7f && c < 0xa0) { printf("\033[7m<%02x>\033[0m", c); } else if (c == 0xa0) { printf("\033[7m \033[0m"); } else if (c > 127) { if (wcwidth(c) >= 1) { char tmp[8] = {0}; to_eight(c,tmp); printf("%s", tmp); } else { if (c < 0x10000) { printf("\033[7m[U+%04x]\033[0m", c); } else { printf("\033[7m[U+%06x]\033[0m", c); } } } else { printf("%c", c); } } static int char_width(int c) { if (c == '\t') { return 8 - (term_x % 8); } else if (c < 32 || c == 0x7F) { return 2; /* ^@ */ } else if (c > 0x7f && c < 0xa0) { return 4; /* */ } else if (c == 0xa0) { return 1; /* nbsp */ } else if (c > 127) { int out = wcwidth(c); if (out >= 1) return out; return (c < 0x10000) ? 8 : 10; } return 1; } static struct termios old; static void get_initial_termios(void) { tcgetattr(STDOUT_FILENO, &old); } static void set_unbuffered(void) { struct termios new = old; new.c_iflag &= (~ICRNL) & (~IXON); new.c_lflag &= (~ICANON) & (~ECHO); tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new); } static void set_buffered(void) { tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old); } static void next_line(void) { term_yish++; if (term_yish < term_height) { printf("\n"); term_x = 0; } else { printf("\n\033[7m--More--\033[0m"); fflush(stdout); do { char buf[1]; read(STDERR_FILENO, buf, 1); char c = buf[0]; switch (c) { case ' ': term_yish = 1; /* fallthrough */ case '\n': case '\r': printf("\r\033[K"); fflush(stdout); term_x = 0; return; case 'q': printf("\r\033[K"); fflush(stdout); set_buffered(); exit(0); return; } } while (1); } } static void do_file(char * name, FILE * f) { if (!f) { printf("\033[7m`%s`: %s\033[0m", name, strerror(errno)); next_line(); return; } uint32_t code, state = 0; while (!feof(f)) { int c = fgetc(f); if (c < 0) break; if (!decode(&state, &code, c)) { if (code == '\n') next_line(); else { int width = char_width(code); if (term_x + width > term_width) { next_line(); } char_draw(code); term_x += width; } } else if (state == UTF8_REJECT) { state = 0; } } } int main(int argc, char * argv[]) { if (argc < 2 && isatty(STDIN_FILENO)) { fprintf(stderr, "usage: %s file...\n", argv[0]); return 1; } struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); term_width = w.ws_col; term_height = w.ws_row; get_initial_termios(); set_unbuffered(); if (argc < 2) { do_file("stdin",stdin); } for (int i = 1; i < argc; ++i) { FILE * f = fopen(argv[i], "r"); do_file(argv[i], f); if (f) fclose(f); } set_buffered(); return 0; } ================================================ FILE: apps/mount.c ================================================ /** * @brief Mount filesystems into the VFS * * @copyright * 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-2018 K. Lange */ #include #include #include #include int main(int argc, char ** argv) { if (argc < 4) { fprintf(stderr, "Usage: %s type device mountpoint\n", argv[0]); return 1; } int ret = mount(argv[2], argv[3], argv[1], 0, NULL); if (ret < 0) { fprintf(stderr, "%s: %s\n", argv[0], strerror(errno)); return ret; } return 0; } ================================================ FILE: apps/msk.c ================================================ /** * @brief Package Management Utility for ToaruOS * * This is a not-quite-faithful reconstruction of the original * Python msk. The supported package format is a bit different, * to avoid the need to implement a full JSON parser. * * Packages can optionally be uncompressed, which is also * important for bootstrapping at the moment. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #define MSK_VERSION "1.0.0" #define VAR_PATH "/var/msk" #define LOCK_PATH "/var/run/msk.lock" static confreader_t * msk_config = NULL; static confreader_t * msk_manifest = NULL; static hashmap_t * msk_installed = NULL; static int lock_fd = -1; static int verbose = 0; static void release_lock(void) { if (lock_fd != -1) { unlink(LOCK_PATH); } } static void needs_lock(void) { if (lock_fd == -1) { lock_fd = open(LOCK_PATH, O_RDWR|O_CREAT|O_EXCL); if (lock_fd < 0) { fprintf(stderr, "msk: failed to obtain exclusive lock\n"); exit(1); } atexit(release_lock); } } /** * checks whether 'candidate' is newer than 'current'. * * Requires version strings to be of the form x.y.z * * > 0 candidate is newer * = 0 candidate is the same * < 0 candidate is older */ static int compare_version_strings(char * current, char * candidate) { int current_x, current_y, current_z; int candidate_x, candidate_y, candidate_z; sscanf(current, "%d.%d.%d", ¤t_x, ¤t_y, ¤t_z); sscanf(candidate, "%d.%d.%d", &candidate_x, &candidate_y, &candidate_z); if (candidate_x > current_x) { return 1; } else if (candidate_x == current_x) { if (candidate_y >current_y) { return 1; } else if (candidate_y == current_y) { if (candidate_z > current_z) { return 1; } else if (candidate_z == current_z) { return 0; } } } return -1; } static void read_config(void) { confreader_t * conf = confreader_load("/etc/msk.conf"); if (!conf) { fprintf(stderr, "failed to read configuration file\n"); exit(1); } if (!strcmp(confreader_getd(conf, "", "verbose",""), "y")) { verbose = 1; } msk_config = conf; } static void read_manifest(int required) { confreader_t * conf = confreader_load(VAR_PATH "/manifest"); if (!conf) { if (required) { fprintf(stderr, "no manifest; try `msk update` first\n"); exit(1); } else { conf = confreader_create_empty(); } } msk_manifest = conf; } static void read_installed(void) { msk_installed = hashmap_create(10); FILE * installed = fopen(VAR_PATH "/installed", "r"); if (!installed) return; while (!feof(installed)) { char tmp[128] = {0}; if (!fgets(tmp, 128, installed)) break; char * nl = strstr(tmp, "\n"); if (nl) *nl = '\0'; char * eqeq = strstr(tmp, "=="); if (!eqeq) { fprintf(stderr, "Installation cache is malformed\n"); fprintf(stderr, "line was: [%s]\n", tmp); exit(1); } *eqeq = '\0'; char * version = eqeq+2; hashmap_set(msk_installed, tmp, strdup(version)); } } static void make_var(void) { struct stat buf; if (stat(VAR_PATH, &buf)) { mkdir(VAR_PATH, 0755); } } static void needs_root(void) { if (geteuid() != 0) { fprintf(stderr, "only root can install packages; try `sudo`\n"); exit(1); } } static int usage(int argc, char * argv[]) { #define _IT "\033[3m" #define _END "\033[0m\n" fprintf(stderr, "%s - package manager " MSK_VERSION "\n" "\n" "usage: %s update\n" " %s install [PACKAGE...]\n" "\n" " update " _IT "update local manifest from remote" _END " install " _IT "install packages" _END "\n", argv[0], argv[0], argv[0]); return 1; } static int update_stores(int argc, char * argv[]) { needs_root(); if (argc > 2) { fprintf(stderr,"%s: %s: unexpected arguments in command\n", argv[0], argv[1]); return usage(argc,argv); } const char * manifest_prefix = ""; #ifdef __aarch64__ manifest_prefix = "aarch64."; #endif needs_lock(); read_config(); make_var(); confreader_t * manifest_out = confreader_create_empty(); hashmap_t * remotes = hashmap_get(msk_config->sections, "remotes"); if (!remotes) { fprintf(stderr, "No remotes defined - bad msk.conf?\n"); return 1; } int one_success = 0; char * order = strdup(confreader_getd(msk_config, "", "remote_order", "")); char * save; char * tok = strtok_r(order, ",", &save); do { char * remote_name = strdup(tok); char * remote_path = hashmap_get(remotes, remote_name); if (!remote_path) { fprintf(stderr, "Undefined remote specified in remote_order: %s\n", remote_name); goto _next; } confreader_t * manifest; if (remote_path[0] == '/') { char source[512]; sprintf(source, "%s/manifest", remote_path); manifest = confreader_load(source); if (!manifest) { fprintf(stderr, "Skipping unavailable local manifest '%s'.\n", remote_name); goto _next; } } else { char cmd[512]; sprintf(cmd, "fetch -vo /tmp/.msk_remote_%s %s/%smanifest", remote_name, remote_path, manifest_prefix); fprintf(stderr, "Downloading remote manifest '%s'...\n", remote_name); if (system(cmd)) { fprintf(stderr, "Skipping unavailable remote manifest '%s' (%s).\n", remote_name, remote_path); goto _next; } sprintf(cmd, "/tmp/.msk_remote_%s", remote_name); manifest = confreader_load(cmd); } list_t * packages = hashmap_keys(manifest->sections); foreach(nnode, packages) { char * package_name = (char*)nnode->value; hashmap_t * package_data = (hashmap_t*)hashmap_get(manifest->sections, package_name); if (!strcmp(package_name,"")) continue; /* skip intro section - remote repo information */ hashmap_set(package_data, "remote_path", remote_path); hashmap_set(package_data, "remote_name", remote_name); if (!hashmap_has(manifest_out->sections, package_name)) { /* Package not yet known */ hashmap_set(manifest_out->sections, package_name, package_data); } else { /* Package is known, keep the newer version */ char * old_version = confreader_get(manifest_out, package_name, "version"); char * new_version = confreader_get(manifest, package_name, "version"); if (compare_version_strings(old_version, new_version) > 0) { hashmap_set(manifest_out->sections, package_name, package_data); } } } one_success = 1; _next: tok = strtok_r(NULL, ",", &save); } while (tok); free(order); if (!one_success) { fprintf(stderr, "\033[1;31merror\033[0m: no remote succeeded, no packages are available\n"); return 1; } return confreader_write(manifest_out, VAR_PATH "/manifest"); } static int list_contains(list_t * list, char * key) { foreach(node, list) { char * v = node->value; if (!strcmp(v,key)) return 1; } return 0; } static int process_package(list_t * pkgs, char * name) { if (!strlen(name)) { fprintf(stderr, "invalid package name\n"); return 1; } if (hashmap_has(msk_installed, name)) return 0; if (list_contains(pkgs, name)) return 0; if (!hashmap_has(msk_manifest->sections, name)) { fprintf(stderr, "don't know how to install '%s'\n", name); return 1; } /* Gather dependencies */ char * tmp = confreader_get(msk_manifest, name, "dependencies"); if (tmp && strlen(tmp)) { char * deps = strdup(tmp); char * save; char * tok = strtok_r(deps, " ", &save); do { process_package(pkgs, tok); tok = strtok_r(NULL, " ", &save); } while (tok); free(deps); } /* Insert */ list_insert(pkgs, strdup(name)); return 0; } static int install_package(char * pkg) { char * type = confreader_getd(msk_manifest, pkg, "type", ""); char * msk_remote = confreader_get(msk_manifest, pkg, "remote_path"); if (strstr(msk_remote, "http:") == msk_remote || strstr(msk_remote, "https:") == msk_remote) { char * source = confreader_get(msk_manifest, pkg, "source"); if (source) { fprintf(stderr, "Download %s...\n", pkg); char cmd[1024]; sprintf(cmd, "fetch -o /tmp/msk.file -v %s/%s", msk_remote, source); int status; if ((status = system(cmd))) { return status; } hashmap_set(hashmap_get(msk_manifest->sections, pkg), "source", "/tmp/msk.file"); } } else if (msk_remote[0] == '/') { char * source = confreader_get(msk_manifest, pkg, "source"); if (source) { char * pkg_name = malloc(strlen(msk_remote) + strlen(source) + 2); sprintf(pkg_name, "%s/%s", msk_remote, source); hashmap_set(hashmap_get(msk_manifest->sections, pkg), "source", pkg_name); } } fprintf(stderr, "Install '%s'...\n", pkg); if (!strcmp(type, "file")) { /* Legacy single-file package, has a source and a destination */ if (verbose) { fprintf(stderr, " - Copy file '%s' to '%s' and set its mask to '%s'\n", confreader_get(msk_manifest, pkg, "source"), confreader_get(msk_manifest, pkg, "destination"), confreader_get(msk_manifest, pkg, "mask")); } char cmd[1024]; sprintf(cmd, "cp %s %s; chmod 0%s %s", confreader_get(msk_manifest, pkg, "source"), confreader_get(msk_manifest, pkg, "destination"), confreader_get(msk_manifest, pkg, "mask"), confreader_get(msk_manifest, pkg, "destination")); int status; if ((status = system(cmd))) { fprintf(stderr, "installation command returned %d\n", status); return status; } } else if (!strcmp(type, "tar")) { /* Uncompressed archive */ if (verbose) { fprintf(stderr, " - Extract '%s' to '%s'\n", confreader_get(msk_manifest, pkg, "source"), confreader_get(msk_manifest, pkg, "destination")); } char cmd[1024]; sprintf(cmd, "cd %s; tar -xf %s", confreader_get(msk_manifest, pkg, "destination"), confreader_get(msk_manifest, pkg, "source")); int status; if ((status = system(cmd))) { fprintf(stderr, "installation command returned %d\n", status); return status; } } else if (!strcmp(type, "tgz")) { /* Compressed archive */ if (verbose) { fprintf(stderr, " - Extract (compressed) '%s' to '%s'\n", confreader_get(msk_manifest, pkg, "source"), confreader_get(msk_manifest, pkg, "destination")); } char cmd[1024]; sprintf(cmd, "cd %s; tar -xzf %s", confreader_get(msk_manifest, pkg, "destination"), confreader_get(msk_manifest, pkg, "source")); int status; if ((status = system(cmd))) { fprintf(stderr, "installation command returned %d\n", status); return status; } } else if (!strcmp(type, "meta")) { /* Do nothing */ } else { fprintf(stderr, "Unknown package type: %s\n", type); return 1; } char * source = confreader_get(msk_manifest, pkg, "source"); if (source && !strcmp(source, "/tmp/msk.file")) { char cmd[1024]; sprintf(cmd, "rm %s", source); int status; if ((status = system(cmd))) { fprintf(stderr, "cleanup command returned %d\n", status); return status; } } char * post = confreader_getd(msk_manifest, pkg, "post", ""); if (strlen(post)) { int status; if ((status = system(post))) { fprintf(stderr, "post-installation command returned %d\n", status); return status; } } /* Mark as installed */ FILE * installed = fopen(VAR_PATH "/installed", "a"); fprintf(installed, "%s==%s\n", pkg, confreader_get(msk_manifest, pkg, "version")); fclose(installed); return 0; } static int install_packages(int argc, char * argv[]) { needs_root(); needs_lock(); read_config(); read_manifest(1); read_installed(); /* Go through each package and find its dependencies */ list_t * ordered = list_create(); for (int i = 2; i < argc; ++i) { if (process_package(ordered, argv[i])) { return 1; } } /* Additional packages must be installed, let's ask. */ if (ordered->length != (unsigned int)(argc - 2) && !getenv("MSK_YES")) { fprintf(stderr, "The following packages will be installed:\n"); fprintf(stderr, " "); int notfirst = 0; foreach(node, ordered) { fprintf(stderr, "%s%s", notfirst ? " " : "", (char*)node->value); notfirst = 1; } fprintf(stderr, "\nContinue? [Y/n] "); fflush(stderr); char resp[5]; fgets(resp, 5, stdin); if (!(!strcmp(resp,"\n") || !strcmp(resp,"y\n") || !strcmp(resp,"Y\n") || !strcmp(resp,"yes\n"))) { fprintf(stderr, "Aborting.\n"); return 1; } } foreach(node, ordered) { if (install_package(node->value)) { return 1; } } return 0; } static int list_packages(int argc, char * argv[]) { read_config(); read_manifest(0); read_installed(); /* Go through sections */ list_t * packages = hashmap_keys(msk_manifest->sections); foreach(node, packages) { char * name = node->value; if (!strlen(name)) continue; /* skip empty section */ char * desc = confreader_get(msk_manifest, name, "description"); fprintf(stderr, " %c %20s %s\n", hashmap_has(msk_installed, name) ? 'I' : ' ', name, desc); /* TODO: Installation status */ } return 0; } static int count_packages(int argc, char * argv[]) { read_config(); read_manifest(0); read_installed(); int installed = 0; int available = 0; /* Go through sections */ list_t * packages = hashmap_keys(msk_manifest->sections); foreach(node, packages) { char * name = node->value; if (!strlen(name)) continue; /* skip empty section */ available++; if (hashmap_has(msk_installed, name)) { installed++; } } fprintf(stdout, "%d installed; %d available\n", installed, available); return 0; } static int version(void) { fprintf(stderr, "msk " MSK_VERSION "\n"); return 0; } int main(int argc, char * argv[]) { if (argc < 2) { return usage(argc,argv); } else if (!strcmp(argv[1],"--version")) { return version(); } else if (!strcmp(argv[1],"update")) { return update_stores(argc,argv); } else if (!strcmp(argv[1], "install")) { return install_packages(argc,argv); } else if (!strcmp(argv[1], "list")) { return list_packages(argc, argv); } else if (!strcmp(argv[1], "count")) { return count_packages(argc, argv); } else { fprintf(stderr, "%s: unknown command '%s'\n", argv[0], argv[1]); return usage(argc,argv); } } ================================================ FILE: apps/mv.c ================================================ /** * @brief Move files * * Poor implementation, mostly just 'cp' and 'rm'. * * Ideally, should figure out if it can use 'rename'... and also * we should implement 'rename'... * * @copyright * 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 */ #include #include #include #include #include #include #include #define APP_NAME "mv" #define IS_MV static int recursive = 1; #include "cp.c" #include "rm.c" int main(int argc, char * argv[]) { int opt; int interactive = 0; int force = 0; while ((opt = getopt(argc, argv, "if")) != -1) { switch (opt) { case 'i': force = 0; interactive = 1; break; case 'f': force = 1; interactive = 0; break; default: fprintf(stderr, "mv: unrecognized option '%c'\n", opt); return 1; } } if (optind + 1 >= argc) { fprintf(stderr, "usage: %s [-if] source_file... destination\n", argv[0]); return 1; } char * destination = argv[argc-1]; int target_is_dir = 0; struct stat statbuf; int exists = 0; if ((exists = !stat(destination, &statbuf))) { if (S_ISDIR(statbuf.st_mode)) { target_is_dir = 1; } } int destination_has_trailing_slash = strlen(destination) && destination[strlen(destination)-1] == '/'; int multiple_args = optind + 2 < argc; if ((multiple_args && !target_is_dir) || (exists && !target_is_dir && destination_has_trailing_slash)) { fprintf(stderr, "%s: %s: Not a directory\n", argv[0], destination); return 1; } int ret = 0; for (int i = optind; i < argc - 1; ++i) { char * target = destination; if (target_is_dir) { char * tmp = strdup(argv[i]); char * target_basename = basename(tmp); size_t size = strlen(destination) + strlen(target_basename) + 2; target = malloc(size); snprintf(target, size, "%s%s%s", destination, destination_has_trailing_slash ? "" : "/", target_basename); free(tmp); } if (!force && !stat(target, &statbuf)) { if (interactive) { /* || (isatty(STDIN_FILENO) && some_check_for_writability...) */ fprintf(stderr, "%s: overwrite '%s'? ", argv[0], target); fflush(stderr); /* just in case */ char tmp[10] = {0}; fgets(tmp, 10, stdin); if (tmp[0] != 'y' && tmp[0] != 'Y') { ret |= 1; goto _continue; } } } if (rename(argv[i], target) < 0) { if (errno != EXDEV && errno != ENOTSUP) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); ret |= 1; } else if (copy_thing(argv[i], target) || rm_thing(argv[i])) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); ret |= 1; } } _continue: if (target != destination) free(target); } return ret; } ================================================ FILE: apps/netty.c ================================================ /** * @brief Provides a PTY over a reverse network socket. * * Pipes data into and out of a PTY from a TCP socket connected to a remote * server. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include int fd_master, fd_slave, fd_serial; volatile int _stop = 0; void * handle_in(void * unused) { while (!_stop) { int index = fswait2(1,&fd_serial,200); char buf[1]; int r; switch (index) { case 0: /* fd_serial */ r = read(fd_serial, buf, 1); if (r > 0) { write(fd_master, buf, r); } break; } } return NULL; } int main(int argc, char * argv[]) { char * user = NULL; if (getuid() != 0) { fprintf(stderr, "%s: only root can do that\n", argv[0]); return 1; } int opt; while ((opt = getopt(argc, argv, "a:")) != -1) { switch (opt) { case 'a': user = optarg; break; } } if (optind == argc) { fprintf(stderr, "usage: %s remote:port\n", argv[0]); return 1; } char * remotehost = argv[optind]; char * colon = strstr(remotehost, ":"); if (!colon) { fprintf(stderr, "usage: %s remote:port\n", argv[0]); return 1; } *colon = '\0'; colon++; int remoteport = atoi(colon); openpty(&fd_master, &fd_slave, NULL, NULL, NULL); int sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket"); return 1; } struct hostent * remote = gethostbyname(remotehost); if (!remote) { perror("gethostbyname"); return 1; } struct sockaddr_in addr; addr.sin_family = AF_INET; memcpy(&addr.sin_addr.s_addr, remote->h_addr, remote->h_length); addr.sin_port = htons(remoteport); if (connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) { perror("connect"); return 1; } fd_serial = sock; //open(file, O_RDWR); pthread_t input_buffer_thread; pthread_create(&input_buffer_thread, NULL, handle_in, NULL); pid_t child = fork(); if (!child) { setsid(); dup2(fd_slave, 0); dup2(fd_slave, 1); dup2(fd_slave, 2); ioctl(STDIN_FILENO, TIOCSCTTY, &(int){1}); tcsetpgrp(STDIN_FILENO, getpid()); system("ttysize -q"); char * tokens[] = {"/bin/login-loop",NULL,NULL,NULL}; if (user) { tokens[1] = "-f"; tokens[2] = user; } execvp(tokens[0], tokens); exit(1); } else { while (1) { int index = fswait2(1,&fd_master,200); char buf[1024]; int r; switch (index) { case 0: /* fd_master */ r = read(fd_master, buf, 1024); write(fd_serial, buf, r); break; default: /* timeout */ { int result = waitpid(child, NULL, WNOHANG); if (result > 0) { /* Child login shell has returned (session ended) */ _stop = 1; return 0; } } break; } } } return 0; } ================================================ FILE: apps/nproc.c ================================================ /** * @brief Print the number of available processors. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include int main(int argc, char * argv[]) { printf("%d\n", sysfunc(TOARU_SYS_FUNC_NPROC, NULL)); return 0; } ================================================ FILE: apps/nslookup.c ================================================ /** * @brief Perform DNS lookups. * * @copyright * 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 #include #include #include #include #include #include #include #include int main(int argc, char * argv[]) { if (argc < 2) return 1; struct hostent * host = gethostbyname(argv[1]); if (!host) { fprintf(stderr, "%s: not found\n", argv[1]); return 1; } char * addr = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); fprintf(stderr, "%s: %s\n", host->h_name, addr); return 0; } ================================================ FILE: apps/nyancat/animation.h ================================================ /* * Pop Tart Cat animation frames */ #ifndef ANIMATION_H #define ANIMATION_H const char * frame0[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ".,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ",,>>>>>>>>,,,,,,,,>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", ">>&&&&&&&&>>>>>>>>&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,", "&&++++++++&&&&&&&&'''++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,", "++++++++++++++++++**''+'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,", "++++++++++++++++++'**'''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", "++########++++++++''**''@$$$$$$-'*************',,,,,,,,,,,,,,,,,", "###################''**'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,", "####################''''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", "##========########====''@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,", "======================='@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,", "==;;;;;;;;.=======;;;;'''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;'***''''''''''''''''''',,,,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;'**'','*',,,,,'*','**',,,,,,,,,,,,,,,,,,,,,", ";;,,,,,.,,;;;.;;;;,,,'''',,'',,,,,,,'',,'',,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame1[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ",,>>>>>>>>,,,,,,,,>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", ">>&&&&&&&&>>>>>>>>&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", "&&++++++++&&&&&&&&+++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", "+++++++++++++++++++'+++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", "++++++++++++++++++'*'++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", "++########++++++++'*''''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", "###################****'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", "###################''**'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", "##========########==='''@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", "======================='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", "==;;;;;;;;========;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;'**','*',,,,,,**','**',,,,,,,,,,,,,,,,,,,,", ";;,,,.,,,,;;;;;;;;,,,,''',,,'',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,..,,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame2[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,..,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,.", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", ">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", "&&>>>>>>>&&&&&&&&>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", "++&&&&&&&++++++++&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", "##+++++++########++++++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", "######################''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", "###################'''''@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", "==#######========#'****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", "==================='''='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", ";;=======;;;;;;;;======'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", ";.;;;;;;;;;;;;;;;;;;;;;'*'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", ".,.;;;;;;,,,,,,,,;;;;;;'**',**',,,,,,**','**',,,,,,,,,,,,,,,,,,,", ",.,,,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame3[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,", ">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", "&&>>>>>>>&&&&&&&&>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", "++&&&&&&&++++++++&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", "##+++++++########++++++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", "#####################'''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", "###################''**'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", "==#######========##****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", "=================='*'=='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", ";;=======;;;;;;;;=='==='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", ",,;;;;;;;,,,,,,,,;;;;;'**','*',,,,,,'*','**',,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,'',,''',,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame4[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,", ",,>>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", ">>&&&&&&&&>>>>>>>>&&&&&'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,", "&&++++++++&&&&&&&&+++++'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,", "++++++++++++++++++'''++'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,", "++########+++++++'**''''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", "#################'****''@$$$$$$-'*************',,,,,,,,,,,,,,,,,", "##################''''*'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,", "##========########==='''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", "======================='@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,", "==;;;;;;;;========;;;;''@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;''''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;'***'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,", ";;,,,,,,,,;;;;;;;;,,'**','**,,,,,,'**,'**',,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,'',,''',,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,..,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame5[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,>>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", ">>&&&&&&&&>>>>>>>>&&&&&'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$''$$$@@','',,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$'**'-$$@''**',,,,,,,,,,,,,,,,,,", "&&++++++++&&&&&&&&+++++'@$$$$$$$$'***$$$@'***',,,,,,,,,,,,,,,,,,", "+++++++++++++++++++'+++'@$$$$$-$$'***''''****',,,,,,,,,,,,,,,,,,", "++++++++++++++++++'*'++'@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", "++########++++++++'*''''@$$$$$$$'*************',,,,,,,,,,,,,,,,,", "###################****'@$$$$$$-'***.'****.'**',,,,,,,,,,,,,,,,,", "###################''**'@$-$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", "##========########==='''@$$$$$$$'*%%********%%',,,,,,,,,,,,,,,,,", "======================='@@$$$-$$$'***''''''**',,,,,,,,,,,,,,,,,,", "==;;;;;;;;========;;;;''@@@$$$$$$$'*********',,,,,,,,,,,,,,,,,,.", ";;;;;;;;;;;;;;;;;;;;;'*''@@@@@@@@@@''''''''',,,,,,,,,,,,,,,,,,,.", ";;;;;;;;;;;;;;;;;;;;'***''''''''''''''''*',,,,,,,,,,,,,,,,,,,,,,", ";;,,,,,,,,;;;;;;;;,,'**','**,,,,,,'**,'**',,,,,,,,,,,,,,,,,,..,.", ",,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame6[] = { ".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,..,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>,,,,,,,>>>>>>>>,,,,,,,'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&>>>>>>>&&&&&&&&>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,", "++&&&&&&&++++++++&'''&&'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,", "++++++++++++++++++'*''+'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,", "++++++++++++++++++'**'''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", "##+++++++########++'**''@$$$$$$-'*************',,,,,,,,,,,,,,,,,", "###################''**'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,", "####################''''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", "==#######========#####''@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,", "======================='@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,", ";;=======;;;;;;;;====='''@@@@@@@@@'*********',,,,,,,,,,,.,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;'***''''''''''''''''''',,,,,,,,,,.,,,.,,,,,", ";;;;;;;;;;;;;;;;;;;;;'**'','*',,,,,'**,'**',,,,,,,,,,,,,,,,,,,,,", ",,;;;;;;;,,,,,,,,;;;;'''',,'',,,,,,,'',,'',,,,,,,,,,,.,,,,,.,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame7[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,..,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>,,,,,,,>>>>>>>>,,,,,,,'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&>>>>>>>&&&&&&&&>>>>>>'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", "++&&&&&&&++++++++&&&&&&'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", "+++++++++++++++++++'+++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", "++++++++++++++++++'*'++'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", "##+++++++########+'*''''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", "###################****'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", "###################''**'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", "==#######========####'''@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", "======================='@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", ";;=======;;;;;;;;======''@@@@@@@@@@'*********',,,.,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;'**','*',,,,,,**','**',,,,,,,,,,,,,,,,,,,,", ",,;;;;;;;,,,,,,,,;;;;;''',,,'',,,,,,''',,''',,.,,,,.,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame8[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,..,...,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,>>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", ">>&&&&&&&&>>>>>>>>&&&&&'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", "&&++++++++&&&&&&&&+++++'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", "++########++++++++#####'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", "######################''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", "###################'''''@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", "##========########'****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", "==================='''='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", "==;;;;;;;;========;;;;;'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;;'*'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", ";;,,,,,,,,;;;;;;;;,,,,,'**',**',,,,,,**'.'**',,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,"}; const char * frame9[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,.,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,>>>>>>>>,,,,,,,,>>>>>>>''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", ">>&&&&&&&&>>>>>>>>&&&&&'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$$''-$$@',,'',,,,,,,,,,,,,,,,,,", "&&++++++++&&&&&&&&+++++'@$$$$$$$$$'**'$$@','**',,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$-$$$'***$$@''***',,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,", "++########++++++++#####'@$$$$$$$$$'***********',,,,,,,,,,,,,,,,,", "#####################'''@$$$$$$-$'*************',,,,,,,,,,,,,,,,", "###################''**'@$-$$$$$$'***.'****.'**',,,,,,,,,,,,,,,,", "##========########=****'@$$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,", "=================='*'=='@@$$$-$$$'*%%********%%',,,,,,,,,,,,,,,,", "==;;;;;;;;========;';;;'@@@$$$$$$$'***''''''**',,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;;''@@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;;'**'''''''''''''''''''',,,,,,,,,,,,,,,,,,,", ";;,,,,,,,,;;;;;;;;,,,,'**','*',,..,.**','**',,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,''',,,'',,,,.,''',,''',,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,"}; const char * frame10[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,.,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ".,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,.,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", "&&>>>>>>>&&&&&&&&>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$-$$$$@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$$''$-$$@','',,,,,,,,,,,,,,,,,,,", "++&&&&&&&++++++++&&&&&&'@$$$$$$$$'**'$$$@''**',,,,,,,,,,,,,,,,,,", "+++++++++++++++++++++++'@$$$$$-$$'***$$$@'***',,,,,,,,,,,,,,,,,,", "++++++++++++++++++'''++'@$$$$$$$$'***''''****',,,,,,,,,,,,,,,,,,", "##+++++++########'**''''@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", "#################'****''@$$$$$$-'*************',,,,,,,,,,,,,,,,,", "##################''''*'@$-$$$$$'***.'****.'**',,,,,,,,,,,,,,,,,", "==#######========####'''@$$$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", "======================='@@$$$-$$'*%%********%%',,,,,,,,,,,,,,,,,", ";;=======;;;;;;;;=====''@@@$$$$$$'***''''''**',,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;;''''@@@@@@@@@'*********',,,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;'***'''''''''''''''''''',,,,,,,,,,,,,,,,,,,,", ",,;;;;;;;,,,,,,,,;;;'**'.'**..,,,,'**''**',,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,''',,,'',,,,,,,''',''',,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ".,.,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char * frame11[] = { ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ">>,,,,,,,>>>>>>>>,,,,,,,,''''''''''''''',,,,,,,,,,,,,,,,,,,,,,,,", ">>>>>>>>>>>>>>>>>>>>>>>>'@@@@@@@@@@@@@@@',,,,,,,,,,,,,,,,,,,,,,,", "&&>>>>>>>&&&&&&&&>>>>>>'@@@$$$$$$$$$$$@@@',,,,,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@@$$$$$-$$''$$$@@','',,,,,,,,,,,,,,,,,,,", "&&&&&&&&&&&&&&&&&&&&&&&'@$$-$$$$$'**'-$$@''**',,,,,,,,,,,,,,,,,,", "++&&&&&&&++++++++&&&&&&'@$$$$$$$$'***$$$@'***',,,,,,,,,,,,,,,,,,", "+++++++++++++++++++'+++'@$$$$$-$$'***''''****',,,,,,,,,,,,,,,,,,", "++++++++++++++++++'*'++'@$$$$$$$$'***********',,,,,,,,,,,,,,,,,,", "##+++++++########+'*''''@$$$$$$$'*************',,,,,,,,,,,,,,,,,", "###################****'@$$$$$$-'***.'****.'**',,,,,,,,,,,,,,,,,", "###################''**'@$-$$$$$'***''**'*''**',,,,,,,,,,,,,,,,,", "==#######========####'''@$$$$$$$'*%%********%%',,,,,,,,,,,,,,,,,", "======================='@@$$$-$$$'***''''''**',,,,,,,,,,,,,,,,,,", ";;=======;;;;;;;;=.===''@@@$$$$$$$'*********',,,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;.'*''@@@@@@@@@@''''''''',,,,,,,,,,,,,,,,,,,,", ";;;;;;;;;;;;;;;;;;;;'***''''''''''''''''*',,,,,,,,,,,,,,,,,,,,,,", ",,;;;;;;;,,,,,,,.;;;'**','**,,,,,,'**''**',,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,''',,''',,,,,,''',,''',,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,", ",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"}; const char ** frames[] = { frame0, frame1, frame2, frame3, frame4, frame5, frame6, frame7, frame8, frame9, frame10, frame11, NULL }; #define FRAME_WIDTH 64 #define FRAME_HEIGHT 64 #endif ================================================ FILE: apps/nyancat/telnet.h ================================================ #ifndef TELNET_H #define TELNET_H /* Telnet Defines */ #define IAC 255 #define DONT 254 #define DO 253 #define WONT 252 #define WILL 251 #define SE 240 // Subnegotiation End #define NOP 241 // No Operation #define DM 242 // Data Mark #define BRK 243 // Break #define IP 244 // Interrupt process #define AO 245 // Abort output #define AYT 246 // Are You There #define EC 247 // Erase Character #define EL 248 // Erase Line #define GA 249 // Go Ahead #define SB 250 // Subnegotiation Begin #define BINARY 0 // 8-bit data path #define ECHO 1 // echo #define RCP 2 // prepare to reconnect #define SGA 3 // suppress go ahead #define NAMS 4 // approximate message size #define STATUS 5 // give status #define TM 6 // timing mark #define RCTE 7 // remote controlled transmission and echo #define NAOL 8 // negotiate about output line width #define NAOP 9 // negotiate about output page size #define NAOCRD 10 // negotiate about CR disposition #define NAOHTS 11 // negotiate about horizontal tabstops #define NAOHTD 12 // negotiate about horizontal tab disposition #define NAOFFD 13 // negotiate about formfeed disposition #define NAOVTS 14 // negotiate about vertical tab stops #define NAOVTD 15 // negotiate about vertical tab disposition #define NAOLFD 16 // negotiate about output LF disposition #define XASCII 17 // extended ascii character set #define LOGOUT 18 // force logout #define BM 19 // byte macro #define DET 20 // data entry terminal #define SUPDUP 21 // supdup protocol #define SUPDUPOUTPUT 22 // supdup output #define SNDLOC 23 // send location #define TTYPE 24 // terminal type #define EOR 25 // end or record #define TUID 26 // TACACS user identification #define OUTMRK 27 // output marking #define TTYLOC 28 // terminal location number #define VT3270REGIME 29 // 3270 regime #define X3PAD 30 // X.3 PAD #define NAWS 31 // window size #define TSPEED 32 // terminal speed #define LFLOW 33 // remote flow control #define LINEMODE 34 // Linemode option #define XDISPLOC 35 // X Display Location #define OLD_ENVIRON 36 // Old - Environment variables #define AUTHENTICATION 37 // Authenticate #define ENCRYPT 38 // Encryption option #define NEW_ENVIRON 39 // New - Environment variables #define TN3270E 40 // TN3270E #define XAUTH 41 // XAUTH #define CHARSET 42 // CHARSET #define RSP 43 // Telnet Remote Serial Port #define COM_PORT_OPTION 44 // Com Port Control Option #define SUPPRESS_LOCAL_ECHO 45 // Telnet Suppress Local Echo #define TLS 46 // Telnet Start TLS #define KERMIT 47 // KERMIT #define SEND_URL 48 // SEND-URL #define FORWARD_X 49 // FORWARD_X #define PRAGMA_LOGON 138 // TELOPT PRAGMA LOGON #define SSPI_LOGON 139 // TELOPT SSPI LOGON #define PRAGMA_HEARTBEAT 140 // TELOPT PRAGMA HEARTBEAT #define EXOPL 255 // Extended-Options-List #define NOOPT 0 #define IS 0 #define SEND 1 #endif ================================================ FILE: apps/nyancat.c ================================================ /* * Copyright (c) 2011-2018 K. Lange. All rights reserved. * * Developed by: K. Lange * http://github.com/klange/nyancat * http://nyancat.dakko.us * * 40-column support by: Peter Hazenberg * http://github.com/Peetz0r/nyancat * http://peter.haas-en-berg.nl * * Build tools unified by: Aaron Peschel * https://github.com/apeschel * * For a complete listing of contributors, please see the git commit history. * * This is a simple telnet server / standalone application which renders the * classic Nyan Cat (or "poptart cat") to your terminal. * * It makes use of various ANSI escape sequences to render color, or in the case * of a VT220, simply dumps text to the screen. * * For more information, please see: * * http://nyancat.dakko.us * * 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 Association for Computing Machinery, K. * Lange, 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. */ #define _XOPEN_SOURCE 700 #define _DARWIN_C_SOURCE 1 #define _BSD_SOURCE #define _DEFAULT_SOURCE #define __BSD_VISIBLE 1 #include #include #include #include #include #include #include #include #include #include #include #ifndef TIOCGWINSZ #include #endif #ifdef ECHO #undef ECHO #endif /* * telnet.h contains some #defines for the various * commands, escape characters, and modes for telnet. * (it surprises some people that telnet is, really, * a protocol, and not just raw text transmission) */ #include "nyancat/telnet.h" /* * The animation frames are stored separately in * this header so they don't clutter the core source */ #include "nyancat/animation.h" /* * Color palette to use for final output * Specifically, this should be either control sequences * or raw characters (ie, for vt220 mode) */ const char * colors[256] = {NULL}; /* * For most modes, we output spaces, but for some * we will use block characters (or even nothing) */ const char * output = " "; /* * Are we currently in telnet mode? */ int telnet = 0; /* * Whether or not to show the counter */ int show_counter = 1; /* * Number of frames to show before quitting * or 0 to repeat forever (default) */ unsigned int frame_count = 0; /* * Clear the screen between frames (as opposed to resetting * the cursor position) */ int clear_screen = 1; /* * Force-set the terminal title. */ int set_title = 1; /* * Environment to use for setjmp/longjmp * when breaking out of options handler */ jmp_buf environment; /* * I refuse to include libm to keep this low * on external dependencies. * * Count the number of digits in a number for * use with string output. */ int digits(int val) { int d = 1, c; if (val >= 0) for (c = 10; c <= val; c *= 10) d++; else for (c = -10 ; c >= val; c *= 10) d++; return (c < 0) ? ++d : d; } /* * These values crop the animation, as we have a full 64x64 stored, * but we only want to display 40x24 (double width). */ int min_row = -1; int max_row = -1; int min_col = -1; int max_col = -1; /* * Actual width/height of terminal. */ int terminal_width = 80; int terminal_height = 24; /* * Flags to keep track of whether width/height were automatically set. */ char using_automatic_width = 0; char using_automatic_height = 0; /* * Print escape sequences to return cursor to visible mode * and exit the application. */ void finish() { if (clear_screen) { printf("\033[?25h\033[0m\033[H\033[2J"); } else { printf("\033[0m\n"); } exit(0); } /* * In the standalone mode, we want to handle an interrupt signal * (^C) so that we can restore the cursor and clear the terminal. */ void SIGINT_handler(int sig){ (void)sig; finish(); } /* * Handle the alarm which breaks us off of options * handling if we didn't receive a terminal */ void SIGALRM_handler(int sig) { (void)sig; alarm(0); longjmp(environment, 1); /* Unreachable */ } /* * Handle the loss of stdout, as would be the case when * in telnet mode and the client disconnects */ void SIGPIPE_handler(int sig) { (void)sig; finish(); } void SIGWINCH_handler(int sig) { (void)sig; struct winsize w; ioctl(0, TIOCGWINSZ, &w); terminal_width = w.ws_col; terminal_height = w.ws_row; if (using_automatic_width) { min_col = (FRAME_WIDTH - terminal_width/2) / 2; max_col = (FRAME_WIDTH + terminal_width/2) / 2; } if (using_automatic_height) { min_row = (FRAME_HEIGHT - (terminal_height-1)) / 2; max_row = (FRAME_HEIGHT + (terminal_height-1)) / 2; } signal(SIGWINCH, SIGWINCH_handler); } /* * Telnet requires us to send a specific sequence * for a line break (\r\000\n), so let's make it happy. */ void newline(int n) { int i = 0; for (i = 0; i < n; ++i) { /* We will send `n` linefeeds to the client */ if (telnet) { /* Send the telnet newline sequence */ putc('\r', stdout); putc(0, stdout); putc('\n', stdout); } else { /* Send a regular line feed */ putc('\n', stdout); } } } /* * These are the options we want to use as * a telnet server. These are set in set_options() */ unsigned char telnet_options[256] = { 0 }; unsigned char telnet_willack[256] = { 0 }; /* * These are the values we have set or * agreed to during our handshake. * These are set in send_command(...) */ unsigned char telnet_do_set[256] = { 0 }; unsigned char telnet_will_set[256]= { 0 }; /* * Set the default options for the telnet server. */ void set_options() { /* We will not echo input */ telnet_options[ECHO] = WONT; /* We will set graphics modes */ telnet_options[SGA] = WILL; /* We will not set new environments */ telnet_options[NEW_ENVIRON] = WONT; /* The client should echo its own input */ telnet_willack[ECHO] = DO; /* The client can set a graphics mode */ telnet_willack[SGA] = DO; /* The client should not change, but it should tell us its window size */ telnet_willack[NAWS] = DO; /* The client should tell us its terminal type (very important) */ telnet_willack[TTYPE] = DO; /* No linemode */ telnet_willack[LINEMODE] = DONT; /* And the client can set a new environment */ telnet_willack[NEW_ENVIRON] = DO; } /* * Send a command (cmd) to the telnet client * Also does special handling for DO/DONT/WILL/WONT */ void send_command(int cmd, int opt) { /* Send a command to the telnet client */ if (cmd == DO || cmd == DONT) { /* DO commands say what the client should do. */ if (((cmd == DO) && (telnet_do_set[opt] != DO)) || ((cmd == DONT) && (telnet_do_set[opt] != DONT))) { /* And we only send them if there is a disagreement */ telnet_do_set[opt] = cmd; printf("%c%c%c", IAC, cmd, opt); } } else if (cmd == WILL || cmd == WONT) { /* Similarly, WILL commands say what the server will do. */ if (((cmd == WILL) && (telnet_will_set[opt] != WILL)) || ((cmd == WONT) && (telnet_will_set[opt] != WONT))) { /* And we only send them during disagreements */ telnet_will_set[opt] = cmd; printf("%c%c%c", IAC, cmd, opt); } } else { /* Other commands are sent raw */ printf("%c%c", IAC, cmd); } } /* * Print the usage / help text describing options */ void usage(char * argv[]) { printf( "Terminal Nyancat\n" "\n" "usage: %s [-hitn] [-f \033[3mframes\033[0m]\n" "\n" " -i --intro \033[3mShow the introduction / about information at startup.\033[0m\n" " -t --telnet \033[3mTelnet mode.\033[0m\n" " -n --no-counter \033[3mDo not display the timer\033[0m\n" " -s --no-title \033[3mDo not set the titlebar text\033[0m\n" " -e --no-clear \033[3mDo not clear the display between frames\033[0m\n" " -d --delay \033[3mDelay image rendering by anywhere between 10ms and 1000ms\n" " -f --frames \033[3mDisplay the requested number of frames, then quit\033[0m\n" " -r --min-rows \033[3mCrop the animation from the top\033[0m\n" " -R --max-rows \033[3mCrop the animation from the bottom\033[0m\n" " -c --min-cols \033[3mCrop the animation from the left\033[0m\n" " -C --max-cols \033[3mCrop the animation from the right\033[0m\n" " -W --width \033[3mCrop the animation to the given width\033[0m\n" " -H --height \033[3mCrop the animation to the given height\033[0m\n" " -h --help \033[3mShow this help message.\033[0m\n", argv[0]); } int main(int argc, char ** argv) { char *term = NULL; unsigned int k; int ttype; #if 0 uint32_t option = 0, done = 0, sb_mode = 0; /* Various pieces for the telnet communication */ unsigned char sb[1024] = {0}; unsigned short sb_len = 0; #endif /* Whether or not to show the MOTD intro */ char show_intro = 0; char skip_intro = 0; /* Long option names */ static struct option long_opts[] = { {"help", no_argument, 0, 'h'}, {"telnet", no_argument, 0, 't'}, {"intro", no_argument, 0, 'i'}, {"skip-intro", no_argument, 0, 'I'}, {"no-counter", no_argument, 0, 'n'}, {"no-title", no_argument, 0, 's'}, {"no-clear", no_argument, 0, 'e'}, {"delay", required_argument, 0, 'd'}, {"frames", required_argument, 0, 'f'}, {"min-rows", required_argument, 0, 'r'}, {"max-rows", required_argument, 0, 'R'}, {"min-cols", required_argument, 0, 'c'}, {"max-cols", required_argument, 0, 'C'}, {"width", required_argument, 0, 'W'}, {"height", required_argument, 0, 'H'}, {0,0,0,0} }; /* Time delay in milliseconds */ int delay_ms = 90; // Default to original value /* Process arguments */ int index, c; while ((c = getopt_long(argc, argv, "eshiItnd:f:r:R:c:C:W:H:", long_opts, &index)) != -1) { if (!c) { if (long_opts[index].flag == 0) { c = long_opts[index].val; } } switch (c) { case 'e': clear_screen = 0; break; case 's': set_title = 0; break; case 'i': /* Show introduction */ show_intro = 1; break; case 'I': skip_intro = 1; break; case 't': /* Expect telnet bits */ telnet = 1; break; case 'h': /* Show help and exit */ usage(argv); exit(0); break; case 'n': show_counter = 0; break; case 'd': if (10 <= atoi(optarg) && atoi(optarg) <= 1000) delay_ms = atoi(optarg); break; case 'f': frame_count = atoi(optarg); break; case 'r': min_row = atoi(optarg); break; case 'R': max_row = atoi(optarg); break; case 'c': min_col = atoi(optarg); break; case 'C': max_col = atoi(optarg); break; case 'W': min_col = (FRAME_WIDTH - atoi(optarg)) / 2; max_col = (FRAME_WIDTH + atoi(optarg)) / 2; break; case 'H': min_row = (FRAME_HEIGHT - atoi(optarg)) / 2; max_row = (FRAME_HEIGHT + atoi(optarg)) / 2; break; default: break; } } (void)skip_intro; #if 0 if (telnet) { /* Telnet mode */ /* show_intro is implied unless skip_intro was set */ show_intro = (skip_intro == 0) ? 1 : 0; /* Set the default options */ set_options(); /* Let the client know what we're using */ for (option = 0; option < 256; option++) { if (telnet_options[option]) { send_command(telnet_options[option], option); fflush(stdout); } } for (option = 0; option < 256; option++) { if (telnet_willack[option]) { send_command(telnet_willack[option], option); fflush(stdout); } } /* Set the alarm handler to execute the longjmp */ signal(SIGALRM, SIGALRM_handler); /* Negotiate options */ if (!setjmp(environment)) { /* We will stop handling options after one second */ alarm(1); /* Let's do this */ while (!feof(stdin) && done < 2) { /* Get either IAC (start command) or a regular character (break, unless in SB mode) */ unsigned char i = getchar(); unsigned char opt = 0; if (i == IAC) { /* If IAC, get the command */ i = getchar(); switch (i) { case SE: /* End of extended option mode */ sb_mode = 0; if (sb[0] == TTYPE) { /* This was a response to the TTYPE command, meaning * that this should be a terminal type */ alarm(2); term = strndup((char *)&sb[2], sizeof(sb)-2); done++; } else if (sb[0] == NAWS) { /* This was a response to the NAWS command, meaning * that this should be a window size */ alarm(2); terminal_width = (sb[1] << 8) | sb[2]; terminal_height = (sb[3] << 8) | sb[4]; done++; } break; case NOP: /* No Op */ send_command(NOP, 0); fflush(stdout); break; case WILL: case WONT: /* Will / Won't Negotiation */ opt = getchar(); if (!telnet_willack[opt]) { /* We default to WONT */ telnet_willack[opt] = WONT; } send_command(telnet_willack[opt], opt); fflush(stdout); if ((i == WILL) && (opt == TTYPE)) { /* WILL TTYPE? Great, let's do that now! */ printf("%c%c%c%c%c%c", IAC, SB, TTYPE, SEND, IAC, SE); fflush(stdout); } break; case DO: case DONT: /* Do / Don't Negotiation */ opt = getchar(); if (!telnet_options[opt]) { /* We default to DONT */ telnet_options[opt] = DONT; } send_command(telnet_options[opt], opt); fflush(stdout); break; case SB: /* Begin Extended Option Mode */ sb_mode = 1; sb_len = 0; memset(sb, 0, sizeof(sb)); break; case IAC: /* IAC IAC? That's probably not right. */ done = 2; break; default: break; } } else if (sb_mode) { /* Extended Option Mode -> Accept character */ if (sb_len < sizeof(sb) - 1) { /* Append this character to the SB string, * but only if it doesn't put us over * our limit; honestly, we shouldn't hit * the limit, as we're only collecting characters * for a terminal type or window size, but better safe than * sorry (and vulnerable). */ sb[sb_len] = i; sb_len++; } } } } alarm(0); } else #endif { /* We are running standalone, retrieve the * terminal type from the environment. */ term = getenv("TERM"); /* Also get the number of columns */ struct winsize w; ioctl(0, TIOCGWINSZ, &w); terminal_width = w.ws_col; terminal_height = w.ws_row; } /* Default ttype */ ttype = 2; if (term) { /* Convert the entire terminal string to lower case */ for (k = 0; k < strlen(term); ++k) { term[k] = tolower(term[k]); } /* Do our terminal detection */ if (strstr(term, "xterm")) { ttype = 1; /* 256-color, spaces */ } else if (strstr(term, "toaru")) { ttype = 1; /* emulates xterm */ } else if (strstr(term, "linux")) { ttype = 3; /* Spaces and blink attribute */ } else if (strstr(term, "vtnt")) { ttype = 5; /* Extended ASCII fallback == Windows */ } else if (strstr(term, "cygwin")) { ttype = 5; /* Extended ASCII fallback == Windows */ } else if (strstr(term, "vt220")) { ttype = 6; /* No color support */ } else if (strstr(term, "fallback")) { ttype = 4; /* Unicode fallback */ } else if (strstr(term, "rxvt-256color")) { ttype = 1; /* xterm 256-color compatible */ } else if (strstr(term, "rxvt")) { ttype = 3; /* Accepts LINUX mode */ } else if (strstr(term, "vt100") && terminal_width == 40) { ttype = 7; /* No color support, only 40 columns */ } else if (!strncmp(term, "st", 2)) { ttype = 1; /* suckless simple terminal is xterm-256color-compatible */ } else if (!strncmp(term, "truecolor", 9)) { ttype = 8; } } int always_escape = 0; /* Used for text mode */ /* Accept ^C -> restore cursor */ signal(SIGINT, SIGINT_handler); /* Handle loss of stdout */ signal(SIGPIPE, SIGPIPE_handler); /* Handle window changes */ if (!telnet) { signal(SIGWINCH, SIGWINCH_handler); } switch (ttype) { case 1: colors[','] = "\033[48;5;17m"; /* Blue background */ colors['.'] = "\033[48;5;231m"; /* White stars */ colors['\''] = "\033[48;5;16m"; /* Black border */ colors['@'] = "\033[48;5;230m"; /* Tan poptart */ colors['$'] = "\033[48;5;175m"; /* Pink poptart */ colors['-'] = "\033[48;5;162m"; /* Red poptart */ colors['>'] = "\033[48;5;196m"; /* Red rainbow */ colors['&'] = "\033[48;5;214m"; /* Orange rainbow */ colors['+'] = "\033[48;5;226m"; /* Yellow Rainbow */ colors['#'] = "\033[48;5;118m"; /* Green rainbow */ colors['='] = "\033[48;5;33m"; /* Light blue rainbow */ colors[';'] = "\033[48;5;19m"; /* Dark blue rainbow */ colors['*'] = "\033[48;5;240m"; /* Gray cat face */ colors['%'] = "\033[48;5;175m"; /* Pink cheeks */ break; case 2: colors[','] = "\033[104m"; /* Blue background */ colors['.'] = "\033[107m"; /* White stars */ colors['\''] = "\033[40m"; /* Black border */ colors['@'] = "\033[47m"; /* Tan poptart */ colors['$'] = "\033[105m"; /* Pink poptart */ colors['-'] = "\033[101m"; /* Red poptart */ colors['>'] = "\033[101m"; /* Red rainbow */ colors['&'] = "\033[43m"; /* Orange rainbow */ colors['+'] = "\033[103m"; /* Yellow Rainbow */ colors['#'] = "\033[102m"; /* Green rainbow */ colors['='] = "\033[104m"; /* Light blue rainbow */ colors[';'] = "\033[44m"; /* Dark blue rainbow */ colors['*'] = "\033[100m"; /* Gray cat face */ colors['%'] = "\033[105m"; /* Pink cheeks */ break; case 3: colors[','] = "\033[25;44m"; /* Blue background */ colors['.'] = "\033[5;47m"; /* White stars */ colors['\''] = "\033[25;40m"; /* Black border */ colors['@'] = "\033[5;47m"; /* Tan poptart */ colors['$'] = "\033[5;45m"; /* Pink poptart */ colors['-'] = "\033[5;41m"; /* Red poptart */ colors['>'] = "\033[5;41m"; /* Red rainbow */ colors['&'] = "\033[25;43m"; /* Orange rainbow */ colors['+'] = "\033[5;43m"; /* Yellow Rainbow */ colors['#'] = "\033[5;42m"; /* Green rainbow */ colors['='] = "\033[25;44m"; /* Light blue rainbow */ colors[';'] = "\033[5;44m"; /* Dark blue rainbow */ colors['*'] = "\033[5;40m"; /* Gray cat face */ colors['%'] = "\033[5;45m"; /* Pink cheeks */ break; case 4: colors[','] = "\033[0;34;44m"; /* Blue background */ colors['.'] = "\033[1;37;47m"; /* White stars */ colors['\''] = "\033[0;30;40m"; /* Black border */ colors['@'] = "\033[1;37;47m"; /* Tan poptart */ colors['$'] = "\033[1;35;45m"; /* Pink poptart */ colors['-'] = "\033[1;31;41m"; /* Red poptart */ colors['>'] = "\033[1;31;41m"; /* Red rainbow */ colors['&'] = "\033[0;33;43m"; /* Orange rainbow */ colors['+'] = "\033[1;33;43m"; /* Yellow Rainbow */ colors['#'] = "\033[1;32;42m"; /* Green rainbow */ colors['='] = "\033[1;34;44m"; /* Light blue rainbow */ colors[';'] = "\033[0;34;44m"; /* Dark blue rainbow */ colors['*'] = "\033[1;30;40m"; /* Gray cat face */ colors['%'] = "\033[1;35;45m"; /* Pink cheeks */ output = "██"; break; case 5: colors[','] = "\033[0;34;44m"; /* Blue background */ colors['.'] = "\033[1;37;47m"; /* White stars */ colors['\''] = "\033[0;30;40m"; /* Black border */ colors['@'] = "\033[1;37;47m"; /* Tan poptart */ colors['$'] = "\033[1;35;45m"; /* Pink poptart */ colors['-'] = "\033[1;31;41m"; /* Red poptart */ colors['>'] = "\033[1;31;41m"; /* Red rainbow */ colors['&'] = "\033[0;33;43m"; /* Orange rainbow */ colors['+'] = "\033[1;33;43m"; /* Yellow Rainbow */ colors['#'] = "\033[1;32;42m"; /* Green rainbow */ colors['='] = "\033[1;34;44m"; /* Light blue rainbow */ colors[';'] = "\033[0;34;44m"; /* Dark blue rainbow */ colors['*'] = "\033[1;30;40m"; /* Gray cat face */ colors['%'] = "\033[1;35;45m"; /* Pink cheeks */ output = "\333\333"; break; case 6: colors[','] = "::"; /* Blue background */ colors['.'] = "@@"; /* White stars */ colors['\''] = " "; /* Black border */ colors['@'] = "##"; /* Tan poptart */ colors['$'] = "??"; /* Pink poptart */ colors['-'] = "<>"; /* Red poptart */ colors['>'] = "##"; /* Red rainbow */ colors['&'] = "=="; /* Orange rainbow */ colors['+'] = "--"; /* Yellow Rainbow */ colors['#'] = "++"; /* Green rainbow */ colors['='] = "~~"; /* Light blue rainbow */ colors[';'] = "$$"; /* Dark blue rainbow */ colors['*'] = ";;"; /* Gray cat face */ colors['%'] = "()"; /* Pink cheeks */ always_escape = 1; break; case 7: colors[','] = "."; /* Blue background */ colors['.'] = "@"; /* White stars */ colors['\''] = " "; /* Black border */ colors['@'] = "#"; /* Tan poptart */ colors['$'] = "?"; /* Pink poptart */ colors['-'] = "O"; /* Red poptart */ colors['>'] = "#"; /* Red rainbow */ colors['&'] = "="; /* Orange rainbow */ colors['+'] = "-"; /* Yellow Rainbow */ colors['#'] = "+"; /* Green rainbow */ colors['='] = "~"; /* Light blue rainbow */ colors[';'] = "$"; /* Dark blue rainbow */ colors['*'] = ";"; /* Gray cat face */ colors['%'] = "o"; /* Pink cheeks */ always_escape = 1; terminal_width = 40; break; case 8: colors[','] = "\033[48;2;0;49;105m"; /* Blue background */ colors['.'] = "\033[48;2;255;255;255m"; /* White stars */ colors['\''] = "\033[48;2;0;0;0m"; /* Black border */ colors['@'] = "\033[48;2;255;205;152m"; /* Tan poptart */ colors['$'] = "\033[48;2;255;169;255m"; /* Pink poptart */ colors['-'] = "\033[48;2;255;76;152m"; /* Red poptart */ colors['>'] = "\033[48;2;255;25;0m"; /* Red rainbow */ colors['&'] = "\033[48;2;255;154;0m"; /* Orange rainbow */ colors['+'] = "\033[48;2;255;240;0m"; /* Yellow Rainbow */ colors['#'] = "\033[48;2;40;220;0m"; /* Green rainbow */ colors['='] = "\033[48;2;0;144;255m"; /* Light blue rainbow */ colors[';'] = "\033[48;2;104;68;255m"; /* Dark blue rainbow */ colors['*'] = "\033[48;2;153;153;153m"; /* Gray cat face */ colors['%'] = "\033[48;2;255;163;152m"; /* Pink cheeks */ break; default: break; } if (min_col == max_col) { min_col = (FRAME_WIDTH - terminal_width/2) / 2; max_col = (FRAME_WIDTH + terminal_width/2) / 2; using_automatic_width = 1; } if (min_row == max_row) { min_row = (FRAME_HEIGHT - (terminal_height-1)) / 2; max_row = (FRAME_HEIGHT + (terminal_height-1)) / 2; using_automatic_height = 1; } /* Attempt to set terminal title */ if (set_title) { printf("\033kNyanyanyanyanyanyanya...\033\134"); printf("\033]1;Nyanyanyanyanyanyanya...\007"); printf("\033]2;Nyanyanyanyanyanyanya...\007"); } if (clear_screen) { /* Clear the screen */ printf("\033[H\033[2J\033[?25l"); } else { printf("\033[s"); } if (show_intro) { /* Display the MOTD */ unsigned int countdown_clock = 5; for (k = 0; k < countdown_clock; ++k) { newline(3); printf(" \033[1mNyancat Telnet Server\033[0m"); newline(2); printf(" written and run by \033[1;32mK. Lange\033[1;34m @_klange\033[0m"); newline(2); printf(" If things don't look right, try:"); newline(1); printf(" TERM=fallback telnet ..."); newline(2); printf(" Or on Windows:"); newline(1); printf(" telnet -t vtnt ..."); newline(2); printf(" Problems? Check the website:"); newline(1); printf(" \033[1;34mhttp://nyancat.dakko.us\033[0m"); newline(2); printf(" This is a telnet server, remember your escape keys!"); newline(1); printf(" \033[1;31m^]quit\033[0m to exit"); newline(2); printf(" Starting in %d... \n", countdown_clock-k); fflush(stdout); usleep(400000); if (clear_screen) { printf("\033[H"); /* Reset cursor */ } else { printf("\033[u"); } } if (clear_screen) { /* Clear the screen again */ printf("\033[H\033[2J\033[?25l"); } } /* Store the start time */ time_t start, current; time(&start); int playing = 1; /* Animation should continue [left here for modifications] */ size_t i = 0; /* Current frame # */ unsigned int f = 0; /* Total frames passed */ char last = 0; /* Last color index rendered */ int y, x; /* x/y coordinates of what we're drawing */ while (playing) { /* Reset cursor */ if (clear_screen) { printf("\033[H"); } else { printf("\033[u"); } /* Render the frame */ for (y = min_row; y < max_row; ++y) { for (x = min_col; x < max_col; ++x) { char color; if (y > 23 && y < 43 && x < 0) { /* * Generate the rainbow tail. * * This is done with a pretty simplistic square wave. */ int mod_x = ((-x+2) % 16) / 8; if ((i / 2) % 2) { mod_x = 1 - mod_x; } /* * Our rainbow, with some padding. */ const char *rainbow = ",,>>&&&+++###==;;;,,"; color = rainbow[mod_x + y-23]; if (color == 0) color = ','; } else if (x < 0 || y < 0 || y >= FRAME_HEIGHT || x >= FRAME_WIDTH) { /* Fill all other areas with background */ color = ','; } else { /* Otherwise, get the color from the animation frame. */ color = frames[i][y][x]; } if (always_escape) { /* Text mode (or "Always Send Color Escapes") */ printf("%s", colors[(int)color]); } else { if (color != last && colors[(int)color]) { /* Normal Mode, send escape (because the color changed) */ last = color; printf("%s%s", colors[(int)color], output); } else { /* Same color, just send the output characters */ printf("%s", output); } } } /* End of row, send newline */ newline(1); } if (show_counter) { /* Get the current time for the "You have nyaned..." string */ time(¤t); double diff = difftime(current, start); /* Now count the length of the time difference so we can center */ int nLen = digits((int)diff); /* * 29 = the length of the rest of the string; * XXX: Replace this was actually checking the written bytes from a * call to sprintf or something */ int width = (terminal_width - 29 - nLen) / 2; /* Spit out some spaces so that we're actually centered */ while (width > 0) { printf(" "); width--; } /* You have nyaned for [n] seconds! * The \033[J ensures that the rest of the line has the dark blue * background, and the \033[1;37m ensures that our text is bright white. * The \033[0m prevents the Apple ][ from flipping everything, but * makes the whole nyancat less bright on the vt220 */ printf("\033[1;37mYou have nyaned for %d seconds!\033[J\033[0m", (int)diff); } /* Reset the last color so that the escape sequences rewrite */ last = 0; /* Update frame count */ ++f; if (frame_count != 0 && f == frame_count) { finish(); } ++i; if (!frames[i]) { /* Loop animation */ i = 0; } /* Wait */ usleep(1000 * delay_ms); } return 0; } ================================================ FILE: apps/package-manager.c ================================================ /** * @brief Graphical interface to msk * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define APPLICATION_TITLE "Package Manager" #define SCROLL_AMOUNT 120 #define VAR_PATH "/var/msk" static yutani_t * yctx; static yutani_window_t * main_window; static gfx_context_t * ctx; static int application_running = 1; static gfx_context_t * contents = NULL; static sprite_t * contents_sprite = NULL; static int available_height = 0; /* How much space is available in the main window for the icon view */ static int scroll_offset = 0; /* How far the icon view should be scrolled */ static int hilighted_offset = -1; /* Which file is hovered by the mouse */ static uint64_t last_click = 0; /* For double click */ static int last_click_offset = -1; /* So that clicking two different things quickly doesn't count as a double click */ static int installation_done = 0; static int currently_installing = 0; static pthread_t _waiter_thread; struct TT_Font * tt_font_thin = NULL; struct TT_Font * tt_font_bold = NULL; struct Package { char name[256]; char friendly_name[256]; char description[1024]; char version[256]; /* Really doesn't need to be that long */ int selected; int installed; }; static struct Package ** pkg_pointers = NULL; /* List of package pointers */ static ssize_t pkg_pointers_len = 0; /* How many packages are in the current list */ static struct menu_bar menu_bar = {0}; static struct menu_bar_entries menu_entries[] = { {"File", "file"}, {"Index", "index"}, {"Help", "help"}, {NULL, NULL}, }; static void _menu_action_exit(struct MenuEntry * entry) { application_running = 0; } /** * Accurate time comparison. * * These methods were taken from the compositor and * allow us to time double-clicks accurately. */ static uint64_t precise_current_time(void) { struct timeval t; gettimeofday(&t, NULL); time_t sec_diff = t.tv_sec; suseconds_t usec_diff = t.tv_usec; return (uint64_t)((uint64_t)sec_diff * 1000LL + usec_diff / 1000); } static uint64_t precise_time_since(uint64_t start_time) { uint64_t now = precise_current_time(); uint64_t diff = now - start_time; /* Milliseconds */ return diff; } static int _close_enough(struct yutani_msg_window_mouse_event * me) { if (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) { return 1; } return 0; } #define BUTTON_HEIGHT 28 #define BUTTON_WIDTH 86 #define BUTTON_PADDING 14 #define HILIGHT_BORDER_TOP rgb(54,128,205) #define HILIGHT_GRADIENT_TOP rgb(93,163,236) #define HILIGHT_GRADIENT_BOTTOM rgb(56,137,220) #define HILIGHT_BORDER_BOTTOM rgb(47,106,167) #define PKG_HEIGHT 70 static void draw_package(struct Package * package, int index) { int offset_y = index * PKG_HEIGHT; if (package->selected) { if (main_window->focused) { draw_rectangle_solid(contents, 0, offset_y, contents->width, PKG_HEIGHT, rgb(93,163,236)); draw_line(contents, 0, contents->width, offset_y, offset_y, HILIGHT_BORDER_TOP); draw_line(contents, 0, contents->width, offset_y + PKG_HEIGHT-1, offset_y + PKG_HEIGHT-1, HILIGHT_BORDER_BOTTOM); for (int i = 1; i < PKG_HEIGHT - 2; ++i) { int thing = ((i - 1) * 256) / (PKG_HEIGHT - 3); if (thing > 255) thing = 255; if (thing < 0) thing = 0; uint32_t c = interp_colors(HILIGHT_GRADIENT_TOP, HILIGHT_GRADIENT_BOTTOM, thing); draw_line(contents, 0, contents->width, offset_y + i, offset_y + i, c); } } else { draw_rectangle_solid(contents, 0, offset_y, contents->width, PKG_HEIGHT, rgb(180,180,180)); } } sprite_t * icon = package->installed ? icon_get_48("package") : icon_get_48("package-uninstalled"); draw_sprite(contents, icon, 8, offset_y + 11); uint32_t text_color = package->selected ? rgb(255,255,255) : rgb(0,0,0); char tmp[2048]; sprintf(tmp, "%s - %s", package->friendly_name, package->version); tt_set_size(tt_font_bold, 18); tt_draw_string(contents, tt_font_bold, 64, offset_y + 4 + 18, tmp, text_color); sprintf(tmp, "%s - %s", package->name, package->description); tt_set_size(tt_font_thin, 13); int x = tt_draw_string(contents, tt_font_thin, 65, offset_y + 24 + 13, package->name, rgb(150,150,150)); tt_draw_string(contents, tt_font_thin, 64 + x + 4, offset_y + 24 + 13, package->description, text_color); } static void redraw_packages(void) { draw_fill(contents, rgba(0,0,0,0)); for (int i = 0; i < pkg_pointers_len; ++i) { draw_package(pkg_pointers[i], i); } } static void load_manifest(void) { if (pkg_pointers) { for (int i = 0; i < pkg_pointers_len; ++i) { free(pkg_pointers[i]); } free(pkg_pointers); pkg_pointers_len = 0; } confreader_t * conf = confreader_load(VAR_PATH "/manifest"); if (conf) { hashmap_t * msk_installed = hashmap_create(10); FILE * installed = fopen(VAR_PATH "/installed", "r"); if (installed) { while (!feof(installed)) { char tmp[128] = {0}; if (!fgets(tmp, 128, installed)) break; char * nl = strstr(tmp, "\n"); if (nl) *nl = '\0'; char * eqeq = strstr(tmp, "=="); if (!eqeq) { /* show error */ break; } *eqeq = '\0'; char * version = eqeq+2; hashmap_set(msk_installed, tmp, strdup(version)); } } list_t * package_list = list_create(); /* Go through sections */ list_t * packages = hashmap_keys(conf->sections); foreach(node, packages) { char * name = node->value; if (!strlen(name)) continue; /* skip empty section */ char * desc = confreader_get(conf, name, "description"); char * version = confreader_get(conf, name, "version"); char * friendly_name = confreader_get(conf, name, "friendly-name"); struct Package * p = malloc(sizeof(struct Package)); sprintf(p->name, name); sprintf(p->friendly_name, friendly_name); sprintf(p->description, desc); sprintf(p->version, version); p->selected = 0; p->installed = hashmap_has(msk_installed, name); list_insert(package_list, p); } list_free(packages); free(packages); hashmap_free(msk_installed); free(msk_installed); pkg_pointers = malloc(sizeof(struct Package *) * package_list->length); pkg_pointers_len = package_list->length; int i = 0; foreach (node, package_list) { pkg_pointers[i] = node->value; i++; } list_free(package_list); free(package_list); int comparator(const void * c1, const void * c2) { const struct Package * f1 = *(const struct Package **)(c1); const struct Package * f2 = *(const struct Package **)(c2); return strcmp(f1->name, f2->name); } qsort(pkg_pointers, pkg_pointers_len, sizeof(struct Package *), comparator); } if (!pkg_pointers_len) { system("showdialog 'Package Manager' '/usr/share/icons/48/package.png' 'No packages are available.' &"); } } static struct Package * get_package_at_offset(int offset) { if (offset >= 0 && offset < pkg_pointers_len) { return pkg_pointers[offset]; } return NULL; } static void clear_offset(int offset) { draw_rectangle_solid(contents, 0, offset * PKG_HEIGHT, contents->width, PKG_HEIGHT, rgba(0,0,0,0)); } static void reinitialize_contents(void) { if (contents) { free(contents); } if (contents_sprite) { sprite_free(contents_sprite); } /* Calculate height for current directory */ int calculated_height = pkg_pointers_len * PKG_HEIGHT; struct decor_bounds bounds; decor_get_bounds(main_window, &bounds); contents_sprite = create_sprite(main_window->width - bounds.width, calculated_height, ALPHA_EMBEDDED); contents = init_graphics_sprite(contents_sprite); /* Draw packages */ redraw_packages(); } static void redraw_window(void) { draw_fill(ctx, rgb(255,255,255)); render_decorations(main_window, ctx, APPLICATION_TITLE); struct decor_bounds bounds; decor_get_bounds(main_window, &bounds); menu_bar.x = bounds.left_width; menu_bar.y = bounds.top_height; menu_bar.width = ctx->width - bounds.width; menu_bar.window = main_window; menu_bar_render(&menu_bar, ctx); gfx_clear_clip(ctx); gfx_add_clip(ctx, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT, ctx->width - bounds.width, ctx->height - MENU_BAR_HEIGHT - bounds.height); draw_sprite(ctx, contents_sprite, bounds.left_width, bounds.top_height + MENU_BAR_HEIGHT - scroll_offset); gfx_clear_clip(ctx); gfx_add_clip(ctx, 0, 0, ctx->width, ctx->height); flip(ctx); yutani_flip(yctx, main_window); } static void resize_finish(int w, int h) { int width_changed = (main_window->width != (unsigned int)w); yutani_window_resize_accept(yctx, main_window, w, h); reinit_graphics_yutani(ctx, main_window); struct decor_bounds bounds; decor_get_bounds(main_window, &bounds); /* Recalculate available size */ available_height = ctx->height - MENU_BAR_HEIGHT - bounds.height; /* If the width changed, we need to rebuild the icon view */ if (width_changed) { reinitialize_contents(); } /* Make sure we're not scrolled weirdly after resizing */ if (available_height > contents->height) { scroll_offset = 0; } else { if (scroll_offset > contents->height - available_height) { scroll_offset = contents->height - available_height; } } /* Redraw */ redraw_window(); yutani_window_resize_done(yctx, main_window); yutani_flip(yctx, main_window); } static void _menu_action_refresh(struct MenuEntry * entry) { /* refresh msk manifest */ system("terminal msk update"); load_manifest(); reinitialize_contents(); redraw_window(); } static void * package_installer_thread(void * arg) { char * packages = arg; putenv("MSK_YES=1"); char tmp[1024]; sprintf(tmp, "terminal msk install %s", packages); free(packages); system(tmp); installation_done = 1; return NULL; } static void install_packages(void) { if (currently_installing) return; /* Figure out what packages to install */ size_t c_len = 0; for (int i = 0; i selected) { c_len += strlen(pkg_pointers[i]->name) + 2; } } if (!c_len) return; /* Nothing selected? */ char * packages = malloc(c_len); char * cursor = packages; for (int i = 0; i selected) { cursor += sprintf(cursor, "%s ", pkg_pointers[i]->name); } } installation_done = 0; currently_installing = 1; pthread_create(&_waiter_thread, NULL, package_installer_thread, packages); } static void _menu_action_about(struct MenuEntry * entry) { /* Show About dialog */ char about_cmd[1024] = "\0"; strcat(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\" "); char coords[100]; sprintf(coords, "%d %d &", (int)main_window->x + (int)main_window->width / 2, (int)main_window->y + (int)main_window->height / 2); strcat(about_cmd, coords); system(about_cmd); redraw_window(); } static void _menu_action_help(struct MenuEntry * entry) { /* show help documentation */ system("help-browser package-manager.trt &"); redraw_window(); } static void toggle_selected(int hilighted_offset, int modifiers) { struct Package * f = get_package_at_offset(hilighted_offset); /* No file at this offset, do nothing. */ if (!f) return; /* Toggle selection of the current file */ f->selected = !f->selected; /* If Ctrl wasn't held, unselect everything else. */ if (!(modifiers & KEY_MOD_LEFT_CTRL)) { for (int i = 0; i < pkg_pointers_len; ++i) { if (pkg_pointers[i] != f && pkg_pointers[i]->selected) { pkg_pointers[i]->selected = 0; clear_offset(i); draw_package(pkg_pointers[i], i); } } } /* Redraw the pkg */ clear_offset(hilighted_offset); draw_package(f, hilighted_offset); /* And repaint the window */ redraw_window(); } static void _scroll_up(void) { scroll_offset -= SCROLL_AMOUNT; if (scroll_offset < 0) { scroll_offset = 0; } } static void _scroll_down(void) { if (available_height > contents->height) { scroll_offset = 0; } else { scroll_offset += SCROLL_AMOUNT; if (scroll_offset > contents->height - available_height) { scroll_offset = contents->height - available_height; } } } static void arrow_select(int y) { if (!pkg_pointers_len) return; /* Find first selected */ int selected = -1; for (int i = 0; i selected) { selected = i; pkg_pointers[i]->selected = 0; clear_offset(i); draw_package(pkg_pointers[i], i); } } if (selected == -1) { selected = 0; } else { selected += y; if (selected >= pkg_pointers_len) selected = pkg_pointers_len - 1; if (selected < 0) selected = 0; } if (selected * PKG_HEIGHT < scroll_offset) { scroll_offset = selected * PKG_HEIGHT; } if (selected * PKG_HEIGHT + PKG_HEIGHT > scroll_offset + available_height) { scroll_offset = selected * PKG_HEIGHT + PKG_HEIGHT - available_height; } pkg_pointers[selected]->selected = 1; clear_offset(selected); draw_package(pkg_pointers[selected], selected); redraw_window(); } static void redraw_window_callback(struct menu_bar * self) { (void)self; redraw_window(); } int main(int argc, char * argv[]) { if (geteuid() != 0) { char * args[] = { "showdialog", "Package Manager", "/usr/share/icons/48/package.png", "Only root can manage packages.", NULL, }; return execvp("showdialog", args); } yctx = yutani_init(); init_decorations(); main_window = yutani_window_create(yctx, 640, 480); yutani_window_move(yctx, main_window, yctx->display_width / 2 - main_window->width / 2, yctx->display_height / 2 - main_window->height / 2); ctx = init_graphics_yutani_double_buffer(main_window); tt_font_thin = tt_font_from_shm("sans-serif"); tt_font_bold = tt_font_from_shm("sans-serif.bold"); yutani_window_advertise_icon(yctx, main_window, APPLICATION_TITLE, "package"); menu_bar.entries = menu_entries; menu_bar.redraw_callback = redraw_window_callback; menu_bar.set = menu_set_create(); struct MenuList * m = menu_create(); /* File */ menu_insert(m, menu_create_normal("exit",NULL,"Exit", _menu_action_exit)); menu_set_insert(menu_bar.set, "file", m); m = menu_create(); /* Go */ menu_insert(m, menu_create_normal("refresh",NULL,"Refresh",_menu_action_refresh)); menu_set_insert(menu_bar.set, "index", m); m = menu_create(); menu_insert(m, menu_create_normal("help","help_browser.trt","Contents",_menu_action_help)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal("star",NULL,"About " APPLICATION_TITLE,_menu_action_about)); menu_set_insert(menu_bar.set, "help", m); struct decor_bounds bounds; decor_get_bounds(main_window, &bounds); available_height = ctx->height - MENU_BAR_HEIGHT - bounds.height; struct stat statbuf; if (!stat(VAR_PATH "/manifest",&statbuf)) { load_manifest(); reinitialize_contents(); redraw_window(); } else { _menu_action_refresh(NULL); /* also redraws window */ } int fds[1] = {fileno(yctx->sock)}; while (application_running) { int index = fswait2(1,fds,200); /* Were we waiting for a package to install? */ if (currently_installing) { if (installation_done) { currently_installing = 0; installation_done = 0; load_manifest(); reinitialize_contents(); redraw_window(); } } if (index != 0) continue; yutani_msg_t * m = yutani_poll(yctx); while (m) { if (menu_process_event(yctx, m)) { redraw_window(); } 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->wid == main_window->wid) { switch (ke->event.keycode) { case KEY_PAGE_UP: _scroll_up(); redraw_window(); break; case KEY_PAGE_DOWN: _scroll_down(); redraw_window(); break; case KEY_ARROW_DOWN: arrow_select(1); break; case KEY_ARROW_UP: arrow_select(-1); break; case '\n': install_packages(); break; case 'f': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[0]); } break; case 'i': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[1]); } break; case 'h': if (ke->event.modifiers & YUTANI_KEY_MODIFIER_ALT) { menu_bar_show_menu(yctx,main_window,&menu_bar,-1,&menu_entries[2]); } break; case 'q': _menu_action_exit(NULL); break; default: break; } } } 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*)(uintptr_t)wf->wid); if (win == main_window) { win->focused = wf->focused; redraw_packages(); redraw_window(); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; if (wr->wid == main_window->wid) { resize_finish(wr->width, wr->height); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; yutani_window_t * win = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid); if (win == main_window) { int result = decor_handle_event(yctx, m); switch (result) { case DECOR_CLOSE: _menu_action_exit(NULL); break; case DECOR_RIGHT: /* right click in decoration, show appropriate menu */ decor_show_default_menu(main_window, main_window->x + me->new_x, main_window->y + me->new_y); break; default: /* Other actions */ break; } /* Menu bar */ menu_bar_mouse_event(yctx, main_window, &menu_bar, me, me->new_x, me->new_y); if (me->new_y > (int)(bounds.top_height + MENU_BAR_HEIGHT) && me->new_y < (int)(main_window->height - bounds.bottom_height) && me->new_x > (int)(bounds.left_width) && me->new_x < (int)(main_window->width - bounds.right_width)) { if (me->buttons & YUTANI_MOUSE_SCROLL_UP) { _scroll_up(); redraw_window(); } else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) { _scroll_down(); redraw_window(); } /* Get offset into contents */ int y_into = me->new_y - bounds.top_height - MENU_BAR_HEIGHT + scroll_offset; int offset = (y_into / PKG_HEIGHT); if (offset != hilighted_offset) { int old_offset = hilighted_offset; hilighted_offset = offset; if (old_offset != -1) { clear_offset(old_offset); struct Package * f = get_package_at_offset(old_offset); if (f) { clear_offset(old_offset); draw_package(f, old_offset); } } struct Package * f = get_package_at_offset(hilighted_offset); if (f) { clear_offset(hilighted_offset); draw_package(f, hilighted_offset); } redraw_window(); } if (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) { struct Package * f = get_package_at_offset(hilighted_offset); if (f) { if (last_click_offset == hilighted_offset && precise_time_since(last_click) < 400) { install_packages(); last_click = 0; } else { last_click = precise_current_time(); last_click_offset = hilighted_offset; toggle_selected(hilighted_offset, me->modifiers); } } else { if (!(me->modifiers & YUTANI_KEY_MODIFIER_CTRL)) { for (int i = 0; i < pkg_pointers_len; ++i) { if (pkg_pointers[i]->selected) { pkg_pointers[i]->selected = 0; clear_offset(i); draw_package(pkg_pointers[i], i); } } redraw_window(); } } } else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) { #if 0 if (!context_menu->window) { struct Package * f = get_package_at_offset(hilighted_offset); if (f && !f->selected) { toggle_selected(hilighted_offset, me->modifiers); } menu_show(context_menu, main_window->ctx); yutani_window_move(main_window->ctx, context_menu->window, me->new_x + main_window->x, me->new_y + main_window->y); } #endif } } else { int old_offset = hilighted_offset; hilighted_offset = -1; if (old_offset != -1) { clear_offset(old_offset); struct Package * f = get_package_at_offset(old_offset); if (f) { clear_offset(old_offset); draw_package(f, old_offset); } redraw_window(); } } } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: _menu_action_exit(NULL); break; default: break; } free(m); m = yutani_poll_async(yctx); } } } ================================================ FILE: apps/panel.c ================================================ /** * @file apps/panel.c * @brief Panel with widgets. Main desktop interface. * * Provides the panel shown at the top of the screen, which * presents application windows, useful widgets, and a menu * for launching new apps. * * Also provides Alt-Tab app switching and a few other goodies. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include /* auto-dep: export-dynamic */ #include #include #include #include #include #include #include #include /* Several theming defines are in here */ #include /* These are local to the core panel, so we don't need to put them in the header */ #define ALTTAB_WIDTH 250 #define ALTTAB_HEIGHT 200 #define ALTTAB_BACKGROUND premultiply(rgba(0,0,0,150)) #define ALTTAB_OFFSET 10 #define ALTTAB_WIN_SIZE 140 #define ALTF2_WIDTH 400 #define ALTF2_HEIGHT 200 /* How many windows we can support in the advertisement lift before truncating it */ #define MAX_WINDOW_COUNT 100 /* Height of the panel window */ #define PANEL_HEIGHT 27 /* How far down dropdown menus should be shown */ #define DROPDOWN_OFFSET PANEL_HEIGHT /* How much padding should be assured on the left and right of the screen for menus */ #define MENU_PAD 4 static struct PanelContext panel_context; static gfx_context_t * ctx = NULL; static yutani_window_t * panel = NULL; static gfx_context_t * actx = NULL; static yutani_window_t * alttab = NULL; static gfx_context_t * a2ctx = NULL; static yutani_window_t * alt_f2 = NULL; static size_t bg_size; static char * bg_blob; /* External interface for widgets */ list_t * window_list = NULL; yutani_t * yctx; int width; int height; list_t * widgets_enabled = NULL; /* Windows, indexed by z-order */ struct window_ad * ads_by_z[MAX_WINDOW_COUNT+1] = {NULL}; int active_window = -1; static int was_tabbing = 0; static int new_focused = -1; static void widgets_layout(void); static int _close_enough(struct yutani_msg_window_mouse_event * me) { if (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) { return 1; } return 0; } static int center_x(int x) { return (width - x) / 2; } static int center_y(int y) { return (height - y) / 2; } static int center_x_a(int x) { return (alttab->width - x) / 2; } static int center_x_a2(int x) { return (ALTF2_WIDTH - x) / 2; } static volatile int _continue = 1; static void toggle_hide_panel(void) { static int panel_hidden = 0; if (panel_hidden) { /* Unhide the panel */ for (int i = PANEL_HEIGHT-1; i >= 0; i--) { yutani_window_move(yctx, panel, 0, -i); usleep(3000); } panel_hidden = 0; } else { /* Hide the panel */ for (int i = 1; i <= PANEL_HEIGHT-1; i++) { yutani_window_move(yctx, panel, 0, -i); usleep(3000); } panel_hidden = 1; } } /* Handle SIGINT by telling other threads (clock) to shut down */ static void sig_int(int sig) { printf("Received shutdown signal in panel!\n"); _continue = 0; signal(SIGINT, sig_int); } 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); } } /* Callback for mouse events */ static void panel_check_click(struct yutani_msg_window_mouse_event * evt) { static struct PanelWidget * old_target = NULL; if (evt->wid == panel->wid) { /* Figure out what widget this belongs to */ struct PanelWidget * target = NULL; if (evt->new_y >= 0 && evt->new_y < PANEL_HEIGHT) { foreach(widget_node, widgets_enabled) { struct PanelWidget * widget = widget_node->value; if (evt->new_x >= widget->left && evt->new_x < widget->left + widget->width) { target = widget; break; } } } int needs_redraw = 0; if (evt->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(evt)) { if (target) needs_redraw |= target->click(target, evt); } else if (evt->buttons & YUTANI_MOUSE_BUTTON_RIGHT) { if (target) needs_redraw |= target->right_click(target, evt); } else if (evt->command == YUTANI_MOUSE_EVENT_MOVE || evt->command == YUTANI_MOUSE_EVENT_ENTER) { if (old_target && target != old_target) needs_redraw |= old_target->leave(old_target, evt); if (target && target != old_target) needs_redraw |= target->enter(target, evt); if (target) needs_redraw |= target->move(target, evt); old_target = target; } else if (evt->command == YUTANI_MOUSE_EVENT_LEAVE) { if (old_target) needs_redraw |= old_target->leave(old_target, evt); old_target = NULL; } if (needs_redraw) redraw(); } } static char altf2_buffer[1024] = {0}; static unsigned int altf2_collected = 0; static void close_altf2(void) { free(a2ctx->backbuffer); free(a2ctx); altf2_buffer[0] = 0; altf2_collected = 0; yutani_close(yctx, alt_f2); alt_f2 = NULL; } static void redraw_altf2(void) { draw_fill(a2ctx, 0); draw_rounded_rectangle(a2ctx,0,0, ALTF2_WIDTH, ALTF2_HEIGHT, 11, premultiply(rgba(120,120,120,150))); draw_rounded_rectangle(a2ctx,1,1, ALTF2_WIDTH-2, ALTF2_HEIGHT-2, 10, ALTTAB_BACKGROUND); tt_set_size(panel_context.font, 20); int t = tt_string_width(panel_context.font, altf2_buffer); tt_draw_string(a2ctx, panel_context.font, center_x_a2(t), 80, altf2_buffer, rgb(255,255,255)); flip(a2ctx); yutani_flip(yctx, alt_f2); } static void redraw_alttab(void) { if (!actx) return; while (new_focused > -1 && !ads_by_z[new_focused]) { new_focused--; } if (new_focused == -1) { /* Stop tabbing */ was_tabbing = 0; free(actx->backbuffer); free(actx); actx = NULL; yutani_close(yctx, alttab); return; } /* How many windows do we have? */ unsigned int window_count = 0; while (ads_by_z[window_count]) window_count++; #define ALTTAB_COLUMNS 5 /* How many rows should that be? */ int rows = (window_count - 1) / ALTTAB_COLUMNS + 1; /* How many columns? */ int columns = (rows == 1) ? window_count : ALTTAB_COLUMNS; /* How much padding on the last row? */ int last_row = (window_count % columns) ? ((ALTTAB_WIN_SIZE + 20) * (columns - (window_count % columns))) / 2 : 0; /* Is the window the right size? */ unsigned int expected_width = columns * (ALTTAB_WIN_SIZE + 20) + 40; unsigned int expected_height = rows * (ALTTAB_WIN_SIZE + 20) + 60; if (alttab->width != expected_width || alttab->height != expected_height) { yutani_window_resize(yctx, alttab, expected_width, expected_height); return; } /* Draw the background, right now just a dark semi-transparent box */ draw_fill(actx, 0); draw_rounded_rectangle(actx,0,0, alttab->width, alttab->height, 11, premultiply(rgba(120,120,120,150))); draw_rounded_rectangle(actx,1,1, alttab->width-2, alttab->height-2, 10, ALTTAB_BACKGROUND); for (unsigned int i = 0; i < window_count; ++i) { if (!ads_by_z[i]) continue; struct window_ad * ad = ads_by_z[i]; /* Figure out grid alignment for this element */ int pos_x = ((window_count - i - 1) % ALTTAB_COLUMNS) * (ALTTAB_WIN_SIZE + 20) + 20; int pos_y = ((window_count - i - 1) / ALTTAB_COLUMNS) * (ALTTAB_WIN_SIZE + 20) + 20; if ((window_count - i - 1) / ALTTAB_COLUMNS == (unsigned int)rows - 1) { pos_x += last_row; } if (i == (unsigned int)new_focused) { draw_rounded_rectangle(actx, pos_x, pos_y, ALTTAB_WIN_SIZE + 20, ALTTAB_WIN_SIZE + 20, 7, premultiply(rgba(170,170,170,150))); } /* try very hard to get a window texture */ char key[1024]; YUTANI_SHMKEY_EXP(yctx->server_ident, key, 1024, ad->bufid); size_t size = 0; uint32_t * buf = shm_obtain(key, &size); if (buf && size >= ad->width * ad->height * 4) { sprite_t tmp; tmp.width = ad->width; tmp.height = ad->height; tmp.bitmap = buf; int ox = 0; int oy = 0; int sw, sh; if (tmp.width > tmp.height) { sw = ALTTAB_WIN_SIZE; sh = tmp.height * ALTTAB_WIN_SIZE / tmp.width; oy = (ALTTAB_WIN_SIZE - sh) / 2; } else { sh = ALTTAB_WIN_SIZE; sw = tmp.width * ALTTAB_WIN_SIZE / tmp.height; ox = (ALTTAB_WIN_SIZE - sw) / 2; } draw_sprite_scaled(actx, &tmp, pos_x + ox + 10, pos_y + oy + 10, sw, sh); shm_release(key); sprite_t * icon = icon_get_48(ad->icon); draw_sprite(actx, icon, pos_x + 10 + ALTTAB_WIN_SIZE - 50, pos_y + 10 + ALTTAB_WIN_SIZE - 50); } else { sprite_t * icon = icon_get_48(ad->icon); draw_sprite(actx, icon, pos_x + 10 + (ALTTAB_WIN_SIZE - 48) / 2, pos_y + 10 + (ALTTAB_WIN_SIZE - 48) / 2); } } { struct window_ad * ad = ads_by_z[new_focused]; int t; char * title = tt_ellipsify(ad->name, 16, panel_context.font, alttab->width - 20, &t); tt_set_size(panel_context.font, 16); tt_draw_string(actx, panel_context.font, center_x_a(t), rows * (ALTTAB_WIN_SIZE + 20) + 44, title, rgb(255,255,255)); free(title); } flip(actx); yutani_window_move(yctx, alttab, center_x(alttab->width), center_y(alttab->height)); yutani_flip(yctx, alttab); } static pthread_t _waiter_thread; static void * logout_prompt_waiter(void * arg) { if (system("showdialog \"Log Out\" /usr/share/icons/48/exit.png \"Are you sure you want to log out?\"") == 0) { yutani_session_end(yctx); _continue = 0; } return NULL; } void launch_application_menu(struct MenuEntry * self) { struct MenuEntry_Normal * _self = (void *)self; if (!strcmp((char *)_self->action,"log-out")) { /* Spin off a thread for this */ pthread_create(&_waiter_thread, NULL, logout_prompt_waiter, NULL); } else { launch_application((char *)_self->action); } } static void handle_key_event(struct yutani_msg_key_event * ke) { /** * Is the Alt+F2 command entry window open? * Then we should capture all typing and use * direct it to the 'input box'. */ if (alt_f2 && ke->wid == alt_f2->wid) { if (ke->event.action == KEY_ACTION_DOWN) { /* Escape = cancel */ if (ke->event.keycode == KEY_ESCAPE) { close_altf2(); return; } /* Backspace */ if (ke->event.key == '\b') { if (altf2_collected) { altf2_buffer[altf2_collected-1] = '\0'; altf2_collected--; redraw_altf2(); } return; } /* Enter */ if (ke->event.key == '\n') { /* execute */ launch_application(altf2_buffer); close_altf2(); return; } /* Some other key */ if (!ke->event.key) { return; } /* Try to add it */ if (altf2_collected < sizeof(altf2_buffer) - 1) { altf2_buffer[altf2_collected] = ke->event.key; altf2_collected++; altf2_buffer[altf2_collected] = 0; redraw_altf2(); } } } /* Ctrl-Alt-T = Open a new terminal */ if ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) && (ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == 't') && (ke->event.action == KEY_ACTION_DOWN)) { launch_application("exec terminal"); return; } /* Ctrl-F11 = Toggle visibility of the panel */ if ((ke->event.modifiers & KEY_MOD_LEFT_CTRL) && (ke->event.keycode == KEY_F11) && (ke->event.action == KEY_ACTION_DOWN)) { fprintf(stderr, "[panel] Toggling visibility.\n"); toggle_hide_panel(); return; } /* Alt-F2 = Show the command entry window */ if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == KEY_F2) && (ke->event.action == KEY_ACTION_DOWN)) { /* show menu */ if (!alt_f2) { alt_f2 = yutani_window_create_flags(yctx, ALTF2_WIDTH, ALTF2_HEIGHT, YUTANI_WINDOW_FLAG_BLUR_BEHIND); yutani_window_update_shape(yctx, alt_f2, 5); yutani_window_move(yctx, alt_f2, center_x(ALTF2_WIDTH), center_y(ALTF2_HEIGHT)); a2ctx = init_graphics_yutani_double_buffer(alt_f2); redraw_altf2(); } } /* Maybe a plugin wants to handle this key bind */ foreach(widget_node, widgets_enabled) { struct PanelWidget * widget = widget_node->value; widget->onkey(widget, ke); } /* Releasing Alt when the Alt-Tab switcher is visible */ if ((was_tabbing) && (ke->event.keycode == 0 || ke->event.keycode == KEY_LEFT_ALT) && (ke->event.modifiers == 0) && (ke->event.action == KEY_ACTION_UP)) { fprintf(stderr, "[panel] Stopping focus new_focused = %d\n", new_focused); struct window_ad * ad = ads_by_z[new_focused]; if (!ad) return; yutani_focus_window(yctx, ad->wid); was_tabbing = 0; new_focused = -1; free(actx->backbuffer); free(actx); actx = NULL; yutani_close(yctx, alttab); return; } /* Alt-Tab = Switch windows */ if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == '\t') && (ke->event.action == KEY_ACTION_DOWN)) { /* Alt-Tab and Alt-Shift-Tab should go in alternate directions */ int direction = (ke->event.modifiers & KEY_MOD_LEFT_SHIFT) ? 1 : -1; /* Are there no windows to switch? */ if (window_list->length < 1) return; /* Figure out the new focused window */ if (was_tabbing) { new_focused = new_focused + direction; } else { new_focused = active_window + direction; /* Create tab window */ alttab = yutani_window_create_flags(yctx, ALTTAB_WIDTH, ALTTAB_HEIGHT, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_NO_ANIMATION | YUTANI_WINDOW_FLAG_BLUR_BEHIND); yutani_window_update_shape(yctx, alttab, 5); yutani_set_stack(yctx, alttab, YUTANI_ZORDER_OVERLAY); /* Initialize graphics context against the window */ actx = init_graphics_yutani_double_buffer(alttab); } /* Handle wraparound */ if (new_focused < 0) { new_focused = 0; for (int i = 0; i < MAX_WINDOW_COUNT; i++) { if (ads_by_z[i+1] == NULL) { new_focused = i; break; } } } else if (ads_by_z[new_focused] == NULL) { if (ads_by_z[0]) { new_focused = 0; } else { new_focused = -1; } } was_tabbing = 1; redraw_alttab(); } } void redraw(void) { memcpy(ctx->backbuffer, bg_blob, bg_size); foreach(widget_node, widgets_enabled) { struct PanelWidget * widget = widget_node->value; gfx_context_t * inner = init_graphics_subregion(ctx, widget->left, 0, widget->width, PANEL_HEIGHT); widget->draw(widget, inner); free(inner); } /* Flip */ flip(ctx); yutani_flip(yctx, panel); } static void update_window_list(void) { yutani_query_windows(yctx); list_t * new_window_list = list_create(); ads_by_z[0] = NULL; int i = 0; while (1) { /* We wait for a series of WINDOW_ADVERTISE messsages */ yutani_msg_t * m = yutani_wait_for(yctx, YUTANI_MSG_WINDOW_ADVERTISE); struct yutani_msg_window_advertise * wa = (void*)m->data; if (wa->size == 0) { /* A sentinal at the end will have a size of 0 */ free(m); break; } /* Store each window advertisement */ struct window_ad * ad = malloc(sizeof(struct window_ad)); char * s = malloc(wa->size); memcpy(s, wa->strings, wa->size); ad->name = &s[0]; ad->icon = &s[wa->icon]; ad->strings = s; ad->flags = wa->flags; ad->wid = wa->wid; ad->bufid = wa->bufid; ad->width = wa->width; ad->height = wa->height; ads_by_z[i] = ad; i++; ads_by_z[i] = NULL; node_t * next = NULL; /* And insert it, ordered by wid, into the window list */ foreach(node, new_window_list) { struct window_ad * n = node->value; if (n->wid > ad->wid) { next = node; break; } } if (next) { list_insert_before(new_window_list, next, ad); } else { list_insert(new_window_list, ad); } free(m); } active_window = i-1; if (window_list) { foreach(node, window_list) { struct window_ad * ad = (void*)node->value; free(ad->strings); free(ad); } list_free(window_list); free(window_list); } window_list = new_window_list; /* And redraw the panel */ redraw(); } static void redraw_panel_background(gfx_context_t * ctx, int width, int height) { draw_fill(ctx, rgba(0,0,0,0xF2)); } static void resize_finish(int xwidth, int xheight) { yutani_window_resize_accept(yctx, panel, xwidth, xheight); reinit_graphics_yutani(ctx, panel); yutani_window_resize_done(yctx, panel); width = xwidth; redraw_panel_background(ctx, xwidth, xheight); /* Copy the prerendered background so we can redraw it quickly */ bg_size = panel->width * panel->height * sizeof(uint32_t); bg_blob = realloc(bg_blob, bg_size); memcpy(bg_blob, ctx->backbuffer, bg_size); widgets_layout(); update_window_list(); } static void bind_keys(void) { /* Cltr-Alt-T = launch terminal */ yutani_key_bind(yctx, 't', KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); /* Alt+Tab = app switcher*/ yutani_key_bind(yctx, '\t', KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); yutani_key_bind(yctx, '\t', KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT, YUTANI_BIND_STEAL); /* Ctrl-F11 = toggle panel visibility */ yutani_key_bind(yctx, KEY_F11, KEY_MOD_LEFT_CTRL, YUTANI_BIND_STEAL); /* Alt+F2 = show app runner */ yutani_key_bind(yctx, KEY_F2, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); /* This lets us receive all just-modifier key releases */ yutani_key_bind(yctx, KEY_LEFT_ALT, 0, YUTANI_BIND_PASSTHROUGH); } static void sig_usr2(int sig) { yutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP); yutani_flip(yctx, panel); bind_keys(); signal(SIGUSR2, sig_usr2); } static int mouse_event_ignore(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { return 0; } static int widget_enter_generic(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { this->highlighted = 1; /* Highlighted */ return 1; } static int widget_leave_generic(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { this->highlighted = 0; /* Not highlighted */ return 1; } void panel_highlight_widget(struct PanelWidget * this, gfx_context_t * ctx, int active) { if (this->highlighted || active) { draw_rounded_rectangle(ctx, 3, 3, ctx->width - 6, ctx->height - 6, 11, premultiply(rgba(120,120,120,active ? 180 : 150))); } } static int widget_draw_generic(struct PanelWidget * this, gfx_context_t * ctx) { draw_rounded_rectangle( ctx, 0, 0, ctx->width, ctx->height, 7, premultiply(rgba(120,120,120,150))); if (this->highlighted) { draw_rounded_rectangle( ctx, 1, 1, ctx->width-2, ctx->height-2, 6, premultiply(rgba(120,160,230,220))); } else { draw_rounded_rectangle( ctx, 1, 1, ctx->width-2, ctx->height-2, 6, ALTTAB_BACKGROUND); } return 0; } static int widget_update_generic(struct PanelWidget * this, int * force_updates) { return 0; } static int widget_onkey_generic(struct PanelWidget * this, struct yutani_msg_key_event * evt) { return 0; } struct PanelWidget * widget_new(void) { struct PanelWidget * out = calloc(1, sizeof(struct PanelWidget)); out->pctx = &panel_context; out->draw = widget_draw_generic; out->click = mouse_event_ignore; /* click_generic */ out->right_click = mouse_event_ignore; /* right_click_generic */ out->leave = widget_leave_generic; out->enter = widget_enter_generic; out->update = widget_update_generic; out->onkey = widget_onkey_generic; out->move = mouse_event_ignore; /* move_generic */ out->highlighted = 0; out->fill = 0; return out; } static void widgets_layout(void) { int total_width = 0; int flexible_widgets = 0; foreach(node, widgets_enabled) { struct PanelWidget * widget = node->value; if (widget->fill) { flexible_widgets++; } else { total_width += widget->width; } } /* Now lay out the widgets */ int x = 0; int available = width; foreach(node, widgets_enabled) { struct PanelWidget * widget = node->value; widget->left = x; if (widget->fill) { widget->width = (available - total_width) / flexible_widgets; } x += widget->width; } } static void update_periodic_widgets(int * force_updates) { *force_updates = 0; int needs_layout = 0; foreach(widget_node, widgets_enabled) { struct PanelWidget * widget = widget_node->value; needs_layout |= widget->update(widget, force_updates); } if (needs_layout) widgets_layout(); redraw(); } int panel_menu_show_at(struct MenuList * menu, int x) { int mwidth, mheight, offset; /* Calculate the expected size of the menu window. */ menu_calculate_dimensions(menu, &mheight, &mwidth); if (x - mwidth / 2 < MENU_PAD) { offset = MENU_PAD; menu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_LEFT; } else if (x + mwidth / 2 > width - MENU_PAD) { offset = width - MENU_PAD - mwidth; menu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_RIGHT; } else { offset = x - mwidth / 2; menu->flags = (menu->flags & ~MENU_FLAG_BUBBLE) | MENU_FLAG_BUBBLE_CENTER; } menu->flags |= MENU_FLAG_TAIL_POSITION; menu->tail_offset = x - offset; /* Prepare the menu, which creates the window. */ menu_show_at(menu, panel, offset, DROPDOWN_OFFSET); /* If we succeeded, move it to the final offset and display it */ if (menu->window) return 0; return 1; } int panel_menu_show(struct PanelWidget * this, struct MenuList * menu) { return panel_menu_show_at(menu, this->left + this->width / 2); } int main (int argc, char ** argv) { if (argc < 2 || strcmp(argv[1],"--really")) { fprintf(stderr, "%s: Desktop environment panel / dock\n" "\n" " Renders the application menu, window list, widgets,\n" " alt-tab window switcher, clock, etc.\n" " You probably don't want to run this directly - it is\n" " started automatically by the session manager.\n", argv[0]); return 1; } /* Connect to window server */ yctx = yutani_init(); /* Shared fonts */ panel_context.font = tt_font_from_shm("sans-serif"); panel_context.font_bold = tt_font_from_shm("sans-serif.bold"); panel_context.font_mono = tt_font_from_shm("monospace"); panel_context.font_mono_bold = tt_font_from_shm("monospace.bold"); /* For convenience, store the display size */ width = yctx->display_width; height = yctx->display_height; panel_context.color_text_normal = rgb(230,230,230); panel_context.color_text_hilighted = rgb(142,216,255); panel_context.color_text_focused = rgb(255,255,255); panel_context.color_icon_normal = rgb(230,230,230); panel_context.color_special = rgb(93,163,236); panel_context.font_size_default = 14; panel_context.extra_widget_spacing = 12; /* Create the panel window */ panel = yutani_window_create_flags(yctx, width, PANEL_HEIGHT, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_ALT_ANIMATION); panel_context.basewindow = panel; /* And move it to the top layer */ yutani_set_stack(yctx, panel, YUTANI_ZORDER_TOP); yutani_window_update_shape(yctx, panel, YUTANI_SHAPE_THRESHOLD_CLEAR); /* Initialize graphics context against the window */ ctx = init_graphics_yutani_double_buffer(panel); /* Draw the background */ redraw_panel_background(ctx, panel->width, panel->height); /* Copy the prerendered background so we can redraw it quickly */ bg_size = panel->width * panel->height * sizeof(uint32_t); bg_blob = malloc(bg_size); memcpy(bg_blob, ctx->backbuffer, bg_size); /* Catch SIGINT */ signal(SIGINT, sig_int); signal(SIGUSR2, sig_usr2); widgets_enabled = list_create(); /* Initialize requested widgets */ const char * widgets_to_load[] = { "appmenu", "windowlist", "volume", "network", "weather", "date", "clock", "logout", NULL }; for (const char ** widget = widgets_to_load; *widget; widget++) { char lib_name[200]; char func_name[200]; snprintf(lib_name, 200, "libtoaru_panel_%s.so", *widget); snprintf(func_name, 200, "widget_init_%s", *widget); void * lib = dlopen(lib_name, 0); if (!lib) { fprintf(stderr, "panel: failed to load widget '%s'\n", *widget); } else { void (*widget_init)(void) = dlsym(lib, func_name); if (!widget_init) { fprintf(stderr, "panel: failed to initialize widget '%s'\n", *widget); } else { widget_init(); } } } /* Lay out the widgets */ int force_updates = 0; update_periodic_widgets(&force_updates); widgets_layout(); /* Subscribe to window updates */ yutani_subscribe_windows(yctx); /* Ask compositor for window list */ update_window_list(); /* Key bindings */ bind_keys(); time_t last_tick = 0; int fds[1] = {fileno(yctx->sock)}; fprintf(stderr, "entering loop?\n"); while (_continue) { int index = fswait2(1,fds,force_updates ? 50 : 200); /* ~20 fps? */ if (index == 0) { /* Respond to Yutani events */ yutani_msg_t * m = yutani_poll(yctx); while (m) { menu_process_event(yctx, m); switch (m->type) { /* New window information is available */ case YUTANI_MSG_NOTIFY: update_window_list(); break; /* Mouse movement / click */ case YUTANI_MSG_WINDOW_MOUSE_EVENT: panel_check_click((struct yutani_msg_window_mouse_event *)m->data); break; case YUTANI_MSG_KEY_EVENT: handle_key_event((struct yutani_msg_key_event *)m->data); break; case YUTANI_MSG_WELCOME: { struct yutani_msg_welcome * mw = (void*)m->data; width = mw->display_width; height = mw->display_height; yutani_window_resize(yctx, panel, mw->display_width, PANEL_HEIGHT); } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; if (wr->wid == panel->wid) { resize_finish(wr->width, wr->height); } else if (alttab && wr->wid == alttab->wid) { yutani_window_resize_accept(yctx, alttab, wr->width, wr->height); reinit_graphics_yutani(actx, alttab); redraw_alttab(); yutani_window_resize_done(yctx, alttab); } } break; default: break; } free(m); m = yutani_poll_async(yctx); } } struct timeval now; gettimeofday(&now, NULL); if (now.tv_sec != last_tick || force_updates) { last_tick = now.tv_sec; waitpid(-1, NULL, WNOHANG); if (was_tabbing) { redraw_alttab(); } update_periodic_widgets(&force_updates); } } /* Close the panel window */ yutani_close(yctx, panel); /* Stop notifying us of window changes */ yutani_unsubscribe_windows(yctx); return 0; } ================================================ FILE: apps/path_demo.krk ================================================ #!/bin/kuroko from _yutani2 import (YutaniCtx, Font, rgb, MenuBar, decor_get_bounds, decor_render, MenuList, MenuEntry, decor_handle_event, decor_show_default_menu, Sprite, TTContour, TransformMatrix, MenuEntrySubmenu, TTShape, MenuEntryToggle) from yutani_mainloop import Window, yctx as y, AsyncMainloop, Task, sleep import math import random let mainloop = AsyncMainloop() let texture_star = Sprite("/usr/share/icons/48/star.png") let texture_flowers = Sprite('/usr/share/wallpaper.jpg') let tt_texture = texture_star def scale_by(tm,val): tm.scale(val/1000,val/1000) def rotate_by(tm,val): tm.rotate(val/1000) def shear_x_by(tm,val): tm.shear(val/1000,0.0) def shear_y_by(tm,val): tm.shear(0.0,val/1000) let x_thing = scale_by let y_thing = rotate_by def draw_text(): let font = Font('sans-serif.bold',128) let Rect, w = font.prepare_string(0,128,"ToaruOS") return Rect let cursor = 0,0 def draw_cursor_position(): let font = Font('sans-serif.bold',128) let Rect, w = font.prepare_string(0,128,f'{cursor[0]},{cursor[1]}') return Rect def draw_box(): let Rect = TTContour(20,20) Rect.line_to(520,20) Rect.line_to(520,520) Rect.line_to(20,520) return Rect let base_graphic = draw_text let tt_filter = TTShape.TT_PATH_FILTER_BILINEAR let tt_wrap = TTShape.TT_PATH_WRAP_REPEAT let warp_shape = False def paint_demo(ctx, cursor): let tm = TransformMatrix() let x, y = cursor x_thing(tm,x) y_thing(tm,y) let Rect = base_graphic() if warp_shape: Rect.transform(tm) let SRect = Rect.finish() Rect.free() SRect.paint_sprite(ctx,tt_texture,tm,tt_filter,tt_wrap) SRect.free() def set_menu(menu, action): for sibling in menu.group: sibling.state = False menu.state = True action() def check_list(entries, default=0): let ml = MenuList() let group = [] for i, e in enumerate(entries): let name, action = e let me = MenuEntryToggle(name, lambda menu: set_menu(menu, action), i == default) me.group = group group.append(me) ml.insert(me) return ml class MyWindow(Window): def __init__(self): super().__init__(640, 480, title="Path Demo", doublebuffer=True) self.bgc = rgb(255,255,255) self.mb = MenuBar((("File",'file'),("View",'view'),("Help",'help'))) let _menu_File = MenuList() _menu_File.insert(MenuEntry("Quit", lambda menu: self.close())) self.mb.insert('file', _menu_File) let _menu_View = MenuList() let _menu_View_Horizontal = check_list(( ("Scale", lambda: x_thing = scale_by), ("Rotate", lambda: x_thing = rotate_by), ("Shear X", lambda: x_thing = shear_x_by), ("Shear Y", lambda: x_thing = shear_y_by), )) self.mb.insert('view-horizontal', _menu_View_Horizontal) _menu_View.insert(MenuEntrySubmenu("Horizontal","view-horizontal")) let _menu_View_Vertical = check_list(( ("Scale", lambda: y_thing = scale_by), ("Rotate", lambda: y_thing = rotate_by), ("Shear X", lambda: y_thing = shear_x_by), ("Shear Y", lambda: y_thing = shear_y_by), ), default=1) self.mb.insert('view-vertical', _menu_View_Vertical) _menu_View.insert(MenuEntrySubmenu("Vertical","view-vertical")) let _menu_View_Filter = check_list(( ("Bilinear", lambda: tt_filter = TTShape.TT_PATH_FILTER_BILINEAR), ("Nearest", lambda: tt_filter = TTShape.TT_PATH_FILTER_NEAREST) )) self.mb.insert('view-filter', _menu_View_Filter) _menu_View.insert(MenuEntrySubmenu("Filter","view-filter")) let _menu_View_Wrap = check_list(( ('Repeat', lambda: tt_wrap = TTShape.TT_PATH_WRAP_REPEAT), ('None', lambda: tt_wrap = TTShape.TT_PATH_WRAP_NONE), ('Pad', lambda: tt_wrap = TTShape.TT_PATH_WRAP_PAD) )) self.mb.insert('view-wrap', _menu_View_Wrap) _menu_View.insert(MenuEntrySubmenu("Wrap",'view-wrap')) let _menu_View_Texture = check_list(( ('Star', lambda: tt_texture = texture_star), ('Flowers', lambda: tt_texture = texture_flowers), )) self.mb.insert('view-texture', _menu_View_Texture) _menu_View.insert(MenuEntrySubmenu('Texture','view-texture')) let _menu_View_Shape = check_list(( ('Text', lambda: base_graphic = draw_text), ('Cursor', lambda: base_graphic = draw_cursor_position), ('Box', lambda: base_graphic = draw_box), )) self.mb.insert('view-shape', _menu_View_Shape) _menu_View.insert(MenuEntrySubmenu('Shape','view-shape')) let _menu_View_Warp_Shape = check_list(( ('No', lambda: warp_shape = False), ('Yes', lambda: warp_shape = True), )) self.mb.insert('view-warp-shape', _menu_View_Warp_Shape) _menu_View.insert(MenuEntrySubmenu('Warp shape','view-warp-shape')) self.mb.insert('view', _menu_View) let _menu_Help = MenuList() let _menu_Help_help = MenuEntry("Help",lambda menu: print("Should open some help here I guess")) _menu_Help.insert(_menu_Help_help) self.mb.insert('help', _menu_Help) self.mb.callback = lambda x: self.pending_updates = True cursor = 0,0 self.redrawer = Task(self.redraw_loop()) self.pending_updates = True async def redraw_loop(self): while not self.closed: if self.pending_updates: self.draw() self.pending_updates = False await sleep(0.016) def draw(self): self.fill(self.bgc) decor_render(self) let bounds = decor_get_bounds(self) self.mb.place(bounds['left_width'],bounds['top_height'],self.width-bounds['width'],self) self.mb.render(self) self.mb.height = 24 # Compatibility let sprite = Sprite(width=self.width-bounds['width'],height=self.height-bounds['height']-self.mb.height) sprite.fill(rgb(255,255,255)) paint_demo(sprite,cursor) self.draw_sprite(sprite,bounds['left_width'],bounds['top_height']+self.mb.height) sprite.free() self.flip() def mouse_event(self, msg): let decResponse = decor_handle_event(msg) if decResponse == 2: self.close() return True else if decResponse == 5: decor_show_default_menu(self, self.x + msg.new_x, self.y + msg.new_y) self.mb.mouse_event(self, msg) let nc = msg.new_x, msg.new_y if cursor != nc: cursor = nc self.pending_updates = True def keyboard_event(self, msg): if msg.keycode == 113 and msg.action == 1: self.close() def close(self): super().close() mainloop.exit() def window_moved(self, msg): self.pending_updates = True def menu_close(self): self.pending_updates = True def finish_resize(self, msg): if msg.width < 100 or msg.height < 100: self.resize_offer(100 if msg.width < 100 else msg.width, 100 if msg.height < 100 else msg.height) return super().finish_resize(msg) mainloop.activate() let w = MyWindow() w.move(200,200) w.draw() mainloop.menu_closed_callback = w.menu_close mainloop.run() ================================================ FILE: apps/piano.c ================================================ /** * @brief piano - Interactively make beeping noises * * @copyright * 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-2018 K. Lange */ #include #include #include #include #include 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)); } struct termios old; void set_unbuffered() { tcgetattr(fileno(stdin), &old); struct termios new = old; new.c_lflag &= (~ICANON & ~ECHO); tcsetattr(fileno(stdin), TCSAFLUSH, &new); } 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]); return 1; } set_unbuffered(); char c; while ((c = fgetc(stdin))) { switch (c) { case 'q': note(0, 1000); return 0; case 'z': note(0, 1000); return 0; case ' ': note(0, 1000); break; case 'a': note(-1, 1308); break; case 'w': note(-1, 1386); break; case 's': note(-1, 1468); break; case 'e': note(-1, 1556); break; case 'd': note(-1, 1648); break; case 'f': note(-1, 1746); break; case 't': note(-1, 1850); break; case 'g': note(-1, 1960); break; case 'y': note(-1, 2077); break; case 'h': note(-1, 2200); break; case 'u': note(-1, 2331); break; case 'j': note(-1, 2469); break; case 'k': note(-1, 2616); break; case 'o': note(-1, 2772); break; case 'l': note(-1, 2937); break; case 'p': note(-1, 3111); break; case ';': note(-1, 3296); break; case '\'': note(-1, 3492);break; } } return 0; } ================================================ FILE: apps/pidof.c ================================================ /** * @brief Look up the PIDs of all processes with a particular name * * @copyright * 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 */ #include #include #include #include #include #include #include #include typedef struct process { int pid; int ppid; int tgid; char name[100]; char path[200]; } p_t; #define LINE_LEN 4096 p_t * build_entry(struct dirent * dent) { char tmp[300]; FILE * f; char line[LINE_LEN]; sprintf(tmp, "/proc/%s/status", dent->d_name); f = fopen(tmp, "r"); p_t * proc = malloc(sizeof(p_t)); while (fgets(line, LINE_LEN, f) != NULL) { char * n = strstr(line,"\n"); if (n) { *n = '\0'; } char * tab = strstr(line,"\t"); if (tab) { *tab = '\0'; tab++; } if (strstr(line, "Pid:") == line) { proc->pid = atoi(tab); } else if (strstr(line, "PPid:") == line) { proc->ppid = atoi(tab); } else if (strstr(line, "Tgid:") == line) { proc->tgid = atoi(tab); } else if (strstr(line, "Name:") == line) { strcpy(proc->name, tab); } else if (strstr(line, "Path:") == line) { strcpy(proc->path, tab); } } if (strstr(proc->name,"python") == proc->name) { char * name = proc->path + strlen(proc->path) - 1; while (1) { if (*name == '/') { name++; break; } if (name == proc->name) break; name--; } memcpy(proc->name, name, strlen(name)+1); } if (proc->tgid != proc->pid) { char tmp[100] = {0}; sprintf(tmp, "{%s}", proc->name); memcpy(proc->name, tmp, strlen(tmp)+1); } fclose(f); return proc; } int main (int argc, char * argv[]) { if (argc < 2) return 1; int space = 0; /* Open the directory */ DIR * dirp = opendir("/proc"); int found_something = 0; struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') { p_t * proc = build_entry(ent); if (!strcmp(proc->name, argv[optind])) { if (space++) printf(" "); printf("%d", proc->pid); found_something = 1; } } ent = readdir(dirp); } closedir(dirp); if (!found_something) { return 1; } printf("\n"); return 0; } ================================================ FILE: apps/ping.c ================================================ /** * @brief Send ICMP pings * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BYTES_TO_SEND 64 struct ICMP_Header { uint8_t type, code; uint16_t checksum; uint16_t identifier; uint16_t sequence_number; uint8_t payload[]; }; static unsigned long clocktime(void) { struct timeval tv; gettimeofday(&tv,NULL); return tv.tv_sec * 1000000 + tv.tv_usec; } static uint16_t icmp_checksum(char * payload, size_t len) { uint32_t sum = 0; uint16_t * s = (uint16_t *)payload; for (size_t i = 0; i < (len) / 2; ++i) { sum += ntohs(s[i]); } if (sum > 0xFFFF) { sum = (sum >> 16) + (sum & 0xFFFF); } return ~(sum & 0xFFFF) & 0xFFFF; } static int break_from_loop = 0; static void sig_break_loop(int sig) { (void)sig; break_from_loop = 1; } int main(int argc, char * argv[]) { if (argc < 2) return 1; int pings_sent = 0; struct hostent * host = gethostbyname(argv[1]); if (!host) { fprintf(stderr, "%s: not found\n", argv[1]); return 1; } char * addr = inet_ntoa(*(struct in_addr*)host->h_addr_list[0]); int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); if (sock < 0) { fprintf(stderr, "%s: No socket: %s\n", argv[1], strerror(errno)); return 1; } int yes = 1; setsockopt(sock, IPPROTO_IP, IP_RECVTTL, &yes, sizeof(int)); signal(SIGINT, sig_break_loop); struct sockaddr_in dest; dest.sin_family = AF_INET; memcpy(&dest.sin_addr.s_addr, host->h_addr, host->h_length); printf("PING %s (%s) %d data bytes\n", argv[1], addr, BYTES_TO_SEND - 8); struct ICMP_Header * ping = malloc(BYTES_TO_SEND); ping->type = 8; /* request */ ping->code = 0; ping->identifier = 0; ping->sequence_number = 0; /* Fill in data */ for (int i = 0; i < BYTES_TO_SEND - 8; ++i) { ping->payload[i] = i; } int responses_received = 0; while (!break_from_loop) { ping->sequence_number = htons(pings_sent+1); ping->checksum = 0; ping->checksum = htons(icmp_checksum((void*)ping, BYTES_TO_SEND)); /* Send it and wait */ unsigned long sent_at = clocktime(); if (sendto(sock, (void*)ping, BYTES_TO_SEND, 0, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0) { fprintf(stderr, "sendto: %s\n", strerror(errno)); } pings_sent++; struct pollfd fds[1]; fds[0].fd = sock; fds[0].events = POLLIN; int ret = poll(fds,1,1000); if (ret > 0) { char data[4096]; char control[4096]; struct sockaddr_in source; socklen_t source_size = sizeof(struct sockaddr_in); struct iovec _iovec = { data, 4096 }; struct msghdr msg = { &source, source_size, &_iovec, 1, control, 4096, 0 }; ssize_t len = recvmsg(sock, &msg, 0); unsigned long rcvd_at = clocktime(); if (len > 0) { /* Is it actually a PING response ? */ struct ICMP_Header * icmp = (void*)data; unsigned char ttl = 0; if (msg.msg_controllen) { char * control_msg = control; while (control_msg - control + sizeof(struct cmsghdr) <= msg.msg_controllen) { struct cmsghdr * cmsg = (void*)control_msg; if (cmsg->cmsg_level == IPPROTO_IP && (cmsg->cmsg_type == IP_RECVTTL || cmsg->cmsg_type == IP_TTL)) { memcpy(&ttl, CMSG_DATA(cmsg), 1); break; } control_msg += cmsg->cmsg_len; } } if (icmp->type == 0) { /* How much data, minus the header? */ /* Get the address */ char * from = inet_ntoa(source.sin_addr); int time_taken = (rcvd_at - sent_at); printf("%zd bytes from %s: icmp_seq=%d ttl=%d time=%d", len, from, ntohs(icmp->sequence_number), (unsigned char)ttl, time_taken / 1000); if (time_taken < 1000) { printf(".%03d", time_taken % 1000); } else if (time_taken < 10000) { printf(".%02d", (time_taken / 10) % 100); } else if (time_taken < 100000) { printf(".%01d", (time_taken / 100) % 10); } printf(" ms\n"); responses_received++; } } } if (!break_from_loop) { sleep(1); } } printf("--- %s statistics ---\n", argv[1]); printf("%d packets transmitted, %d received, %d%% packet loss\n", pings_sent, responses_received, 100*(pings_sent-responses_received)/pings_sent); return 0; } ================================================ FILE: apps/plasma.c ================================================ /** * @brief Threaded graphical demo that draws animated plasma. * * Good for burning CPU. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include #include #include #define dist(a,b,c,d) sqrt((double)(((a) - (c)) * ((a) - (c)) + ((b) - (d)) * ((b) - (d)))) static yutani_t * yctx; static yutani_window_t * wina; static int should_exit = 0; uint16_t win_width; uint16_t win_height; uint16_t off_x; uint16_t off_y; static int volatile draw_lock = 0; gfx_context_t * ctx; pthread_t thread; void sigint_handler() { should_exit = 1; pthread_join(thread, NULL); exit(1); } void redraw_borders() { render_decorations(wina, ctx, "Plasma"); } uint32_t hsv_to_rgb(int h, float s, float v) { float c = v * s; float hp = (float)h / 42.6666666f; float x = c * (1.0 - fabs(fmod(hp, 2) - 1.0)); float m = v - c; float rp, gp, bp; if (hp < 1.0) { rp = c; gp = x; bp = 0; } else if (hp < 2.0) { rp = x; gp = c; bp = 0; } else if (hp < 3.0) { rp = 0; gp = c; bp = x; } else if (hp < 4.0) { rp = 0; gp = x; bp = c; } else if (hp < 5.0) { rp = x; gp = 0; bp = c; } else if (hp < 6.0) { rp = c; gp = 0; bp = x; } else { rp = 0; gp = 0; bp = 0; } return rgb((rp + m) * 255, (gp + m) * 255, (bp + m) * 255); } void * draw_thread(void * garbage) { (void)garbage; double time = 0; /* Generate a palette */ uint32_t palette[256]; for (int x = 0; x < 256; ++x) { palette[x] = hsv_to_rgb(x,1.0,1.0); } while (!should_exit) { time += 1.0; spin_lock(&draw_lock); for (int x = 0; x < win_width; ++x) { for (int y = 0; y < win_height; ++y) { double value = sin(dist(x + time, y, 128.0, 128.0) / 8.0) + sin(dist(x, y, 64.0, 64.0) / 8.0) + sin(dist(x, y + time / 7, 192.0, 64) / 7.0) + sin(dist(x, y, 192.0, 100.0) / 8.0); GFX(ctx, x + off_x, y + off_y) = palette[(unsigned int)((value + 4) * 32) & 0xFF]; } } redraw_borders(); flip(ctx); yutani_flip(yctx, wina); spin_unlock(&draw_lock); sched_yield(); } return NULL; } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, wina, w, h); reinit_graphics_yutani(ctx, wina); struct decor_bounds bounds; decor_get_bounds(wina, &bounds); win_width = w - bounds.width; win_height = h - bounds.height; off_x = bounds.left_width; off_y = bounds.top_height; yutani_window_resize_done(yctx, wina); } int main (int argc, char ** argv) { yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]); return 1; } win_width = 300; win_height = 300; init_decorations(); struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); /* Do something with a window */ wina = yutani_window_create(yctx, win_width + bounds.width, win_height + bounds.height); yutani_window_move(yctx, wina, 300, 300); decor_get_bounds(wina, &bounds); off_x = bounds.left_width; off_y = bounds.top_height; win_width = wina->width - bounds.width; win_height = wina->height - bounds.height; ctx = init_graphics_yutani_double_buffer(wina); draw_fill(ctx, rgb(0,0,0)); redraw_borders(); flip(ctx); yutani_flip(yctx, wina); yutani_window_advertise_icon(yctx, wina, "Plasma", "plasma"); pthread_create(&thread, NULL, draw_thread, NULL); signal(SIGINT, sigint_handler); while (!should_exit) { yutani_msg_t * m = yutani_poll(yctx); while (m) { menu_process_event(yctx, 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; } } 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*)(uintptr_t)wf->wid); if (win && win == wina) { win->focused = wf->focused; } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: should_exit = 1; break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; if (wr->wid == wina->wid) { spin_lock(&draw_lock); resize_finish(wr->width, wr->height); spin_unlock(&draw_lock); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; switch (decor_handle_event(yctx, m)) { case DECOR_CLOSE: should_exit = 1; break; case DECOR_RIGHT: decor_show_default_menu(wina, wina->x + me->new_x, wina->y + me->new_y); break; } } break; default: break; } free(m); m = yutani_poll_async(yctx); } } wait(NULL); yutani_close(yctx, wina); return 0; } ================================================ FILE: apps/play.c ================================================ /** * @brief play - Play back PCM samples * * This needs very specifically-formatted PCM data to function * properly - 16-bit, signed, stereo, little endian, and 48KHz. * * TODO This should be fixed up to play back WAV files properly. * We have a sample rate convert in the out-of-repo playmp3 * * @copyright * 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 */ #include #include #include #include #include #include #include #define DSP_PATH "/dev/dsp" static int usage(char * argv[]) { fprintf(stderr, "usage: %s [-d dsp_path] /path/to/48ks16le.wav\n", argv[0]); return 1; } int main(int argc, char * argv[]) { char buf[0x1000]; int spkr, song; ssize_t r; int opt; char * dsp_path = DSP_PATH; while ((opt = getopt(argc, argv, "d:s:")) != -1) { switch (opt) { case 'd': /* DSP path */ dsp_path = optarg; break; default: return usage(argv); } } if (optind == argc) return usage(argv); spkr = open(dsp_path, O_WRONLY); if (spkr < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], dsp_path, strerror(errno)); return 1; } if (!strcmp(argv[optind], "-")) { song = STDIN_FILENO; } else { song = open(argv[optind], O_RDONLY); if (song < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); return 1; } } while ((r = read(song, buf, sizeof(buf))) > 0) { if (write(spkr, buf, r) < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], dsp_path, strerror(errno)); return 1; } } if (r < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); return 1; } return 0; } ================================================ FILE: apps/polygons.c ================================================ /** * @brief Draw filled polygons from line segments. * * This is an older version of the polygon rasterizer that turned * into the TrueType gylph rasterizer. Still makes for a neat * little graphical demo. Should probably be updated to use * the glyph rasterization code instead of its own oudated * copy though... * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #define min(a,b) ((a) < (b) ? (a) : (b)) 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; struct coord { float x; float y; }; struct edge { struct coord start; struct coord end; int direction; }; struct contour { size_t edgeCount; size_t nextAlloc; size_t flags; size_t last_start; struct edge edges[]; }; struct intersection { float x; int affect; }; struct shape { size_t edgeCount; int lastY; struct edge edges[]; }; static int edge_sorter_high_scanline(const void * a, const void * b) { const struct edge * left = a; const struct edge * right = b; if (left->start.y < right->start.y) return -1; if (left->start.y > right->start.y) return 1; return 0; } static void sort_edges(size_t edgeCount, struct edge edges[edgeCount]) { qsort(edges, edgeCount, sizeof(struct edge), edge_sorter_high_scanline); } static int intersection_sorter(const void * a, const void * b) { const struct intersection * left = a; const struct intersection * right = b; if (left->x < right->x) return -1; if (left->x > right->x) return 1; return 0; } static void sort_intersections(size_t cnt, struct intersection intersections[cnt]) { qsort(intersections, cnt, sizeof(struct intersection), intersection_sorter); } static size_t prune_edges(size_t edgeCount, float y, struct edge edges[edgeCount], struct edge into[edgeCount]) { size_t outWriter = 0; for (size_t i = 0; i < edgeCount; ++i) { if (y > edges[i].start.y && y > edges[i].end.y) continue; if (y <= edges[i].start.y && y <= edges[i].end.y) break; into[outWriter++] = edges[i]; } return outWriter; } static float edge_at(float y, struct edge * edge) { float u = (y - edge->start.y) / (edge->end.y - edge->start.y); return edge->start.x + u * (edge->end.x - edge->start.x); } struct shape * path_finish(struct contour * in) { size_t size = in->edgeCount + 1; struct shape * tmp = malloc(sizeof(struct shape) + sizeof(struct edge) * size); memcpy(tmp->edges, in->edges, sizeof(struct edge) * in->edgeCount); if (in->flags & 1) { size--; } else { tmp->edges[in->edgeCount].start.x = in->edges[in->edgeCount-1].end.x; tmp->edges[in->edgeCount].start.y = in->edges[in->edgeCount-1].end.y; tmp->edges[in->edgeCount].end.x = in->edges[in->last_start].start.x; tmp->edges[in->edgeCount].end.y = in->edges[in->last_start].start.y; } for (size_t i = 0; i < size; ++i) { if (tmp->edges[i].start.y < tmp->edges[i].end.y) { tmp->edges[i].direction = 1; } else { tmp->edges[i].direction = -1; struct coord j = tmp->edges[i].start; tmp->edges[i].start = tmp->edges[i].end; tmp->edges[i].end = j; } } sort_edges(size, tmp->edges); tmp->edgeCount = size; tmp->lastY = 0; for (size_t i = 0; i < size; ++i) { if (tmp->edges[i].end.y + 1 > tmp->lastY) tmp->lastY = tmp->edges[i].end.y + 1; } return tmp; } void path_paint(gfx_context_t * ctx, struct shape * shape, uint32_t color) { size_t size = shape->edgeCount; struct edge * intersects = malloc(sizeof(struct edge) * size); struct intersection * crosses = malloc(sizeof(struct intersection) * size); float * subsamples = malloc(sizeof(float) * width); memset(subsamples, 0, sizeof(float) * width); /* We have sorted by the scanline at which the line becomes active, so we should be able to do this... */ int yres = 4; for (int y = shape->edges[0].start.y; y < shape->lastY; ++y) { /* Figure out which ones fit here */ float _y = y; int start_x = ctx->width; int max_x = 0; for (int l = 0; l < yres; ++l) { size_t cnt = prune_edges(size, _y, shape->edges, intersects); if (cnt) { /* Get intersections */ for (size_t j = 0; j < cnt; ++j) { crosses[j].x = edge_at(_y,&intersects[j]); crosses[j].affect = intersects[j].direction; } /* Now sort the intersections */ sort_intersections(cnt, crosses); if (crosses[0].x < start_x) start_x = crosses[0].x; if (crosses[cnt-1].x+1 > max_x) max_x = crosses[cnt-1].x+1; int wind = 0; size_t j = 0; for (int x = 0; x < width && j < cnt; ++x) { while (j < cnt && x > crosses[j].x) { wind += crosses[j].affect; j++; } float last = x; while (j < cnt && (x+1) > crosses[j].x) { if (wind != 0) { subsamples[x] += crosses[j].x - last; } last = crosses[j].x; wind += crosses[j].affect; j++; } if (wind != 0) { subsamples[x] += (x+1) - last; } } } _y += 1.0/(float)yres; } for (int x = start_x; x < max_x && x < ctx->width; ++x) { unsigned int c = subsamples[x] / (float)yres * (float)_ALP(color); uint32_t nc = premultiply((color & 0xFFFFFF) | ((c & 0xFF) << 24)); GFX(ctx, x, y) = alpha_blend_rgba(GFX(ctx, x, y), nc); subsamples[x] = 0; } } free(subsamples); free(crosses); free(intersects); } struct contour * shape = NULL; struct shape * finalizedShape = NULL; static void move_to(float x, float y); static uint32_t myColor = 0; static void add_point(float x, float y) { myColor = rgb(rand() % 255,rand() % 255,rand() % 255); if (!shape) { move_to(x,y); } else if (shape->flags & 1) { shape->edges[shape->edgeCount].end.x = x; shape->edges[shape->edgeCount].end.y = y; shape->edgeCount++; shape->flags &= ~1; } else { if (shape->edgeCount + 1 == shape->nextAlloc) { shape->nextAlloc *= 2; shape = realloc(shape, sizeof(struct contour) + sizeof(struct edge) * (shape->nextAlloc)); } shape->edges[shape->edgeCount].start.x = shape->edges[shape->edgeCount-1].end.x; shape->edges[shape->edgeCount].start.y = shape->edges[shape->edgeCount-1].end.y; shape->edges[shape->edgeCount].end.x = x; shape->edges[shape->edgeCount].end.y = y; shape->edgeCount++; shape->flags &= ~1; } } static void move_to(float x, float y) { if (!shape) { shape = malloc(sizeof(struct contour) + sizeof(struct edge) * 2); shape->edgeCount = 0; shape->nextAlloc = 2; shape->flags = 0; shape->last_start = 0; } else if (!(shape->flags & 1) && shape->edgeCount) { add_point(shape->edges[shape->last_start].start.x, shape->edges[shape->last_start].start.y); } if (shape->edgeCount + 1 == shape->nextAlloc) { shape->nextAlloc *= 2; shape = realloc(shape, sizeof(struct contour) + sizeof(struct edge) * (shape->nextAlloc)); } shape->edges[shape->edgeCount].start.x = x; shape->edges[shape->edgeCount].start.y = y; shape->last_start = shape->edgeCount; shape->flags |= 1; } static void draw(void) { draw_fill(ctx, rgba(0,0,0,10)); if (shape) { if (shape->last_start + 1 == shape->edgeCount) { draw_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)); } if (finalizedShape) { /* Oh boy */ path_paint(ctx, finalizedShape, myColor); } } } static void finish_draw(void) { flip(ctx); yutani_flip(yctx, wina); } int main (int argc, char ** argv) { left = 100; top = 100; width = 500; height = 500; 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, "polygons", "polygons"); ctx = init_graphics_yutani_double_buffer(wina); draw(); finish_draw(); while (!should_exit) { int fds[1] = {fileno(yctx->sock)}; int index = fswait2(1,fds,20); if (index == 0) { yutani_msg_t * m = yutani_poll(yctx); while (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; } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; float x = (float)me->new_x; float y = (float)me->new_y; if (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { add_point(x, y); if (finalizedShape) free(finalizedShape); finalizedShape = path_finish(shape); draw(); finish_draw(); } else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) { move_to(x, y); draw(); finish_draw(); } else if (shape && (shape->flags & 1)) { draw(); draw_line(ctx, shape->edges[shape->edgeCount].start.x, x, shape->edges[shape->edgeCount].start.y, y, rgb(0,200,0)); finish_draw(); } else if (shape && !(shape->flags & 1)) { draw(); draw_line(ctx, shape->edges[shape->edgeCount-1].end.x, x, shape->edges[shape->edgeCount-1].end.y, y, rgb(0,200,0)); finish_draw(); } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: should_exit = 1; break; default: break; } free(m); m = yutani_poll_async(yctx); } } } yutani_close(yctx, wina); return 0; } ================================================ FILE: apps/pong.c ================================================ /** * @brief pong - Window Manager Pong * * Play pong where the paddles and ball are all windows. * Use the WM bindings to drag the left paddle to play. * Press `q` to quit. * * Rendering updates are all done by the compositor, while the game * only renders to the windows once at start up. * * Window movement tracking keeps the game logic aware of the paddle * position, and window moves for the ball and other paddle keep * things in the right place visually. * * @copyright * 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 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #define GAME_PATH "/usr/share/games/pong" #define PADDLE_WIDTH 50 #define PADDLE_HEIGHT 300 #define BALL_SIZE 50 static yutani_t * yctx; static int spkr = 0; struct object { double x; double y; int width; int height; double vel_x; double vel_y; sprite_t sprite; }; static yutani_window_t * paddle_left; static yutani_window_t * paddle_right; static yutani_window_t * ball_win; static struct object left; static struct object right; static struct object ball; static gfx_context_t * paddle_left_ctx; static gfx_context_t * paddle_right_ctx; static gfx_context_t * ball_ctx; static int should_exit = 0; static int left_score = 0; static int right_score = 0; struct spkr { int length; int frequency; }; static void note(int frequency) { struct spkr s = { .length = 2, .frequency = frequency, }; write(spkr, &s, sizeof(s)); } static uint32_t current_time() { static uint32_t start_time = 0; static uint32_t start_subtime = 0; struct timeval t; gettimeofday(&t, NULL); if (!start_time) { start_time = t.tv_sec; start_subtime = t.tv_usec; } uint32_t sec_diff = t.tv_sec - start_time; uint32_t usec_diff = t.tv_usec - start_subtime; if (t.tv_usec < (int)start_subtime) { sec_diff -= 1; usec_diff = (1000000 + t.tv_usec) - start_subtime; } return (uint32_t)(sec_diff * 1000 + usec_diff / 1000); } static int colliding(struct object * a, struct object * b) { if (a->x >= b->x + b->width) return 0; if (a->y >= b->y + b->height) return 0; if (b->x >= a->x + a->width) return 0; if (b->y >= a->y + a->height) return 0; return 1; } void redraw(void) { draw_fill(paddle_left_ctx, rgba(0,0,0,0)); draw_fill(paddle_right_ctx, rgba(0,0,0,0)); draw_fill(ball_ctx, rgba(0,0,0,0)); draw_sprite(paddle_left_ctx, &left.sprite, 0, 0); draw_sprite(paddle_right_ctx, &right.sprite, 0, 0); draw_sprite(ball_ctx, &ball.sprite, 0, 0); yutani_flip(yctx, paddle_left); yutani_flip(yctx, paddle_right); yutani_flip(yctx, ball_win); } void update_left(void) { yutani_window_move(yctx, paddle_left, left.x, left.y); } void update_right(void) { yutani_window_move(yctx, paddle_right, right.x, right.y); } void update_ball(void) { yutani_window_move(yctx, ball_win, ball.x, ball.y); } void update_stuff(void) { right.vel_y = (right.y + right.height / 2 < ball.y + ball.height / 2) ? 2.0 : -2.0; right.y += right.vel_y; update_right(); ball.x += ball.vel_x; ball.y += ball.vel_y; if (ball.y < 0) { ball.vel_y = -ball.vel_y; ball.y = 0; } if (ball.y > yctx->display_height - ball.height) { ball.vel_y = -ball.vel_y; ball.y = yctx->display_height - ball.height; } if (ball.x < 0) { ball.x = yctx->display_width / 2 - ball.width / 2; ball.y = yctx->display_height / 2 - ball.height / 2; ball.vel_x = -10.0; ball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0; note(10000); right_score++; printf("%d : %d\n", left_score, right_score); } if (ball.x > yctx->display_width - ball.width ) { ball.x = yctx->display_width / 2 - ball.width / 2; ball.y = yctx->display_height / 2 - ball.height / 2; ball.vel_x = 10.0; ball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0; note(17000); left_score++; printf("%d : %d\n", left_score, right_score); } if (colliding(&ball, &left)) { ball.x = left.x + left.width + 2; ball.vel_x = (fabs(ball.vel_x) < 8.0) ? -ball.vel_x * 1.05 : -ball.vel_x; double intersect = ((ball.y + ball.height/2) - (left.y)) / ((double)left.height) - 0.5; ball.vel_y = intersect * 8.0; note(15680); } if (colliding(&ball, &right)) { ball.x = right.x - ball.width - 2; ball.vel_x = (fabs(ball.vel_x) < 8.0) ? -ball.vel_x * 1.05 : -ball.vel_x; double intersect = ((ball.y + ball.height/2) - (right.y)) / ((double)right.height/2.0); ball.vel_y = intersect * 3.0; note(11747); } update_ball(); } int main (int argc, char ** argv) { yctx = yutani_init(); left.width = PADDLE_WIDTH; left.height = PADDLE_HEIGHT; right.width = PADDLE_WIDTH; right.height = PADDLE_HEIGHT; ball.width = BALL_SIZE; ball.height = BALL_SIZE; ball.x = yctx->display_width / 2 - ball.width / 2; ball.y = yctx->display_height / 2 - ball.height / 2; left.x = 10; left.y = yctx->display_height / 2 - left.height / 2; right.x = yctx->display_width - right.width - 10; right.y = yctx->display_height / 2 - right.height / 2; paddle_left = yutani_window_create(yctx, PADDLE_WIDTH, PADDLE_HEIGHT); paddle_right = yutani_window_create(yctx, PADDLE_WIDTH, PADDLE_HEIGHT); ball_win = yutani_window_create(yctx, BALL_SIZE, BALL_SIZE); paddle_left_ctx = init_graphics_yutani(paddle_left); paddle_right_ctx = init_graphics_yutani(paddle_right); ball_ctx = init_graphics_yutani(ball_win); srand(time(NULL)); ball.vel_y = ((double)rand() / RAND_MAX) * 6.0 - 3.0; ball.vel_x = -10.0; fprintf(stderr, "Loading sprites...\n"); load_sprite(&left.sprite, GAME_PATH "/paddle-red.png"); load_sprite(&right.sprite,GAME_PATH "/paddle-blue.png"); load_sprite(&ball.sprite, GAME_PATH "/ball.png"); redraw(); update_left(); update_right(); update_ball(); uint32_t last_tick = current_time(); spkr = open("/dev/spkr", O_WRONLY); while (!should_exit) { uint32_t t = current_time(); if (t > last_tick + 10) { last_tick += 10; update_stuff(); } yutani_msg_t * m = yutani_poll_async(yctx); if (m) { switch (m->type) { case YUTANI_MSG_KEY_EVENT: { struct yutani_msg_key_event * ke = (void*)m->data; if (ke->event.key == 'q' && ke->event.action == KEY_ACTION_DOWN) { should_exit = 1; } } break; case YUTANI_MSG_WINDOW_MOVE: { struct yutani_msg_window_move * wm = (void*)m->data; if (wm->wid == paddle_left->wid) { /* Update paddle speed and position */ left.y = (double)wm->y; if (wm->x != (int)left.x) { update_left(); } } } 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) { if (me->wid == paddle_left->wid) { yutani_window_drag_start(yctx, paddle_left); } } } break; case YUTANI_MSG_SESSION_END: should_exit = 1; break; default: break; } free(m); } else { sched_yield(); } } yutani_close(yctx, paddle_left); yutani_close(yctx, paddle_right); yutani_close(yctx, ball_win); return 0; } ================================================ FILE: apps/prompt_and_delete.krk ================================================ #!/bin/kuroko import kuroko import os import _waitpid let count = len(kuroko.argv) if count < 3: print("expected at least two arguments") return 1 let callbackPid = int(kuroko.argv[1]) if callbackPid < 1: print("suspicious callback pid:", callbackPid) return 1 count -= 2 def show_dialog(msg,title="File Browser",icon="/usr/share/icons/48/folder.png"): let pid = os.fork() if pid == 0: os.execvp('showdialog',['showdialog',title,icon,msg]) os.exit(1) while True: let result = _waitpid.waitpid(pid) if result[0] != pid: continue return result[1] == 0 if show_dialog(f'This will permanently delete {count} file{"s" if count != 1 else ""}.'): for file in kuroko.argv[2:]: try: os.remove(file) except Exception as e: if not show_dialog(f'An error occured while trying to delete "{file}":\n{e.arg}'): break os.kill(callbackPid, 21) # SIGURG else: return 1 ================================================ FILE: apps/ps.c ================================================ /** * @brief Print a list of running processes. * * The listed processes are limited to ones owned by the current * user and are listed in PID order. Various options allow for * threads to be shown separately, extra information to be * included in the output, etc. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #define LINE_LEN 4096 static int show_all = 0; static int show_threads = 0; static int show_username = 0; static int show_mem = 0; static int show_cpu = 0; static int show_time = 0; static int collect_commandline = 0; static int widths[] = {3,3,4,3,3,4,4,4}; struct process { int uid; int pid; int tid; int mem; int vsz; int shm; int cpu; unsigned long time; char * process; char * command_line; }; static hashmap_t * process_ents = NULL; void print_username(int uid) { struct passwd * p = getpwuid(uid); if (p) { printf("%-8s", p->pw_name); } else { printf("%-8d", uid); } endpwent(); } struct process * process_from_pid(pid_t pid) { return hashmap_get(process_ents, (void*)(uintptr_t)pid); } struct process * process_entry(struct dirent *dent) { char tmp[300]; FILE * f; char line[LINE_LEN]; int pid = 0, uid = 0, tgid = 0, mem = 0, shm = 0, vsz = 0, cpu = 0; unsigned long ttime = 0; char name[100]; sprintf(tmp, "/proc/%s/status", dent->d_name); f = fopen(tmp, "r"); if (!f) { return NULL; } line[0] = 0; while (fgets(line, LINE_LEN, f) != NULL) { char * n = strstr(line,"\n"); if (n) { *n = '\0'; } char * tab = strstr(line,"\t"); if (tab) { *tab = '\0'; tab++; } if (strstr(line, "Pid:") == line) { pid = atoi(tab); } else if (strstr(line, "Uid:") == line) { uid = atoi(tab); } else if (strstr(line, "Tgid:") == line) { tgid = atoi(tab); } else if (strstr(line, "Name:") == line) { strcpy(name, tab); } else if (strstr(line, "VmSize:") == line) { vsz = atoi(tab); } else if (strstr(line, "RssShmem:") == line) { shm = atoi(tab); } else if (strstr(line, "MemPermille:") == line) { mem = atoi(tab); } else if (strstr(line, "CpuPermille:") == line) { cpu = atoi(tab); } else if (strstr(line, "TotalTime:") == line) { ttime = strtoul(tab,NULL,0); } } fclose(f); if (!show_all) { /* Filter not ours */ if (uid != getuid()) return NULL; } if (!show_threads) { if (tgid != pid) { /* Add this thread's CPU usage to the parent */ struct process * parent = process_from_pid(tgid); if (parent) { parent->cpu += cpu; parent->time += ttime; } return NULL; } } struct process * out = malloc(sizeof(struct process)); out->uid = uid; out->pid = tgid; out->tid = pid; out->mem = mem; out->shm = shm; out->vsz = vsz; out->cpu = cpu; out->time = ttime; out->process = strdup(name); out->command_line = NULL; hashmap_set(process_ents, (void*)(uintptr_t)pid, out); char garbage[1024]; int len; if ((len = sprintf(garbage, "%d", out->pid)) > widths[0]) widths[0] = len; if ((len = sprintf(garbage, "%d", out->tid)) > widths[1]) widths[1] = len; if ((len = sprintf(garbage, "%d", out->vsz)) > widths[3]) widths[3] = len; if ((len = sprintf(garbage, "%d", out->shm)) > widths[4]) widths[4] = len; if ((len = sprintf(garbage, "%d.%01d", out->mem / 10, out->mem % 10)) > widths[5]) widths[5] = len; if ((len = sprintf(garbage, "%d.%01d", out->cpu / 10, out->cpu % 10)) > widths[6]) widths[6] = len; if ((len = sprintf(garbage, "%lu:%02lu.%02lu", (out->time / (1000000UL * 60 * 60)), (out->time / (1000000UL * 60)) % 60, (out->time / (1000000UL)) % 60)) > widths[7]) widths[7] = len; struct passwd * p = getpwuid(out->uid); if (p) { if ((len = strlen(p->pw_name)) > widths[2]) widths[2] = len; } else { if ((len = sprintf(garbage, "%d", out->uid)) > widths[2]) widths[2] = len; } endpwent(); if (collect_commandline) { sprintf(tmp, "/proc/%s/cmdline", dent->d_name); f = fopen(tmp, "r"); char foo[1024]; int s = fread(foo, 1, 1024, f); if (s > 0) { out->command_line = malloc(s + 1); memset(out->command_line, 0, s + 1); memcpy(out->command_line, foo, s); for (int i = 0; i < s; ++i) { if (out->command_line[i] == 30) { out->command_line[i] = ' '; } } } fclose(f); } return out; } void print_header(void) { if (show_username) { printf("%-*s ", widths[2], "USER"); } printf("%*s ", widths[0], "PID"); if (show_threads) { printf("%*s ", widths[1], "TID"); } if (show_cpu) { printf("%*s ", widths[6], "%CPU"); } if (show_mem) { printf("%*s ", widths[5], "%MEM"); printf("%*s ", widths[3], "VSZ"); printf("%*s ", widths[4], "SHM"); } if (show_time) { printf("%*s ", widths[7], "TIME"); } printf("CMD\n"); } void print_entry(struct process * out) { if (show_username) { struct passwd * p = getpwuid(out->uid); if (p) { printf("%-*s ", widths[2], p->pw_name); } else { printf("%-*d ", widths[2], out->uid); } endpwent(); } printf("%*d ", widths[0], out->pid); if (show_threads) { printf("%*d ", widths[1], out->tid); } if (show_cpu) { char tmp[10]; sprintf(tmp, "%*d.%01d", widths[6]-2, out->cpu / 10, out->cpu % 10); printf("%*s ", widths[6], tmp); } if (show_mem) { char tmp[10]; sprintf(tmp, "%*d.%01d", widths[5]-2, out->mem / 10, out->mem % 10); printf("%*s ", widths[5], tmp); printf("%*d ", widths[3], out->vsz); printf("%*d ", widths[4], out->shm); } if (show_time) { char tmp[30]; sprintf(tmp, "%lu:%02lu.%02lu", (out->time / (1000000UL * 60 * 60)), (out->time / (1000000UL * 60)) % 60, (out->time / (1000000UL)) % 60); printf("%*s ", widths[7], tmp); } if (out->command_line) { printf("%s\n", out->command_line); } else { printf("%s\n", out->process); } } void show_usage(int argc, char * argv[]) { printf( "ps - list running processes\n" "\n" "usage: %s [-A] [format]\n" "\n" " -A \033[3mshow other users' processes\033[0m\n" " -T \033[3mshow threads\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n" " [format] supports some BSD options:\n" "\n" " a \033[3mshow full command line\033[0m\n" " u \033[3muse 'user-oriented' format\033[0m\n" "\n", argv[0]); } int main (int argc, char * argv[]) { /* Parse arguments */ int c; while ((c = getopt(argc, argv, "AT?")) != -1) { switch (c) { case 'A': show_all = 1; break; case 'T': show_threads = 1; break; case '?': show_usage(argc, argv); return 0; } } if (optind < argc) { char * show = argv[optind]; while (*show) { switch (*show) { case 'u': show_username = 1; show_mem = 1; show_cpu = 1; show_time = 1; // fallthrough case 'a': collect_commandline = 1; break; default: break; } show++; } } /* Open the directory */ DIR * dirp = opendir("/proc"); /* Read the entries in the directory */ list_t * ents_list = list_create(); process_ents = hashmap_create_int(10); struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') { struct process * p = process_entry(ent); if (p) { list_insert(ents_list, (void *)p); } } ent = readdir(dirp); } closedir(dirp); print_header(); foreach(entry, ents_list) { print_entry(entry->value); } return 0; } ================================================ FILE: apps/pstree.c ================================================ /** * @brief pstree - Display a tree of running process * * @copyright * 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-2018 K. Lange */ #include #include #include #include #include #include #include #include #include #include typedef struct process { int pid; int ppid; int tgid; char name[100]; char path[200]; } p_t; #define LINE_LEN 4096 p_t * build_entry(struct dirent * dent) { char tmp[300]; FILE * f; char line[LINE_LEN]; sprintf(tmp, "/proc/%s/status", dent->d_name); f = fopen(tmp, "r"); p_t * proc = malloc(sizeof(p_t)); while (fgets(line, LINE_LEN, f) != NULL) { char * n = strstr(line,"\n"); if (n) { *n = '\0'; } char * tab = strstr(line,"\t"); if (tab) { *tab = '\0'; tab++; } if (strstr(line, "Pid:") == line) { proc->pid = atoi(tab); } else if (strstr(line, "PPid:") == line) { proc->ppid = atoi(tab); } else if (strstr(line, "Tgid:") == line) { proc->tgid = atoi(tab); } else if (strstr(line, "Name:") == line) { strcpy(proc->name, tab); } else if (strstr(line, "Path:") == line) { strcpy(proc->path, tab); } } if (strstr(proc->name,"python") == proc->name) { char * name = proc->path + strlen(proc->path) - 1; while (1) { if (*name == '/') { name++; break; } if (name == proc->name) break; name--; } memcpy(proc->name, name, strlen(name)+1); } if (proc->tgid != proc->pid) { char tmp[100] = {0}; sprintf(tmp, "{%s}", proc->name); memcpy(proc->name, tmp, strlen(tmp)+1); } fclose(f); return proc; } uint8_t find_pid(void * proc_v, void * pid_v) { p_t * p = proc_v; pid_t i = (pid_t)(uintptr_t)pid_v; return (uint8_t)(p->pid == i); } void print_process_tree_node(tree_node_t * node, size_t depth, int indented, int more, char lines[]) { p_t * proc = node->value; for (int i = 0; i < (int)strlen(proc->name)+3; ++i) { lines[depth+i] = 0; } if (!indented && depth) { if (more) { printf("─┬─"); lines[depth+1] = 1; } else { printf("───"); } depth += 3; } else if (depth) { for (int i = 0; i < (int)depth; ++i) { if (lines[i]) { printf("│"); } else { printf(" "); } } if (more) { printf(" ├─"); lines[depth+1] = 1; } else { printf(" └─"); } depth += 3; } printf(proc->name); if (!node->children->length) { printf("\n"); } else { depth += strlen(proc->name); int t = 0; foreach(child, node->children) { /* Recursively print the children */ print_process_tree_node(child->value, depth, !!(t), ((t+1)!=(int)node->children->length), lines); t++; } } for (int i = 0; i < (int)strlen(proc->name)+3; ++i) { lines[depth+i] = 0; } } int main (int argc, char * argv[]) { /* Open the directory */ DIR * dirp = opendir("/proc"); /* Read the entries in the directory */ tree_t * procs = tree_create(); struct dirent * ent = readdir(dirp); while (ent != NULL) { if (ent->d_name[0] >= '0' && ent->d_name[0] <= '9') { p_t * proc = build_entry(ent); if (proc->ppid == 0 && proc->pid == 1) { tree_set_root(procs, proc); } else { tree_node_t * parent = tree_find(procs,(void *)(uintptr_t)proc->ppid,find_pid); if (parent) { tree_node_insert_child(procs, parent, proc); } } } ent = readdir(dirp); } closedir(dirp); char lines[500] = {0}; print_process_tree_node(procs->root, 0, 0, 0, lines); return 0; } ================================================ FILE: apps/pwd.c ================================================ /** * @brief Print the current working directory * * @copyright * 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 */ #include #include int main(int argc, char * argv[]) { char tmp[1024]; if (getcwd(tmp, 1023)) { puts(tmp); return 0; } else { return 1; } } ================================================ FILE: apps/qemu-display-hack.c ================================================ /** * @brief qemu-display-hack - Manage display size under QEMU * * XXX Does this still work? Does the TTY interface interfere * with the operation of the communication pipe? * * Communicates with a harness on the host running QEMU to * automatically update the display resolution when the * QEMU window size changes, similar to how VirtualBox's * display size changing works. * * @copyright * 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 */ #include #include #include #include #include #include #include #include int main(int argc, char * argv[]) { if (system("qemu-fwcfg -q opt/org.toaruos.displayharness") != 0) { fprintf(stderr, "%s: display harness not enabled\n", argv[0]); return 1; } int fd = open("/dev/fb0", O_RDONLY); if (fd < 0) { fprintf(stderr, "%s: failed to open framebuffer: %s\n", argv[0], strerror(errno)); return 1; } struct vid_size s; FILE * f = fopen("/dev/ttyS1","r+"); if (!f) { fprintf(stderr, "%s: failed to open serial: %s\n", argv[0], strerror(errno)); return 1; } if (!fork()) { while (!feof(f)) { char data[128]; fgets(data, 128, f); char * linefeed = strstr(data,"\n"); if (linefeed) { *linefeed = '\0'; } char * width; char * height; width = strstr(data, " "); if (width) { *width = '\0'; width++; } else { continue; /* bad line */ } height = strstr(width, " "); if (height) { *height = '\0'; height++; } else { continue; /* bad line */ } s.width = atoi(width); s.height = atoi(height); ioctl(fd, IO_VID_SET, &s); fprintf(f, "X"); fflush(f); } return 0; } return 0; } ================================================ FILE: apps/qemu-fwcfg.c ================================================ /** * @brief qemu-fwcfg - Read QEMU fwcfg values. * * Provides easy access to values and files set by QEMU's -fw_cfg * flag. This is used by the QEMU harness, as well as the bootloader, * and can be used to provide files directly to the guest. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include #define FW_CFG_PORT_OUT 0x510 #define FW_CFG_PORT_IN 0x511 #define FW_CFG_SELECT_QEMU 0x0000 #define FW_CFG_SELECT_LIST 0x0019 static int port_fd = -1; /* outw / inb helper functions */ static void outports(unsigned short _port, unsigned short _data) { pwrite(port_fd, &_data, 2, _port); } static unsigned char inportb(unsigned short _port) { unsigned char out; pread(port_fd, &out, 1, _port); return out; } /* Despite primarily emulating x86, these are all big-endian */ static void swap_bytes(void * in, int count) { char * bytes = in; if (count == 4) { uint32_t * t = in; *t = (bytes[0] << 24) | (bytes[1] << 12) | (bytes[2] << 8) | bytes[3]; } else if (count == 2) { uint16_t * t = in; *t = (bytes[0] << 8) | bytes[1]; } } /* Layout of the information returned from the fw_cfg port */ struct fw_cfg_file { uint32_t size; uint16_t select; uint16_t reserved; char name[56]; }; static int usage(char * argv[]) { printf( "Obtain QEMU fw_cfg values\n" "\n" "usage: %s [-?ln] [config name]\n" "\n" " -l \033[3mlist available config entries\033[0m\n" " -n \033[3mdon't print a new line after data\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); return 1; } static void sig_pass(int sig) { exit(1); } int main(int argc, char * argv[]) { uint32_t count = 0; uint8_t * bytes = (uint8_t *)&count; int found = 0; struct fw_cfg_file file; uint8_t * tmp = (uint8_t *)&file; int opt = 0; int list = 0; int no_newline = 0; int query_quietly = 0; while ((opt = getopt(argc, argv, "?lnq")) != -1) { switch (opt) { case '?': return usage(argv); case 'n': no_newline = 1; break; case 'q': query_quietly = 1; break; case 'l': list = 1; break; } } if (optind >= argc && !list) { return usage(argv); } port_fd = open("/dev/port", O_RDWR); if (port_fd < 0) { /* Try the special aarch64 interface. */ port_fd = open("/dev/fwcfg", O_RDWR); } if (port_fd < 0) { fprintf(stderr, "%s: could not open port IO device\n", argv[0]); return 1; } signal(SIGILL, sig_pass); /* First check for QEMU */ outports(FW_CFG_PORT_OUT, FW_CFG_SELECT_QEMU); if (inportb(FW_CFG_PORT_IN) != 'Q' || inportb(FW_CFG_PORT_IN) != 'E' || inportb(FW_CFG_PORT_IN) != 'M' || inportb(FW_CFG_PORT_IN) != 'U') { fprintf(stderr, "%s: this doesn't seem to be qemu\n", argv[0]); return 1; } /* Then get the list of "files" so we can look at names */ outports(FW_CFG_PORT_OUT, FW_CFG_SELECT_LIST); for (int i = 0; i < 4; ++i) { bytes[i] = inportb(FW_CFG_PORT_IN); } swap_bytes(&count, sizeof(count)); for (unsigned int i = 0; i < count; ++i) { /* read one file entry */ for (unsigned int j = 0; j < sizeof(struct fw_cfg_file); ++j) { tmp[j] = inportb(FW_CFG_PORT_IN); } /* endian swap to get file size and selector ID */ swap_bytes(&file.size, sizeof(file.size)); swap_bytes(&file.select, sizeof(file.select)); if (list) { /* 0x0020 org/whatever (1234 bytes) */ fprintf(stdout, "0x%04x %s (%d byte%s)\n", file.select, file.name, (int)file.size, file.size == 1 ? "" : "s"); } else { if (!strcmp(file.name, argv[optind])) { /* found the requested file */ found = 1; break; } } } if (query_quietly) { return !found; } if (found) { /* if we found the requested file, read it from the port */ outports(FW_CFG_PORT_OUT, file.select); for (unsigned int i = 0; i < file.size; ++i) { fputc(inportb(FW_CFG_PORT_IN), stdout); } if (!no_newline) { fprintf(stdout, "\n"); } else { fflush(stdout); } } else if (!list) { fprintf(stderr, "%s: config option not found\n", argv[0]); return 1; } return 0; } ================================================ FILE: apps/readelf.c ================================================ /** * @file readelf.c * @brief Display information about a 64-bit Elf binary or object. * * Implementation of a `readelf` utility. * * I've tried to get the output here as close to the binutils format * as possible, so it should be compatible with tools that try to * parse that. Most of the same arguments should also be supported. * * This is a rewrite of an earlier version. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2020-2021 K. Lange */ #include #include #include #include #include #ifdef __toaru__ #include #else #include "../base/usr/include/kernel/elf.h" #endif #define SHOW_FILE_HEADER 0x0001 #define SHOW_SECTION_HEADERS 0x0002 #define SHOW_PROGRAM_HEADERS 0x0004 #define SHOW_SYMBOLS 0x0008 #define SHOW_DYNAMIC 0x0010 #define SHOW_RELOCATIONS 0x0020 static const char * elf_classToStr(unsigned char ei_class) { static char buf[64]; switch (ei_class) { case ELFCLASS32: return "ELF32"; case ELFCLASS64: return "ELF64"; default: sprintf(buf, "unknown (%d)", ei_class); return buf; } } static const char * elf_dataToStr(unsigned char ei_data) { static char buf[64]; switch (ei_data) { case ELFDATA2LSB: return "2's complement, little endian"; case ELFDATA2MSB: return "2's complement, big endian"; default: sprintf(buf, "unknown (%d)", ei_data); return buf; } } static char * elf_versionToStr(unsigned char ei_version) { static char buf[64]; switch (ei_version) { case 1: return "1 (current)"; default: sprintf(buf, "unknown (%d)", ei_version); return buf; } } static char * elf_osabiToStr(unsigned char ei_osabi) { static char buf[64]; switch (ei_osabi) { case 0: return "UNIX - System V"; case 1: return "HP-UX"; case 255: return "Standalone"; default: sprintf(buf, "unknown (%d)", ei_osabi); return buf; } } static char * elf_typeToStr(Elf64_Half type) { static char buf[64]; switch (type) { case ET_NONE: return "NONE (No file type)"; case ET_REL: return "REL (Relocatable object file)"; case ET_EXEC: return "EXEC (Executable file)"; case ET_DYN: return "DYN (Shared object file)"; case ET_CORE: return "CORE (Core file)"; default: sprintf(buf, "unknown (%d)", type); return buf; } } static char * elf_machineToStr(Elf64_Half machine) { static char buf[64]; switch (machine) { case EM_X86_64: return "Advanced Micro Devices X86-64"; default: sprintf(buf, "unknown (%d)", machine); return buf; } } static char * sectionHeaderTypeToStr(Elf64_Word type) { static char buf[64]; switch (type) { case SHT_NULL: return "NULL"; case SHT_PROGBITS: return "PROGBITS"; case SHT_SYMTAB: return "SYMTAB"; case SHT_STRTAB: return "STRTAB"; case SHT_RELA: return "RELA"; case SHT_HASH: return "HASH"; case SHT_DYNAMIC: return "DYNAMIC"; case SHT_NOTE: return "NOTE"; case SHT_NOBITS: return "NOBITS"; case SHT_REL: return "REL"; case SHT_SHLIB: return "SHLIB"; case SHT_DYNSYM: return "DYNSYM"; case 0xE: return "INIT_ARRAY"; case 0xF: return "FINI_ARRAY"; case 0x6ffffff6: return "GNU_HASH"; case 0x6ffffffe: return "VERNEED"; case 0x6fffffff: return "VERSYM"; default: sprintf(buf, "(%x)", type); return buf; } } static char * programHeaderTypeToStr(Elf64_Word type) { static char buf[64]; switch (type) { case PT_NULL: return "NULL"; case PT_LOAD: return "LOAD"; case PT_DYNAMIC: return "DYNAMIC"; case PT_INTERP: return "INTERP"; case PT_NOTE: return "NOTE"; case PT_PHDR: return "PHDR"; case PT_GNU_EH_FRAME: return "GNU_EH_FRAME"; case 0x6474e553: return "GNU_PROPERTY"; case 0x6474e551: return "GNU_STACK"; case 0x6474e552: return "GNU_RELRO"; default: sprintf(buf, "(%x)", type); return buf; } } static char * programHeaderFlagsToStr(Elf64_Word flags) { static char buf[10]; snprintf(buf, 10, "%c%c%c ", (flags & PF_R) ? 'R' : ' ', (flags & PF_W) ? 'W' : ' ', (flags & PF_X) ? 'E' : ' '); /* yes, E, not X... */ return buf; } static char * dynamicTagToStr(Elf64_Dyn * dynEntry, char * dynstr) { static char buf[1024]; static char extra[500]; char * name = NULL; sprintf(extra, "0x%lx", dynEntry->d_un.d_val); switch (dynEntry->d_tag) { case DT_NULL: name = "(NULL)"; break; case DT_NEEDED: name = "(NEEDED)"; sprintf(extra, "[shared lib = %s]", dynstr + dynEntry->d_un.d_val); break; case DT_PLTRELSZ: name = "(PLTRELSZ)"; break; case DT_PLTGOT: name = "(PLTGOT)"; break; case DT_HASH: name = "(HASH)"; break; case DT_STRTAB: name = "(STRTAB)"; break; case DT_SYMTAB: name = "(SYMTAB)"; break; case DT_RELA: name = "(RELA)"; break; case DT_RELASZ: name = "(RELASZ)"; break; case DT_RELAENT: name = "(RELAENT)"; break; case DT_STRSZ: name = "(STRSZ)"; sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val); break; case DT_SYMENT: name = "(SYMENT)"; sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val); break; case DT_INIT: name = "(INIT)"; break; case DT_FINI: name = "(FINI)"; break; case DT_SONAME: name = "(SONAME)"; break; case DT_RPATH: name = "(RPATH)"; break; case DT_SYMBOLIC: name = "(SYMBOLIC)"; break; case DT_REL: name = "(REL)"; break; case DT_RELSZ: name = "(RELSZ)"; sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val); break; case DT_RELENT: name = "(RELENT)"; break; case DT_PLTREL: name = "(PLTREL)"; sprintf(extra, "%s", dynEntry->d_un.d_val == DT_REL ? "REL" : "RELA"); break; case DT_DEBUG: name = "(DEBUG)"; break; case DT_TEXTREL: name = "(TEXTREL)"; break; case DT_JMPREL: name = "(JMPREL)"; break; case DT_BIND_NOW: name = "(BIND_NOW)"; break; case DT_INIT_ARRAY: name = "(INIT_ARRAY)"; break; case DT_FINI_ARRAY: name = "(FINI_ARRAY)"; break; case DT_INIT_ARRAYSZ: name = "(INIT_ARRAYSZ)"; sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val); break; case DT_FINI_ARRAYSZ: name = "(FINI_ARRASZ)"; sprintf(extra, "%ld (bytes)", dynEntry->d_un.d_val); break; case 0x1E: name = "(FLAGS)"; break; case 0x6ffffef5: name = "(GNU_HASH)"; break; case 0x6ffffffb: name = "(FLAGS_1)"; break; case 0x6ffffffe: name = "(VERNEED)"; break; case 0x6fffffff: name = "(VERNEEDNUM)"; sprintf(extra, "%ld", dynEntry->d_un.d_val); break; case 0x6ffffff0: name = "(VERSYM)"; break; case 0x6ffffff9: name = "(RELACOUNT)"; snprintf(extra, 500, "%ld", dynEntry->d_un.d_val); break; default: name = "(unknown)"; break; } snprintf(buf, 1024, "%-15s %s", name, extra); return buf; } static char * relocationInfoToStr(Elf64_Xword info) { #define CASE(o) case o: return #o; switch (info) { CASE(R_X86_64_NONE) CASE(R_X86_64_64) CASE(R_X86_64_PC32) CASE(R_X86_64_GOT32) CASE(R_X86_64_PLT32) CASE(R_X86_64_COPY) CASE(R_X86_64_GLOB_DAT) CASE(R_X86_64_JUMP_SLOT) CASE(R_X86_64_RELATIVE) CASE(R_X86_64_GOTPCREL) CASE(R_X86_64_32) CASE(R_X86_64_32S) CASE(R_X86_64_DTPMOD64) CASE(R_X86_64_DTPOFF64) CASE(R_X86_64_TPOFF64) CASE(R_X86_64_TLSGD) CASE(R_X86_64_TLSLD) CASE(R_X86_64_DTPOFF32) CASE(R_X86_64_GOTTPOFF) CASE(R_X86_64_TPOFF32) CASE(R_X86_64_PC64) CASE(R_X86_64_GOTOFF64) CASE(R_X86_64_GOTPC32) CASE(R_X86_64_GOT64) CASE(R_X86_64_GOTPCREL64) CASE(R_X86_64_GOTPC64) CASE(R_X86_64_GOTPLT64) CASE(R_X86_64_PLTOFF64) CASE(R_X86_64_SIZE32) CASE(R_X86_64_SIZE64) CASE(R_X86_64_GOTPC32_TLSDESC) CASE(R_X86_64_TLSDESC_CALL) CASE(R_X86_64_TLSDESC) CASE(R_X86_64_IRELATIVE) default: return "unknown"; } #undef CASE } static char * symbolTypeToStr(int type) { static char buf[10]; switch (type) { case STT_NOTYPE: return "NOTYPE"; case STT_OBJECT: return "OBJECT"; case STT_FUNC: return "FUNC"; case STT_SECTION: return "SECTION"; case STT_FILE: return "FILE"; default: sprintf(buf, "%x", type); return buf; } } static char * symbolBindToStr(int bind) { static char buf[10]; switch (bind) { case STB_LOCAL: return "LOCAL"; case STB_GLOBAL: return "GLOBAL"; case STB_WEAK: return "WEAK"; default: sprintf(buf, "%x", bind); return buf; } } static char * symbolVisToStr(int vis) { static char buf[10]; switch (vis) { case 0: return "DEFAULT"; case 1: return "INTERNAL"; case 2: return "HIDDEN"; case 3: return "PROTECTED"; default: sprintf(buf, "%x", vis); return buf; } } static char * symbolNdxToStr(int ndx) { static char buf[10]; switch (ndx) { case 0: return "UND"; case 65521: return "ABS"; default: sprintf(buf, "%d", ndx); return buf; } } static int usage(char * argv[]) { fprintf(stderr, "Usage: %s elf-file(s)\n" " Displays information about ELF object files in a GNU binutils-compatible way.\n" " Supported options:\n" " -a --all Equivalent to -h -l -S -s -d -r\n" " -h --file-header Display the ELF file header\n" " -l --program-headers Display the program headers\n" " -S --section-headers Display the section headers\n" " -e --headers Equivalent to -h -l -S\n" " -s --syms Display symbol table\n" " -d --dynamic Display dynamic section\n" " -r --relocs Display relocations\n" " -H --help Show this help text\n" " Aliases:\n" " --segments Same as --file-header\n" " --sections Same as --section-headers\n" " --symbols Same as --syms\n" , argv[0]); return 1; } struct StringTable { size_t length; char strings[]; }; static struct StringTable * load_string_table(FILE * f, Elf64_Shdr * header) { struct StringTable * out = malloc(sizeof(struct StringTable) + header->sh_size + 1); out->length = header->sh_size; fseek(f, header->sh_offset, SEEK_SET); fread(out->strings, header->sh_size, 1, f); out->strings[out->length] = 0; return out; } static const char * string_from_table(struct StringTable * table, size_t offset) { if (offset >= table->length) return "(out of range)"; return table->strings + offset; } int main(int argc, char * argv[]) { static struct option long_opts[] = { {"all", no_argument, 0, 'a'}, {"file-header", no_argument, 0, 'h'}, {"program-headers", no_argument, 0, 'l'}, {"section-headers", no_argument, 0, 'S'}, {"headers", no_argument, 0, 'e'}, {"syms", no_argument, 0, 's'}, {"dynamic", no_argument, 0, 'd'}, {"relocs", no_argument, 0, 'r'}, {"help", no_argument, 0, 'H'}, {"segments", no_argument, 0, 'l'}, /* Alias for --program-headers */ {"sections", no_argument, 0, 'S'}, /* Alias for --section-headers */ {"symbols", no_argument, 0, 's'}, /* Alias for --syms */ {0,0,0,0} }; int show_bits = 0; int index, c; while ((c = getopt_long(argc, argv, "ahlSesdrH", long_opts, &index)) != -1) { if (!c) { if (long_opts[index].flag == 0) { c = long_opts[index].val; } } switch (c) { case 'H': return usage(argv); case 'a': show_bits |= SHOW_FILE_HEADER | SHOW_SECTION_HEADERS | SHOW_PROGRAM_HEADERS | SHOW_SYMBOLS | SHOW_DYNAMIC | SHOW_RELOCATIONS; break; case 'd': show_bits |= SHOW_DYNAMIC; break; case 'h': show_bits |= SHOW_FILE_HEADER; break; case 'l': show_bits |= SHOW_PROGRAM_HEADERS; break; case 'S': show_bits |= SHOW_SECTION_HEADERS; break; case 'e': show_bits |= SHOW_FILE_HEADER | SHOW_PROGRAM_HEADERS | SHOW_SECTION_HEADERS; break; case 's': show_bits |= SHOW_SYMBOLS; break; case 'r': show_bits |= SHOW_RELOCATIONS; break; default: fprintf(stderr, "Unrecognized option: %c\n", c); break; } } if (optind >= argc || !show_bits) { return usage(argv); } int out = 0; int print_names = 0; if (optind + 1 < argc) { print_names = 1; } for (; optind < argc; optind++) { FILE * f = fopen(argv[optind],"r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); out = 1; continue; } if (print_names) { printf("\nFile: %s\n", argv[optind]); } /** * Validate header. */ Elf64_Header header; fread(&header, sizeof(Elf64_Header), 1, f); if (memcmp("\x7F" "ELF",&header,4)) { fprintf(stderr, "%s: %s: not an elf\n", argv[0], argv[optind]); out = 1; continue; } if (show_bits & SHOW_FILE_HEADER) { printf("ELF Header:\n"); printf(" Magic: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n", header.e_ident[0], header.e_ident[1], header.e_ident[2], header.e_ident[3], header.e_ident[4], header.e_ident[5], header.e_ident[6], header.e_ident[7], header.e_ident[8], header.e_ident[9], header.e_ident[10], header.e_ident[11], header.e_ident[12], header.e_ident[13], header.e_ident[14], header.e_ident[15]); printf(" Class: %s\n", elf_classToStr(header.e_ident[EI_CLASS])); printf(" Data: %s\n", elf_dataToStr(header.e_ident[EI_DATA])); printf(" Version: %s\n", elf_versionToStr(header.e_ident[EI_VERSION])); printf(" OS/ABI: %s\n", elf_osabiToStr(header.e_ident[EI_OSABI])); printf(" ABI Version: %u\n", header.e_ident[EI_ABIVERSION]); } if (header.e_ident[EI_CLASS] != ELFCLASS64) { continue; } if (show_bits & SHOW_FILE_HEADER) { /* Byte-order dependent from here on out... */ printf(" Type: %s\n", elf_typeToStr(header.e_type)); printf(" Machine: %s\n", elf_machineToStr(header.e_machine)); printf(" Version: 0x%x\n", header.e_version); printf(" Entry point address: 0x%lx\n", header.e_entry); printf(" Start of program headers: %lu (bytes into file)\n", header.e_phoff); printf(" Start of section headers: %lu (bytes into file)\n", header.e_shoff); printf(" Flags: 0x%x\n", header.e_flags); printf(" Size of this header: %u (bytes)\n", header.e_ehsize); printf(" Size of program headers: %u (bytes)\n", header.e_phentsize); printf(" Number of program headers: %u\n", header.e_phnum); printf(" Size of section headers: %u (bytes)\n", header.e_shentsize); printf(" Number of section headers: %u\n", header.e_shnum); printf(" Section header string table index: %u\n", header.e_shstrndx); } /* Get the section header string table */ Elf64_Shdr shstr_hdr; fseek(f, header.e_shoff + header.e_shentsize * header.e_shstrndx, SEEK_SET); fread(&shstr_hdr, sizeof(Elf64_Shdr), 1, f); struct StringTable * stringTable = load_string_table(f, &shstr_hdr); /** * Section Headers */ if (show_bits & SHOW_SECTION_HEADERS) { printf("\nSection Headers:\n"); printf(" [Nr] Name Type Address Offset\n"); printf(" Size EntSize Flags Link Info Align\n"); for (unsigned int i = 0; i < header.e_shnum; ++i) { fseek(f, header.e_shoff + header.e_shentsize * i, SEEK_SET); Elf64_Shdr sectionHeader; fread(§ionHeader, sizeof(Elf64_Shdr), 1, f); printf(" [%2d] %-17.17s %-16.16s %016lx %08lx\n", i, string_from_table(stringTable, sectionHeader.sh_name), sectionHeaderTypeToStr(sectionHeader.sh_type), sectionHeader.sh_addr, sectionHeader.sh_offset); printf(" %016lx %016lx %4ld %6d %5d %5ld\n", sectionHeader.sh_size, sectionHeader.sh_entsize, sectionHeader.sh_flags, sectionHeader.sh_link, sectionHeader.sh_info, sectionHeader.sh_addralign); } } /** * Program Headers */ if (show_bits & SHOW_PROGRAM_HEADERS && header.e_phoff && header.e_phnum) { printf("\nProgram Headers:\n"); printf(" Type Offset VirtAddr PhysAddr\n"); printf(" FileSiz MemSiz Flags Align\n"); for (unsigned int i = 0; i < header.e_phnum; ++i) { fseek(f, header.e_phoff + header.e_phentsize * i, SEEK_SET); Elf64_Phdr programHeader; fread(&programHeader, sizeof(Elf64_Phdr), 1, f); printf(" %-14.14s 0x%016lx 0x%016lx 0x%016lx\n", programHeaderTypeToStr(programHeader.p_type), programHeader.p_offset, programHeader.p_vaddr, programHeader.p_paddr); printf(" 0x%016lx 0x%016lx %s 0x%lx\n", programHeader.p_filesz, programHeader.p_memsz, programHeaderFlagsToStr(programHeader.p_flags), programHeader.p_align); if (programHeader.p_type == PT_INTERP) { /* Read interpreter string */ char * tmp = malloc(programHeader.p_filesz); fseek(f, programHeader.p_offset, SEEK_SET); fread(tmp, programHeader.p_filesz, 1, f); printf(" [Requesting program interpreter: %.*s]\n", (int)programHeader.p_filesz, tmp); free(tmp); } } } /* TODO Section to segment mapping? */ /** * Dump section information. */ for (unsigned int i = 0; i < header.e_shnum; ++i) { fseek(f, header.e_shoff + header.e_shentsize * i, SEEK_SET); Elf64_Shdr sectionHeader; fread(§ionHeader, sizeof(Elf64_Shdr), 1, f); if (sectionHeader.sh_size > 0x40000000) { /* Suspiciously large section header... */ continue; } /* I think there should only be one of these... */ switch (sectionHeader.sh_type) { case SHT_DYNAMIC: if (show_bits & SHOW_DYNAMIC) { printf("\nDynamic section at offset 0x%lx contains (up to) %ld entries:\n", sectionHeader.sh_offset, sectionHeader.sh_size / sectionHeader.sh_entsize); printf(" Tag Type Name/Value\n"); /* Read the linked string table */ Elf64_Shdr dynstr; fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET); fread(&dynstr, sizeof(Elf64_Shdr), 1, f); char * dynStr = malloc(dynstr.sh_size); fseek(f, dynstr.sh_offset, SEEK_SET); fread(dynStr, dynstr.sh_size, 1, f); char * dynTable = malloc(sectionHeader.sh_size); fseek(f, sectionHeader.sh_offset, SEEK_SET); fread(dynTable, sectionHeader.sh_size, 1, f); for (unsigned int i = 0; i < sectionHeader.sh_size / sectionHeader.sh_entsize; i++) { Elf64_Dyn * dynEntry = (Elf64_Dyn *)(dynTable + sectionHeader.sh_entsize * i); printf(" 0x%016lx %s\n", dynEntry->d_tag, dynamicTagToStr(dynEntry, dynStr)); if (dynEntry->d_tag == DT_NULL) break; } free(dynStr); free(dynTable); } break; case SHT_RELA: if (show_bits & SHOW_RELOCATIONS) { printf("\nRelocation section '%s' at offset 0x%lx contains %ld entries.\n", string_from_table(stringTable, sectionHeader.sh_name), sectionHeader.sh_offset, sectionHeader.sh_size / sizeof(Elf64_Rela)); printf(" Offset Info Type Sym. Value Sym. Name + Addend\n"); /* Section this relocation is in */ Elf64_Shdr shdr_this; fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_info, SEEK_SET); fread(&shdr_this, sizeof(Elf64_Shdr), 1, f); /* Symbol table link */ Elf64_Shdr shdr_symtab; fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET); fread(&shdr_symtab, sizeof(Elf64_Shdr), 1, f); Elf64_Sym * symtab = malloc(shdr_symtab.sh_size); fseek(f, shdr_symtab.sh_offset, SEEK_SET); fread(symtab, shdr_symtab.sh_size, 1, f); /* Symbol table's string table link */ Elf64_Shdr shdr_strtab; fseek(f, header.e_shoff + header.e_shentsize * shdr_symtab.sh_link, SEEK_SET); fread(&shdr_strtab, sizeof(Elf64_Shdr), 1, f); struct StringTable * strtab = load_string_table(f, &shdr_strtab); /* Load relocations from file */ Elf64_Rela * relocations = malloc(sectionHeader.sh_size); fseek(f, sectionHeader.sh_offset, SEEK_SET); fread((void*)relocations, sectionHeader.sh_size, 1, f); for (unsigned int i = 0; i < sectionHeader.sh_size / sizeof(Elf64_Rela); ++i) { Elf64_Shdr shdr; size_t offset = ELF64_R_SYM(relocations[i].r_info); Elf64_Xword value = 42; printf("%012lx %012lx %-15.15s ", relocations[i].r_offset, relocations[i].r_info, relocationInfoToStr(ELF64_R_TYPE(relocations[i].r_info))); const char * symName = "(null)"; if (!offset) { printf(" "); } else if (offset < shdr_symtab.sh_size) { Elf64_Sym * this = &symtab[offset]; /* Get symbol name for this relocation */ if ((this->st_info & 0xF) == STT_SECTION) { fseek(f, header.e_shoff + header.e_shentsize * this->st_shndx, SEEK_SET); fread(&shdr, sizeof(Elf64_Shdr), 1, f); symName = string_from_table(stringTable, shdr.sh_name); } else { symName = string_from_table(strtab, this->st_name); } value = this->st_value + relocations[i].r_addend; printf("%016lx %s +", value, symName); } printf(" %lx\n", relocations[i].r_addend); } free(relocations); free(strtab); free(symtab); } break; case SHT_DYNSYM: case SHT_SYMTAB: if (show_bits & SHOW_SYMBOLS) { printf("\nSymbol table '%s' contains %ld entries.\n", string_from_table(stringTable, sectionHeader.sh_name), sectionHeader.sh_size / sizeof(Elf64_Sym)); printf(" Num: Value Size Type Bind Vis Ndx Name\n"); Elf64_Sym * symtab = malloc(sectionHeader.sh_size); fseek(f, sectionHeader.sh_offset, SEEK_SET); fread(symtab, sectionHeader.sh_size, 1, f); Elf64_Shdr shdr_strtab; fseek(f, header.e_shoff + header.e_shentsize * sectionHeader.sh_link, SEEK_SET); fread(&shdr_strtab, sizeof(Elf64_Shdr), 1, f); struct StringTable * strtab = load_string_table(f, &shdr_strtab); for (unsigned int i = 0; i < sectionHeader.sh_size / sizeof(Elf64_Sym); ++i) { printf("%6u: %016lx %6lu %-7.7s %-6.6s %-7.7s %4s %s\n", i, symtab[i].st_value, symtab[i].st_size, symbolTypeToStr(symtab[i].st_info & 0xF), symbolBindToStr(symtab[i].st_info >> 4), symbolVisToStr(symtab[i].st_other), symbolNdxToStr(symtab[i].st_shndx), string_from_table(strtab, symtab[i].st_name)); } free(strtab); free(symtab); } break; default: break; } } free(stringTable); } return out; } ================================================ FILE: apps/readlink.c ================================================ /** * @brief Examine symlinks and print the path they point to. * * @copyright * 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 Mike Gerow */ #include #include #include #define MAX_LINK_SIZE 4096 static char usage[] = "Usage: %s LINK\n"; int main(int argc, char * argv[]) { if (argc != 2) { fprintf(stderr, usage, argv[0]); exit(EXIT_FAILURE); } char * name = argv[1]; char buf[MAX_LINK_SIZE]; if (readlink(name, buf, sizeof(buf)) < 0) { //perror("link"); exit(EXIT_FAILURE); } fprintf(stdout, "%s\n", buf); exit(EXIT_SUCCESS); } ================================================ FILE: apps/reboot.c ================================================ /** * @brief (Try to) reboot the system. * * Note that only root can syscall_reboot, and this doesn't * do any fancy setuid stuff. * * @copyright * 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-2014 K. Lange */ #include #include int main(int argc, char ** argv) { if (syscall_reboot() < 0) { printf("%s: permission denied\n", argv[0]); } return 1; } ================================================ FILE: apps/reload_desktop.sh ================================================ #!/bin/esh export-cmd DESKTOP cat /var/run/.wallpaper.pid if not empty? "$DESKTOP" then kill -SIGUSR2 $DESKTOP ================================================ FILE: apps/reset.c ================================================ /** * @brief reset - make the terminal sane, and clear it * * Also clears scrollback! * * @copyright * 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-2021 K. Lange */ #include #include int main(int argc, char * argv[]) { system("stty sane"); printf("\033c\033[H\033[2J\033[3J"); fflush(stdout); return 0; } ================================================ FILE: apps/rm.c ================================================ /** * @brief rm - Unlink files * * TODO: Support recursive, directory removal, etc. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #ifndef IS_MV #define APP_NAME "rm" static int recursive = 0; #endif static int rm_thing(char * tmp); static int rm_directory(char * source) { DIR * dirp = opendir(source); if (dirp == NULL) { fprintf(stderr, "could not open %s\n", source); return 1; } struct dirent * ent = readdir(dirp); while (ent != NULL) { if (!strcmp(ent->d_name,".") || !strcmp(ent->d_name,"..")) { ent = readdir(dirp); continue; } char tmp[strlen(source)+strlen(ent->d_name)+2]; sprintf(tmp, "%s/%s", source, ent->d_name); int status = rm_thing(tmp); if (status) return status; rewinddir(dirp); ent = readdir(dirp); } closedir(dirp); int res = unlink(source); if (res < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", source, strerror(errno)); return 1; } return 0; } static int rm_thing(char * tmp) { struct stat statbuf; lstat(tmp,&statbuf); if (S_ISDIR(statbuf.st_mode)) { if (!recursive) { fprintf(stderr, APP_NAME ": %s: is a directory\n", tmp); return 1; } return rm_directory(tmp); } else { int res = unlink(tmp); if (res < 0) { fprintf(stderr, APP_NAME ": %s: %s\n", tmp, strerror(errno)); return 1; } return 0; } } #ifndef IS_MV static int rm_top_level(char **argv, int argc, int optind) { int ret = 0; for (int i = optind; i < argc; ++i) { ret |= rm_thing(argv[i]); } return ret; } int main(int argc, char * argv[]) { int opt; while ((opt = getopt(argc, argv, "fr")) != -1) { switch (opt) { case 'r': recursive = 1; break; case 'f': /* ignore */ break; default: fprintf(stderr, "rm: unrecognized option '%c'\n", opt); break; } } return rm_top_level(argv, argc, optind); } #endif ================================================ FILE: apps/rmdir.c ================================================ /** * @brief rmdir - remove empty directories * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2026 K. Lange */ #include #include #include #include #include static int usage(char * argv[]) { #define _I "\033[3m" #define _E "\033[0m\n" fprintf(stderr, "usage: %s [-p] path...\n" "\n" "Deletes empty directories.\n" "\n" " -p " _I "Remove parents if also empty" _E " -v " _I "Print directory names when they are successfully removed" _E "\n", argv[0]); #undef _I #undef _E return 1; } int main(int argc, char * argv[]) { int parents = 0; int verbose = 0; int opt; while ((opt = getopt(argc, argv, "pv")) != -1) { switch (opt) { case 'p': parents = 1; break; case 'v': verbose = 1; break; default: return usage(argv); } } if (optind == argc) return usage(argv); int ret = 0; while (optind < argc) { if (rmdir(argv[optind]) < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); ret |= 1; optind++; continue; } if (verbose) fprintf(stdout, "%s\n", argv[optind]); if (parents) { char * parent = dirname(argv[optind]); while (parent && strcmp(parent,".") && strcmp(parent,"/")) { if (rmdir(parent) < 0) { fprintf(stderr, "%s: %s: %s\n", argv[0], parent, strerror(errno)); ret |= 1; break; } if (verbose) fprintf(stdout, "%s\n", parent); parent = dirname(parent); } } optind++; } return ret; } ================================================ FILE: apps/serial-console.c ================================================ /** * @brief serial console * * Old tool for poking serial ports. Probably doesn't work right * anymore since serial ports are now behind PTYs. * * @copyright * 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-2014 K. Lange */ #include #include #include #include #include #include #include #include #include int fd = 0; int keep_echo = 0; int dos_lines = 0; int keep_canon = 0; struct termios old; void set_unbuffered() { tcgetattr(fileno(stdin), &old); struct termios new = old; if (!keep_canon) { new.c_lflag &= (~ICANON); } if (!keep_echo) { new.c_lflag &= (~ECHO); } tcsetattr(fileno(stdin), TCSAFLUSH, &new); } void set_buffered() { tcsetattr(fileno(stdin), TCSAFLUSH, &old); } int show_usage(int argc, char * argv[]) { printf( "Serial client.\n" "\n" "usage: %s [-e] [-r] [-c] [device path]\n" "\n" " -e \033[3mkeep echo enabled\033[0m\n" " -c \033[3mkeep canon enabled\033[0m\n" " -r \033[3mtransform line feeds to \\r\\n\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); return 1; } int main(int argc, char ** argv) { int arg = 1; char * device; while (arg < argc) { if (argv[arg][0] != '-') break; if (!strcmp(argv[arg], "-e")) { keep_echo = 1; } else if (!strcmp(argv[arg], "-r")) { dos_lines = 1; } else if (!strcmp(argv[arg], "-c")) { keep_canon = 1; } else if (!strcmp(argv[arg], "-?")) { return show_usage(argc, argv); } else { fprintf(stderr, "%s: Unrecognized option: %s\n", argv[0], argv[arg]); } arg++; } if (arg == argc) { device = "/dev/ttyS0"; } else { device = argv[arg]; } set_unbuffered(); fd = open(device, 0, 0); int fds[2] = {STDIN_FILENO, fd}; while (1) { int index = fswait(2, fds); if (index == -1) { fprintf(stderr, "serial-console: fswait: erroneous file descriptor\n"); fprintf(stderr, "serial-console: (did you try to open a file that isn't a serial console?\n"); return 1; } if (index == 0) { char c = fgetc(stdin); if (c == 0x1D) { /* ^] */ while (1) { printf("serial-console> "); set_buffered(); fflush(stdout); char line[1024]; fgets(line, 1024, stdin); if (feof(stdin)) { return 0; } int i = strlen(line); line[i-1] = '\0'; if (!strcmp(line, "quit")) { return 0; } else if (!strcmp(line, "continue")) { set_unbuffered(); fflush(stdout); break; } } } else { if (dos_lines && c == '\n') { char buf[1] = {'\r'}; write(fd, buf, 1); } char buf[1] = {c}; write(fd, buf, 1); } } else { char buf[1024]; size_t r = read(fd, buf, 1024); fwrite(buf, 1, r, stdout); fflush(stdout); } } close(fd); set_buffered(); return 0; } ================================================ FILE: apps/session.c ================================================ /** * @brief session - UI session manager * * Runs the user's yutanirc or starts up a panel and desktop * if they don't have one. Generally run by glogin. * * @copyright * 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 */ #include #include #include #include #include #include int main(int argc, char * argv[]) { char path[1024]; char * home = getenv("HOME"); if (home) { sprintf(path, "%s/.yutanirc", home); char * args[] = {path, NULL}; execvp(args[0], args); } /* Fallback */ int _background_pid = fork(); if (!_background_pid) { sprintf(path, "%s/Desktop", home); chdir(path); char * args[] = {"/bin/file-browser", "--wallpaper", NULL}; execvp(args[0], args); } int _panel_pid = fork(); if (!_panel_pid) { char * args[] = {"/bin/panel", "--really", NULL}; execvp(args[0], args); } wait(NULL); int pid; do { pid = waitpid(-1, NULL, 0); } while ((pid > 0) || (pid == -1 && errno == EINTR)); return 0; } ================================================ FILE: apps/set-resolution.c ================================================ /** * @brief set-resolution - Change the display resolution. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include int main(int argc, char * argv[]) { if (argc < 3) { fprintf(stderr, "Usage: %s [--initialize DRIVER] WIDTH HEIGHT\n", argv[0]); return 1; } /* Open framebuffer */ int fd = open("/dev/fb0", O_RDONLY); if (fd < 0) { perror("open"); return 1; } int i = 1; int init = 0; char * driver = NULL; if (argc > 4 && !strcmp(argv[1],"--initialize")) { init = 1; driver = argv[2]; i += 2; } /* Prepare ioctl from arguments */ struct vid_size s; s.width = atoi(argv[i]); s.height = atoi(argv[i+1]); /* Send ioctl */ if (init) { char tmp[100]; sprintf(tmp, "%s,%u,%u", driver, s.width, s.height); if (ioctl(fd, IO_VID_REINIT, tmp) < 0) { perror("ioctl"); return 1; } } else { if (ioctl(fd, IO_VID_SET, &s) < 0) { perror("ioctl"); return 1; } } return 0; } ================================================ FILE: apps/set-wallpaper.sh ================================================ #!/bin/esh if empty? "$1" then exec sh -c "echo 'usage: $0 WALLPAPER'" if not stat -Lq "$1" then exec sh -c "echo '$0: $1 does not exist'" export-cmd DESKTOP cat /var/run/.wallpaper.pid if empty? "$DESKTOP" then sh -c "echo '$0: No wallpaper running?'" echo "wallpaper=$1" > $HOME/.wallpaper.conf kill -SIGUSR1 $DESKTOP ================================================ FILE: apps/show-toasts.krk ================================================ #!/bin/kuroko import kuroko import os import fileio import time time.sleep(2) try: let meminfo = {} let data with fileio.open('/proc/meminfo','r') as f: data = f.readlines() for line in data: if not ':' in line: continue let bits = line.strip().split(':',1) meminfo[bits[0].strip()] = bits[1].strip() if 'MemTotal' in meminfo: let kB = int(meminfo['MemTotal'].replace('kB','')) if kB < 1000000: let sock = os.open("/dev/pex/toast",os.O_WRONLY) let msg = '{"icon":"/usr/share/icons/48/help.png","body":"Low System Memory
At least 1GiB of RAM is
recommended for the Live CD.
' + str(kB//1024) + 'MiB was detected."}' os.write(sock,msg.encode()) try: let manifest with fileio.open('/var/msk/manifest','r') as f: manifest = f.readlines() let count = 0 for line in manifest: if line.startswith('['): count++ if count: let sock = os.open("/dev/pex/toast",os.O_WRONLY) let msg = '{"icon":"/usr/share/icons/48/package.png","body":"Packages Available
' + str(count) + ' package' + ('s' if count > 1 else '') + ' are available from
the package repository."}' os.write(sock,msg.encode()) return 0 ================================================ FILE: apps/show-tutorial.sh ================================================ #!/bin/sh touch ~/.tutorial-shown sh -c "sleep 1; tutorial" & ================================================ FILE: apps/showdialog.c ================================================ /** * @brief showdialog - show a window with a dialog prompt with buttons * * @copyright * 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 */ #include #include #include #include #include #include #include #define BUTTON_HEIGHT 28 #define BUTTON_WIDTH 86 #define BUTTON_PADDING 14 static yutani_t * yctx; static yutani_window_t * window = NULL; static gfx_context_t * ctx = NULL; static sprite_t logo; static int32_t width = 600; static int32_t height = 150; static char * icon_path; static char * title_str; static char * copyright_str[20] = {NULL}; static struct TT_Font * _tt_font = NULL; static void draw_string(int y, const char * string, uint32_t color) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); tt_set_size(_tt_font, 13); tt_draw_string(ctx, _tt_font, bounds.left_width + 80, bounds.top_height + 30 + y + 13, string, color); } struct TTKButton _ok = {0}; struct TTKButton _cancel = {0}; 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 + 20, bounds.top_height + 20); int offset = 0; 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, rgb(0,0,255)); offset += 20; } else { draw_string(offset, *copy_str, rgb(0,0,0)); offset += 20; } } ttk_button_draw(ctx, &_ok); ttk_button_draw(ctx, &_cancel); 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 = "Dialog Prompt"; icon_path = "/usr/share/icons/48/folder.png"; copyright_str[0] = "This is a demonstration of a dialog box."; copyright_str[1] = "You can press \"Okay\" or \"Cancel\" or close the window."; } int in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) { if (me->new_y >= button->y && me->new_y < button->y + button->height) { if (me->new_x >= button->x && me->new_x < button->x + button->width) { return 1; } } return 0; } void setup_buttons(void) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); _ok.title = "Okay"; _ok.width = BUTTON_WIDTH; _ok.height = BUTTON_HEIGHT; _ok.x = ctx->width - bounds.right_width - BUTTON_WIDTH - BUTTON_PADDING; _ok.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING; _cancel.title = "Cancel"; _cancel.width = BUTTON_WIDTH; _cancel.height = BUTTON_HEIGHT; _cancel.x = ctx->width - bounds.right_width - BUTTON_WIDTH * 2 - BUTTON_PADDING * 2; _cancel.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING; } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, window, w, h); reinit_graphics_yutani(ctx, window); width = w; height = h; setup_buttons(); redraw(); yutani_window_resize_done(yctx, window); } void set_hilight(struct TTKButton * button, int hilight) { if (!button && (_ok.hilight || _cancel.hilight)) { _ok.hilight = 0; _cancel.hilight = 0; redraw(); } else if (button && (button->hilight != hilight)) { _ok.hilight = 0; _cancel.hilight = 0; button->hilight = hilight; redraw(); } } 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); _tt_font = tt_font_from_shm("sans-serif"); window = yutani_window_create_flags(yctx, width + bounds.width, height + bounds.height, YUTANI_WINDOW_FLAG_DIALOG_ANIMATION); req_center_x = yctx->display_width / 2; req_center_y = yctx->display_height / 2; if (argc < 2) { init_default(); } else if (argc < 4) { fprintf(stderr, "Invalid arguments.\n"); return 1; } else { title_str = argv[1]; icon_path = argv[2]; int i = 0; char * me = argv[3], * 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); setup_buttons(); load_sprite(&logo, icon_path); redraw(); struct TTKButton * _down_button = NULL; int playing = 1; int status = 0; 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 == '\n') { playing = 0; status = 0; } else if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == KEY_ESCAPE) { playing = 0; status = 2; } } 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*)(uintptr_t)wf->wid); if (win) { win->focused = wf->focused; redraw(); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; if (me->wid == window->wid) { int result = decor_handle_event(yctx, m); switch (result) { case DECOR_CLOSE: playing = 0; status = 2; 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; } struct decor_bounds bounds; decor_get_bounds(window, &bounds); if (me->new_y > bounds.top_height) { if (me->command == YUTANI_MOUSE_EVENT_DOWN) { if (in_button(&_ok, me)) { set_hilight(&_ok, 2); _down_button = &_ok; } else if (in_button(&_cancel, me)) { set_hilight(&_cancel, 2); _down_button = &_cancel; } } else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) { if (_down_button) { if (in_button(_down_button, me)) { if (_down_button == &_cancel) { playing = 0; status = 1; break; } else if (_down_button == &_ok) { playing = 0; status = 0; break; } _down_button->hilight = 0; } } _down_button = NULL; } if (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { if (in_button(&_ok, me)) { set_hilight(&_ok, 1); } else if (in_button(&_cancel, me)) { set_hilight(&_cancel, 1); } else { set_hilight(NULL,0); } } else if (_down_button) { if (in_button(_down_button, me)) { set_hilight(_down_button, 2); } else { set_hilight(NULL, 0); } } } } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: playing = 0; status = 2; break; default: break; } free(m); m = yutani_poll_async(yctx); } } yutani_close(yctx, window); return status; } ================================================ FILE: apps/sleep.c ================================================ /** * @brief sleep - Do nothing, efficiently. * * @copyright * 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 */ #include #include #include #include int main(int argc, char ** argv) { int ret = 0; if (argc < 2) { fprintf(stderr, "%s: missing operand\n", argv[0]); return 1; } char * arg = strdup(argv[1]); float time = atof(arg); unsigned int seconds = (unsigned int)time; unsigned int subsecs = (unsigned int)((time - (float)seconds) * 100); ret = syscall_sleep(seconds, subsecs); return ret; } ================================================ FILE: apps/snow.c ================================================ /** * @brief Draw pretty falling snowflakes. * * @copyright * 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 */ #include #include #include #include #include #include #include #include #include #include static yutani_t * yctx; static yutani_window_t * wina; static gfx_context_t * ctx; static int should_exit = 0; static sprite_t snowflake; #define FLAKES 40 #define FALL_SPEED 3 struct { int16_t x; int16_t y; uint8_t rotation; uint8_t alpha; int8_t wind; uint8_t exists; } flakes[FLAKES]; int flakes_made = 0; static void add_flake() { for (int i = 0; i < FLAKES; ++i) { if (!flakes[i].exists) { flakes[i].exists = 1; flakes[i].y = -snowflake.height / 2; flakes[i].x = rand() % (ctx->width); flakes[i].alpha = rand() % 50 + 50; flakes[i].rotation = rand() % 255; flakes[i].wind = (rand() % 6) - 3; return; } } } static void draw(void) { draw_fill(ctx, 0); for (int i = 0; i < FLAKES; ++i) { if (flakes[i].exists) { draw_sprite_rotate(ctx, &snowflake, flakes[i].x, flakes[i].y, (float)(flakes[i].rotation) / 100.0, (float)(flakes[i].alpha) / 100.0); flakes[i].y += FALL_SPEED; flakes[i].x += flakes[i].wind; if (flakes[i].y >= ctx->height + snowflake.height / 2 || flakes[i].x <= -snowflake.width / 2 || flakes[i].x >= ctx->width + snowflake.width / 2) { flakes[i].exists = 0; add_flake(); } } } flip(ctx); yutani_flip(yctx, wina); } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, wina, w, h); reinit_graphics_yutani(ctx, wina); draw(); yutani_window_resize_done(yctx, wina); } uint64_t last_flake = 0; static uint64_t precise_current_time(void) { struct timeval t; gettimeofday(&t, NULL); time_t sec_diff = t.tv_sec; suseconds_t usec_diff = t.tv_usec; return (uint64_t)(sec_diff * 1000 + usec_diff / 1000); } static uint64_t precise_time_since(uint64_t start_time) { uint32_t now = precise_current_time(); uint32_t diff = now - start_time; /* Milliseconds */ return diff; } int main (int argc, char ** argv) { srand(time(NULL)); memset(&flakes, 0, sizeof(flakes)); yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]); return 1; } load_sprite(&snowflake, "/usr/share/snowflake.bmp"); wina = yutani_window_create(yctx, 100, 100); if (argc < 2 || strcmp(argv[1],"--no-ad")) { yutani_window_advertise(yctx, wina, "snow"); } yutani_special_request(yctx, wina, YUTANI_SPECIAL_REQUEST_MAXIMIZE); yutani_window_update_shape(yctx, wina, 256); ctx = init_graphics_yutani_double_buffer(wina); draw_fill(ctx, rgba(0,0,0,0)); flip(ctx); while (!should_exit) { int fds[1] = {fileno(yctx->sock)}; int index = fswait2(1,fds,10); if (index == 0) { yutani_msg_t * m = yutani_poll(yctx); while (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_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: should_exit = 1; break; default: break; } free(m); m = yutani_poll_async(yctx); } } else { if (flakes_made < 20 && precise_time_since(last_flake) > 1000) { add_flake(); flakes_made += 1; last_flake = precise_current_time(); } } draw(); } yutani_close(yctx, wina); return 0; } ================================================ FILE: apps/sort.c ================================================ /** * @brief Sort input lines. * * XXX for reasons unknown this is using its own insertion-sort * instead of our much nicer quicksort? * * @copyright * 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 */ #include #include #include #include #include #include #include #include int compare(const char * a, const char * b) { while (1) { while (*a == *b || tolower(*a) == tolower(*b)) { if (!*a) return 0; a++; b++; } while (*a && !isalnum(*a)) a++; while (*b && !isalnum(*b)) b++; if (tolower(*a) == tolower(*b)) continue; if (tolower(*a) < tolower(*b)) return -1; return 1; } } int main(int argc, char * argv[]) { int reverse = 0; int opt; list_t * lines = list_create(); list_t * files = list_create(); while ((opt = getopt(argc, argv, "r")) != -1) { switch (opt) { case 'r': reverse = 1; break; } } if (optind == argc) { /* No arguments */ list_insert(files, stdin); } else { while (optind < argc) { FILE * f = fopen(argv[optind], "r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[optind], strerror(errno)); } else { list_insert(files, f); } optind++; } } char line_buf[4096] = {0}; foreach (node, files) { FILE * f = node->value; while (!feof(f)) { if (!fgets(line_buf, 4096, f)) { break; } if (!strchr(line_buf,'\n')) { fprintf(stderr, "%s: oversized line\n", argv[0]); } char * line = strdup(line_buf); node_t * next = NULL; foreach (lnode, lines) { char * cmp = lnode->value; if (reverse ? (compare(cmp, line) < 0) : (compare(line, cmp) < 0)) { next = lnode; break; } } if (next) { list_insert_before(lines, next, line); } else { list_insert(lines, line); } } } foreach (lnode, lines) { char * line = lnode->value; fprintf(stdout, "%s", line); } return 0; } ================================================ FILE: apps/splash-log.c ================================================ /** * @brief Console log manager. * * Presents a PEX endpoint for startup processes to write log * messages to and will only send them to the console if the * debug flag is set or 2 seconds have elapsed since we started. * * This also makes message writes a bit more asynchonrous, which * is useful because the framebuffer console output can be quite * slow and we don't want to slow down start up processes... * * The downside to that is that splash-log may have to play catch-up * and could still be spewing messages to the console after startup * has finished... * * This used to do a lot more work, as it managed both graphical * output of messages and output to the VGA terminal, but that has * all moved back into the kernel's 'fbterm', so now we're just * a message buffer. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TIMEOUT_SECS 2 static FILE * console; static void update_message(char * c) { fprintf(console, "%s\n", c); } static FILE * pex_endpoint = NULL; static void open_socket(void) { pex_endpoint = pex_bind("splash"); if (!pex_endpoint) exit(1); } static void say_hello(void) { /* Get our release version */ struct utsname u; uname(&u); /* Strip git tag */ char * tmp = strstr(u.release, "-"); if (tmp) *tmp = '\0'; /* Setup hello message */ char hello_msg[512]; snprintf(hello_msg, 511, "ToaruOS %s is starting up...", u.release); /* Add it to the log */ update_message(hello_msg); } #include "../kernel/misc/args.c" static hashmap_t * get_cmdline(void) { char * results = args_from_procfs(); if (results) free(results); return kernel_args_map; } int main(int argc, char * argv[]) { if (getuid() != 0) { fprintf(stderr, "%s: only root should run this\n", argv[0]); return 1; } if (!fork()) { hashmap_t * cmdline = get_cmdline(); int quiet = 0; char * last_message = NULL; clock_t start = times(NULL); if (!hashmap_has(cmdline, "debug")) { quiet = 1; } open_socket(); console = fopen("/dev/console","a"); if (!quiet) say_hello(); while (1) { int pex_fd[] = {fileno(pex_endpoint)}; int index = fswait2(1, pex_fd, 100); if (index == 0) { pex_packet_t * p = calloc(PACKET_SIZE, 1); pex_listen(pex_endpoint, p); if (p->size < 4) { free(p); continue; } /* Ignore blank messages, erroneous line feeds, etc. */ if (p->size > 80) { free(p); continue; } /* Ignore overly large messages */ if (!strncmp((char*)p->data, "!quit", 5)) { /* Use the special message !quit to exit. */ fclose(pex_endpoint); return 0; } if (!quiet) { p->data[p->size] = '\0'; update_message((char*)p->data + (p->data[0] == ':' ? 1 : 0)); } if (last_message) { free(last_message); last_message = NULL; } if (quiet) { last_message = strdup((char*)p->data + (p->data[0] == ':' ? 1 : 0)); } free(p); } else if (quiet && times(NULL) - start > TIMEOUT_SECS * 1000000L) { quiet = 0; if (last_message) { update_message("Startup is taking a while, enabling log. Last message was:"); update_message(last_message); free(last_message); last_message = NULL; } else { update_message("Startup is taking a while, enabling log."); } } } } return 0; } ================================================ FILE: apps/stat.c ================================================ /** * @brief Display file status. * * The format for this is terrible and we're missing a bunch * of data we provide in our statbuf... * * @copyright * 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 */ #include #include #include #include #include #include #include #include static void show_usage(int argc, char * argv[]) { printf( "stat - display file status\n" "\n" "usage: %s [-Lq] PATH\n" "\n" " -L \033[3mdereference symlinks\033[0m\n" " -q \033[3mdon't print anything, just return 0 if file exists\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); } static int dereference = 0, quiet = 0; static int stat_file(char * file) { struct stat _stat; int result; if (dereference) { result = stat(file, &_stat); } else { result = lstat(file, &_stat); } if (result == -1) { if (!quiet) { fprintf(stderr, "stat: %s: %s\n", file, strerror(errno)); } return 1; } if (quiet) return 0; const char * file_type = "regular file"; if (S_ISDIR(_stat.st_mode)) file_type = "directory"; else if (S_ISFIFO(_stat.st_mode)) file_type = "fifo"; else if (S_ISLNK(_stat.st_mode)) file_type = "symbolic link"; else if (S_ISBLK(_stat.st_mode)) file_type = "block device"; else if (S_ISCHR(_stat.st_mode)) file_type = "character device"; struct stat * f = &_stat; printf(" File: %s\n", file); /* TODO: st_blocks is not being set, skip it */ printf(" Size: %-10lu %s\n", f->st_size, file_type); printf("Device: %-10lu Inode: %-10lu Links: %u\n", f->st_dev, f->st_ino, f->st_nlink); printf("Access: "); /* Copied from apps/ls.c */ if (S_ISLNK(f->st_mode)) { printf("l"); } else if (S_ISCHR(f->st_mode)) { printf("c"); } else if (S_ISBLK(f->st_mode)) { printf("b"); } else if (S_ISDIR(f->st_mode)) { printf("d"); } else { printf("-"); } printf( (f->st_mode & S_IRUSR) ? "r" : "-"); printf( (f->st_mode & S_IWUSR) ? "w" : "-"); printf( (f->st_mode & S_ISUID) ? "s" : ((f->st_mode & S_IXUSR) ? "x" : "-")); printf( (f->st_mode & S_IRGRP) ? "r" : "-"); printf( (f->st_mode & S_IWGRP) ? "w" : "-"); printf( (f->st_mode & S_IXGRP) ? "x" : "-"); printf( (f->st_mode & S_IROTH) ? "r" : "-"); printf( (f->st_mode & S_IWOTH) ? "w" : "-"); printf( (f->st_mode & S_IXOTH) ? "x" : "-"); printf(" Uid: %-8u Gid: %-8u\n", f->st_uid, f->st_gid); char time_buf[80]; strftime(time_buf, 80, "%c", localtime((time_t*)&f->st_atime)); printf("Access: %s\n", time_buf); strftime(time_buf, 80, "%c", localtime((time_t*)&f->st_mtime)); printf("Modify: %s\n", time_buf); strftime(time_buf, 80, "%c", localtime((time_t*)&f->st_ctime)); printf("Change: %s\n", time_buf); return 0; } int main(int argc, char ** argv) { int opt; while ((opt = getopt(argc, argv, "?Lq")) != -1) { switch (opt) { case 'L': dereference = 1; break; case 'q': quiet = 1; break; case '?': show_usage(argc,argv); return 1; } } if (optind >= argc) { show_usage(argc, argv); return 1; } int ret = 0; while (optind < argc) { ret |= stat_file(argv[optind]); optind++; } return ret; } ================================================ FILE: apps/strace.c ================================================ /** * @brief Process system call tracer. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static FILE * logfile; /* System call names */ const char * syscall_names[] = { [SYS_EXT] = "exit", [SYS_GETEUID] = "geteuid", [SYS_OPEN] = "open", [SYS_READ] = "read", [SYS_WRITE] = "write", [SYS_CLOSE] = "close", [SYS_GETTIMEOFDAY] = "gettimeofday", [SYS_GETPID] = "getpid", [SYS_SBRK] = "sbrk", [SYS_UNAME] = "uname", [SYS_SEEK] = "seek", [SYS_STAT] = "stat", [SYS_GETUID] = "getuid", [SYS_SETUID] = "setuid", [SYS_READDIR] = "readdir", [SYS_CHDIR] = "chdir", [SYS_GETCWD] = "getcwd", [SYS_SETHOSTNAME] = "sethostname", [SYS_GETHOSTNAME] = "gethostname", [SYS_MKDIR] = "mkdir", [SYS_GETTID] = "gettid", [SYS_SYSFUNC] = "sysfunc", [SYS_IOCTL] = "ioctl", [SYS_ACCESS] = "access", [SYS_STATF] = "statf", [SYS_CHMOD] = "chmod", [SYS_UMASK] = "umask", [SYS_UNLINK] = "unlink", [SYS_MOUNT] = "mount", [SYS_SYMLINK] = "symlink", [SYS_READLINK] = "readlink", [SYS_LSTAT] = "lstat", [SYS_CHOWN] = "chown", [SYS_SETSID] = "setsid", [SYS_SETPGID] = "setpgid", [SYS_GETPGID] = "getpgid", [SYS_DUP2] = "dup2", [SYS_EXECVE] = "execve", [SYS_FORK] = "fork", [SYS_WAITPID] = "waitpid", [SYS_YIELD] = "yield", [SYS_SLEEPABS] = "sleepabs", [SYS_SLEEP] = "sleep", [SYS_PIPE] = "pipe", [SYS_FSWAIT] = "fswait", [SYS_FSWAIT2] = "fswait_timeout", [SYS_FSWAIT3] = "fswait_multi", [SYS_CLONE] = "clone", [SYS_OPENPTY] = "openpty", [SYS_SHM_OBTAIN] = "shm_obtain", [SYS_SHM_RELEASE] = "shm_release", [SYS_SIGNAL] = "signal", [SYS_KILL] = "kill", [SYS_REBOOT] = "reboot", [SYS_GETGID] = "getgid", [SYS_GETEGID] = "getegid", [SYS_SETGID] = "setgid", [SYS_GETGROUPS] = "getgroups", [SYS_SETGROUPS] = "setgroups", [SYS_TIMES] = "times", [SYS_PTRACE] = "ptrace", [SYS_SOCKET] = "socket", [SYS_SETSOCKOPT] = "setsockopt", [SYS_BIND] = "bind", [SYS_ACCEPT] = "accept", [SYS_LISTEN] = "listen", [SYS_CONNECT] = "connect", [SYS_GETSOCKOPT] = "getsockopt", [SYS_RECV] = "recv", [SYS_SEND] = "send", [SYS_SHUTDOWN] = "shutdown", [SYS_SIGACTION] = "sigaction", [SYS_SIGPENDING] = "sigpending", [SYS_SIGPROCMASK] = "sigprocmask", [SYS_SIGSUSPEND] = "sigsuspend", [SYS_SIGWAIT] = "sigwait", [SYS_PREAD] = "pread", [SYS_PWRITE] = "pwrite", [SYS_RENAME] = "rename", [SYS_FCNTL] = "fcntl", [SYS_FCHMOD] = "fchmod", [SYS_FCHOWN] = "fchown", [SYS_TRUNCATE] = "truncate", [SYS_FTRUNCATE] = "ftruncate", [SYS_SETTIMEOFDAY] = "settimeofday", [SYS_GETSOCKNAME] = "getsockname", [SYS_GETPEERNAME] = "getpeername", }; char syscall_mask[] = { [SYS_EXT] = 1, [SYS_GETEUID] = 1, [SYS_OPEN] = 1, [SYS_READ] = 1, [SYS_WRITE] = 1, [SYS_CLOSE] = 1, [SYS_GETTIMEOFDAY] = 1, [SYS_GETPID] = 1, [SYS_SBRK] = 1, [SYS_UNAME] = 1, [SYS_SEEK] = 1, [SYS_STAT] = 1, [SYS_GETUID] = 1, [SYS_SETUID] = 1, [SYS_READDIR] = 1, [SYS_CHDIR] = 1, [SYS_GETCWD] = 1, [SYS_SETHOSTNAME] = 1, [SYS_GETHOSTNAME] = 1, [SYS_MKDIR] = 1, [SYS_GETTID] = 1, [SYS_SYSFUNC] = 1, [SYS_IOCTL] = 1, [SYS_ACCESS] = 1, [SYS_STATF] = 1, [SYS_CHMOD] = 1, [SYS_UMASK] = 1, [SYS_UNLINK] = 1, [SYS_MOUNT] = 1, [SYS_SYMLINK] = 1, [SYS_READLINK] = 1, [SYS_LSTAT] = 1, [SYS_CHOWN] = 1, [SYS_SETSID] = 1, [SYS_SETPGID] = 1, [SYS_GETPGID] = 1, [SYS_DUP2] = 1, [SYS_EXECVE] = 1, [SYS_FORK] = 1, [SYS_WAITPID] = 1, [SYS_YIELD] = 1, [SYS_SLEEPABS] = 1, [SYS_SLEEP] = 1, [SYS_PIPE] = 1, [SYS_FSWAIT] = 1, [SYS_FSWAIT2] = 1, [SYS_FSWAIT3] = 1, [SYS_CLONE] = 1, [SYS_OPENPTY] = 1, [SYS_SHM_OBTAIN] = 1, [SYS_SHM_RELEASE] = 1, [SYS_SIGNAL] = 1, [SYS_KILL] = 1, [SYS_REBOOT] = 1, [SYS_GETGID] = 1, [SYS_GETEGID] = 1, [SYS_SETGID] = 1, [SYS_GETGROUPS] = 1, [SYS_SETGROUPS] = 1, [SYS_TIMES] = 1, [SYS_PTRACE] = 1, [SYS_SOCKET] = 1, [SYS_SETSOCKOPT] = 1, [SYS_BIND] = 1, [SYS_ACCEPT] = 1, [SYS_LISTEN] = 1, [SYS_CONNECT] = 1, [SYS_GETSOCKOPT] = 1, [SYS_RECV] = 1, [SYS_SEND] = 1, [SYS_SHUTDOWN] = 1, [SYS_PREAD] = 1, [SYS_PWRITE] = 1, [SYS_SIGACTION] = 1, [SYS_SIGPENDING] = 1, [SYS_SIGPROCMASK] = 1, [SYS_SIGSUSPEND] = 1, [SYS_SIGWAIT] = 1, [SYS_RENAME] = 1, [SYS_FCNTL] = 1, [SYS_FCHMOD] = 1, [SYS_FCHOWN] = 1, [SYS_TRUNCATE] = 1, [SYS_FTRUNCATE] = 1, [SYS_SETTIMEOFDAY] = 1, [SYS_GETSOCKNAME] = 1, [SYS_GETPEERNAME] = 1, }; #define M(e) [e] = #e const char * errno_names[] = { M(EPERM), M(ENOENT), M(ESRCH), M(EINTR), M(EIO), M(ENXIO), M(E2BIG), M(ENOEXEC), M(EBADF), M(ECHILD), M(EAGAIN), M(ENOMEM), M(EACCES), M(EFAULT), M(ENOTBLK), M(EBUSY), M(EEXIST), M(EXDEV), M(ENODEV), M(ENOTDIR), M(EISDIR), M(EINVAL), M(ENFILE), M(EMFILE), M(ENOTTY), M(ETXTBSY), M(EFBIG), M(ENOSPC), M(ESPIPE), M(EROFS), M(EMLINK), M(EPIPE), M(EDOM), M(ERANGE), M(ENOMSG), M(EIDRM), M(ECHRNG), M(EL2NSYNC), M(EL3HLT), M(EL3RST), M(ELNRNG), M(EUNATCH), M(ENOCSI), M(EL2HLT), M(EDEADLK), M(ENOLCK), M(EBADE), M(EBADR), M(EXFULL), M(ENOANO), M(EBADRQC), M(EBADSLT), M(EDEADLOCK), M(EBFONT), M(ENOSTR), M(ENODATA), M(ETIME), M(ENOSR), M(ENONET), M(ENOPKG), M(EREMOTE), M(ENOLINK), M(EADV), M(ESRMNT), M(ECOMM), M(EPROTO), M(EMULTIHOP), M(ELBIN), M(EDOTDOT), M(EBADMSG), M(EFTYPE), M(ENOTUNIQ), M(EBADFD), M(EREMCHG), M(ELIBACC), M(ELIBBAD), M(ELIBSCN), M(ELIBMAX), M(ELIBEXEC), M(ENOSYS), M(ENOTEMPTY), M(ENAMETOOLONG), M(ELOOP), M(EOPNOTSUPP), M(EPFNOSUPPORT), M(ECONNRESET), M(ENOBUFS), M(EAFNOSUPPORT), M(EPROTOTYPE), M(ENOTSOCK), M(ENOPROTOOPT), M(ESHUTDOWN), M(ECONNREFUSED), M(EADDRINUSE), M(ECONNABORTED), M(ENETUNREACH), M(ENETDOWN), M(ETIMEDOUT), M(EHOSTDOWN), M(EHOSTUNREACH), M(EINPROGRESS), M(EALREADY), M(EDESTADDRREQ), M(EMSGSIZE), M(EPROTONOSUPPORT), M(ESOCKTNOSUPPORT), M(EADDRNOTAVAIL), M(EISCONN), M(ENOTCONN), M(ENOTSUP), M(EOVERFLOW), M(ECANCELED), M(ENOTRECOVERABLE), M(EOWNERDEAD), M(ESTRPIPE), M(ERESTARTSYS), M(ERESTARTSIGSUSPEND), }; const char * signal_names[NSIG] = { M(SIGHUP), M(SIGINT), M(SIGQUIT), M(SIGILL), M(SIGTRAP), M(SIGABRT), M(SIGEMT), M(SIGFPE), M(SIGKILL), M(SIGBUS), M(SIGSEGV), M(SIGSYS), M(SIGPIPE), M(SIGALRM), M(SIGTERM), M(SIGUSR1), M(SIGUSR2), M(SIGCHLD), M(SIGPWR), M(SIGWINCH), M(SIGURG), M(SIGPOLL), M(SIGSTOP), M(SIGTSTP), M(SIGCONT), M(SIGTTIN), M(SIGTTOUT), M(SIGVTALRM), M(SIGPROF), M(SIGXCPU), M(SIGXFSZ), M(SIGWAITING), M(SIGDIAF), M(SIGHATE), M(SIGWINEVENT), M(SIGCAT), M(SIGTTOU), }; const char * fcntl_cmd_names[] = { M(F_GETFD), M(F_SETFD), M(F_GETFL), M(F_SETFL), M(F_DUPFD), M(F_GETLK), M(F_SETLK), M(F_SETLKW), }; static void open_flags(int flags) { if (!flags) { fprintf(logfile, "O_RDONLY"); return; } /* That's all that's valid right now */ flags &= 0xFFFF; #define H(flg) do { if (flags & flg) { fprintf(logfile, #flg); flags &= (~flg); if (flags) fprintf(logfile, "|"); } } while (0) H(O_WRONLY); H(O_RDWR); H(O_APPEND); H(O_CREAT); H(O_TRUNC); H(O_EXCL); H(O_NOFOLLOW); H(O_PATH); H(O_NONBLOCK); H(O_DIRECTORY); if (flags) { fprintf(logfile, "(%#x)", flags); } } static void string_arg(pid_t pid, uintptr_t ptr) { if (ptr == 0) { fprintf(logfile, "NULL"); return; } fprintf(logfile, "\""); size_t size = 0; uint8_t buf = 0; do { long result = ptrace(PTRACE_PEEKDATA, pid, (void*)ptr, &buf); if (result != 0) break; if (!buf) { fprintf(logfile, "\""); return; } if (buf == '\\') fprintf(logfile, "\\\\"); else if (buf == '"') fprintf(logfile, "\\\""); else if (buf >= ' ' && buf <= '~') fprintf(logfile, "%c", buf); else if (buf == '\r') fprintf(logfile, "\\r"); else if (buf == '\n') fprintf(logfile, "\\n"); else fprintf(logfile, "\\x%02x", buf); ptr++; size++; if (size > 30) break; } while (buf); fprintf(logfile, "\"..."); } #define C(arg) case arg: fprintf(logfile, #arg); break #define COMMA fprintf(logfile, ", "); static void pointer_arg(uintptr_t ptr) { if (ptr == 0) fprintf(logfile, "NULL"); else fprintf(logfile, "%#zx", ptr); } static void uint_arg(size_t val) { fprintf(logfile, "%zu", val); } static void int_arg(size_t val) { fprintf(logfile, "%zd", val); } static void mode_arg(mode_t val) { fprintf(logfile, "%#03o", val); } static void fd_arg(pid_t pid, int val) { /* TODO: Look up file in user data? */ fprintf(logfile, "%d", val); } static void sock_dom_arg(int domain) { switch (domain) { C(AF_UNSPEC); C(AF_RAW); C(AF_INET); default: fprintf(logfile, "%d", domain); break; } } static void sock_typ_arg(int type) { switch (type) { C(SOCK_DGRAM); C(SOCK_STREAM); C(SOCK_RAW); default: fprintf(logfile, "%d", type); break; } } static void sock_pro_arg(int domain, int type, int proto) { switch (domain) { case AF_INET: switch (proto) { C(IPPROTO_IP); C(IPPROTO_ICMP); C(IPPROTO_TCP); C(IPPROTO_UDP); default: fprintf(logfile, "%d", proto); break; } return; } /* Fallback case */ fprintf(logfile, "%d", proto); } static void sock_lvl_arg(int level) { switch (level) { C(SOL_SOCKET); default: fprintf(logfile, "%d", level); break; } } static void sock_opt_arg(int opt) { switch (opt) { C(SO_KEEPALIVE); C(SO_REUSEADDR); C(SO_BINDTODEVICE); default: fprintf(logfile, "%d", opt); break; } } static int data_read_bytes(pid_t pid, uintptr_t addr, char * buf, size_t size) { for (unsigned int i = 0; i < size; ++i) { if (ptrace(PTRACE_PEEKDATA, pid, (void*)addr++, &buf[i])) { return 1; } } return 0; } static int data_read_int(pid_t pid, uintptr_t addr) { int x; data_read_bytes(pid, addr, (char*)&x, sizeof(int)); return x; } static uintptr_t data_read_ptr(pid_t pid, uintptr_t addr) { uintptr_t x; data_read_bytes(pid, addr, (char*)&x, sizeof(uintptr_t)); return x; } static void sockaddr_arg(pid_t pid, uintptr_t addr, size_t size) { if (addr == 0) { fprintf(logfile, "null"); return; } struct sockaddr_storage sa = {0}; data_read_bytes(pid, addr, (char*)&sa, size < sizeof(struct sockaddr_storage) ? size : sizeof(struct sockaddr_storage)); fprintf(logfile, "{sa_family="); sock_dom_arg(sa.ss_family); if (sa.ss_family == AF_INET && size >= sizeof(struct sockaddr_in)) { struct sockaddr_in addr; memcpy(&addr, &sa, sizeof(struct sockaddr_in)); fprintf(logfile, ", sin_port=htons(%d), sin_addr=inet_addr(\"%s\")", ntohs(addr.sin_port), inet_ntoa(addr.sin_addr)); } fprintf(logfile, "}"); } static void sockaddrp_arg(pid_t pid, uintptr_t addr, uintptr_t size_p) { size_t size = 0; data_read_bytes(pid, size_p, (char*)&size, sizeof(size_t)); sockaddr_arg(pid,addr,size); } static void fds_arg(pid_t pid, size_t ecount, uintptr_t array) { fprintf(logfile, "["); for (size_t count = 0; count < 10 && count < ecount; ++count) { int x = data_read_int(pid, array); fprintf(logfile, "%d", x); if (count + 1 < ecount) fprintf(logfile, ","); array += sizeof(int); } fprintf(logfile, "]"); } static void string_array_arg(pid_t pid, uintptr_t array) { fprintf(logfile, "["); uintptr_t val = data_read_ptr(pid, array); for (size_t count = 0; count < 10; ++count) { string_arg(pid, val); array += sizeof(uintptr_t); val = data_read_ptr(pid, array); if (val) { COMMA; } else break; } fprintf(logfile, "]"); } static void buffer_arg(pid_t pid, uintptr_t buffer, ssize_t count) { if (count < 0) { fprintf(logfile, "..."); } else if (buffer == 0) { fprintf(logfile, "NULL"); } else { ssize_t x = 0; uint8_t buf = 0; fprintf(logfile, "\""); while (x < count && x < 30) { long result = ptrace(PTRACE_PEEKDATA, pid, (void*)buffer, &buf); if (result != 0) break; if (buf == '\\') fprintf(logfile, "\\\\"); else if (buf == '"') fprintf(logfile, "\\\""); else if (buf >= ' ' && buf < '~') fprintf(logfile, "%c", buf); else if (buf == '\r') fprintf(logfile, "\\r"); else if (buf == '\n') fprintf(logfile, "\\n"); else fprintf(logfile, "\\x%02x", buf); buffer++; x++; } fprintf(logfile, "\""); if (x < count) fprintf(logfile, "..."); } } static void msghdr_arg(pid_t pid, uintptr_t msghdr) { struct msghdr data = {0}; if (data_read_bytes(pid, msghdr, (char*)&data, sizeof(struct msghdr))) { fprintf(logfile, "(?)"); } else { fprintf(logfile, "{msg_name=%#zx,msg_iovlen=%zu,msg_iov[0]=", (uintptr_t)data.msg_name, data.msg_iovlen); if (data.msg_iovlen > 0) { struct iovec iodata = {0}; if (data_read_bytes(pid, (uintptr_t)data.msg_iov, (char*)&iodata, sizeof(struct iovec))) { fprintf(logfile,"?"); } else { fprintf(logfile,"{iov_base=%#zx,iov_len=%zu}", (uintptr_t)iodata.iov_base, iodata.iov_len); } } fprintf(logfile,"}"); } } static void fcntl_cmd_arg(long cmd) { const char * name = (cmd >= 0 && (size_t)cmd < (sizeof(fcntl_cmd_names) / sizeof(*fcntl_cmd_names))) ? fcntl_cmd_names[cmd] : NULL; if (name) { fprintf(logfile, "%s", name); } else { fprintf(logfile, "%ld", cmd); } } static void print_error(int err) { const char * name = (err >= 0 && (size_t)err < (sizeof(errno_names) / sizeof(*errno_names))) ? errno_names[err] : NULL; if (name) { fprintf(logfile, " %s (%s)", name, strerror(err)); } else { fprintf(logfile, " %d (%s)", err, strerror(err)); } } static void maybe_errno(struct URegs * r) { fprintf(logfile, ") = %ld", uregs_syscall_result(r)); if ((intptr_t)uregs_syscall_result(r) < 0) print_error(-uregs_syscall_result(r)); fprintf(logfile, "\n"); } static void struct_utsname_arg(pid_t pid, uintptr_t ptr) { if (!ptr) { fprintf(logfile, "NULL"); return; } fprintf(logfile, "{"); fprintf(logfile, "sysname="); string_arg(pid, ptr + offsetof(struct utsname, sysname)); COMMA; fprintf(logfile, "nodename="); string_arg(pid, ptr + offsetof(struct utsname, nodename)); COMMA; fprintf(logfile, "...}"); } static void struct_timeval_arg(pid_t pid, uintptr_t ptr) { if (!ptr) { fprintf(logfile, "NULL"); return; } fprintf(logfile, "{"); fprintf(logfile, "tv_sec="); int_arg(data_read_ptr(pid, ptr + offsetof(struct timeval, tv_sec))); COMMA; fprintf(logfile, "tv_usec="); int_arg(data_read_ptr(pid, ptr + offsetof(struct timeval, tv_usec))); fprintf(logfile, "}"); } static void signal_arg(int signum) { if (signum >= 0 && signum < NSIG && signal_names[signum]) { fprintf(logfile, "%s", signal_names[signum]); } else { fprintf(logfile, "%d", signum); } } static void sigset_ptr_arg(pid_t pid, uintptr_t ptr) { if (!ptr) { fprintf(logfile, "NULL"); return; } uint64_t sigset = data_read_ptr(pid, ptr); /* handle special cases */ if (sigset == 0xFFFFffffFFFFffff) { fprintf(logfile, "sigfillset()"); return; } else if (sigset == 0) { fprintf(logfile, "sigemptyset()"); return; } uint64_t x = (1ULL << NUMSIGNALS) - 2; /* 0 is also unused */ sigset &= x; fprintf(logfile, "{"); for (int i = 1; i < NUMSIGNALS; ++i) { uint64_t s = 1ULL << i; if (sigset & s) { signal_arg(i); sigset &= ~s; if (sigset) { fprintf(logfile, "|"); } } } fprintf(logfile, "}"); } static void signal_ptr_arg(pid_t pid, uintptr_t ptr) { int i = data_read_int(pid, ptr); fprintf(logfile, "{"); signal_arg(i); fprintf(logfile, "}"); } static void handle_syscall(pid_t pid, struct URegs * r) { if (uregs_syscall_num(r) >= sizeof(syscall_mask)) return; if (!syscall_mask[uregs_syscall_num(r)]) return; fprintf(logfile, "%s(", syscall_names[uregs_syscall_num(r)]); switch (uregs_syscall_num(r)) { case SYS_OPEN: string_arg(pid, uregs_syscall_arg1(r)); COMMA; open_flags(uregs_syscall_arg2(r)); break; case SYS_CHMOD: string_arg(pid, uregs_syscall_arg1(r)); COMMA; mode_arg(uregs_syscall_arg2(r)); break; case SYS_CHOWN: string_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); COMMA; int_arg(uregs_syscall_arg3(r)); break; case SYS_TRUNCATE: string_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); break; case SYS_READ: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; /* Plus two more when done */ break; case SYS_WRITE: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; buffer_arg(pid, uregs_syscall_arg2(r), uregs_syscall_arg3(r)); COMMA; uint_arg(uregs_syscall_arg3(r)); break; case SYS_PREAD: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; /* Plus three more when done */ break; case SYS_PWRITE: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; buffer_arg(pid, uregs_syscall_arg2(r), uregs_syscall_arg3(r)); COMMA; uint_arg(uregs_syscall_arg3(r)); COMMA; int_arg(uregs_syscall_arg4(r)); break; case SYS_CLOSE: fd_arg(pid, uregs_syscall_arg1(r)); break; case SYS_SBRK: uint_arg(uregs_syscall_arg1(r)); break; case SYS_SEEK: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); COMMA; switch (uregs_syscall_arg3(r)) { C(SEEK_SET); C(SEEK_CUR); C(SEEK_END); default: int_arg(uregs_syscall_arg3(r)); break; } break; case SYS_STATF: string_arg(pid, uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); break; case SYS_LSTAT: string_arg(pid, uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); break; case SYS_READDIR: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); COMMA; pointer_arg(uregs_syscall_arg3(r)); break; case SYS_KILL: int_arg(uregs_syscall_arg1(r)); COMMA; /* pid_arg? */ signal_arg(uregs_syscall_arg2(r)); break; case SYS_CHDIR: string_arg(pid, uregs_syscall_arg1(r)); break; case SYS_GETCWD: /* output is first arg */ pointer_arg(uregs_syscall_arg1(r)); COMMA; /* TODO syscall outputs */ uint_arg(uregs_syscall_arg2(r)); break; case SYS_CLONE: pointer_arg(uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); COMMA; pointer_arg(uregs_syscall_arg3(r)); break; case SYS_SETHOSTNAME: string_arg(pid, uregs_syscall_arg1(r)); break; case SYS_GETHOSTNAME: /* plus one more when done */ break; case SYS_MKDIR: string_arg(pid, uregs_syscall_arg1(r)); COMMA; uint_arg(uregs_syscall_arg2(r)); break; case SYS_RENAME: string_arg(pid, uregs_syscall_arg1(r)); COMMA; string_arg(pid, uregs_syscall_arg2(r)); break; case SYS_ACCESS: string_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); break; case SYS_PTRACE: switch (uregs_syscall_arg1(r)) { C(PTRACE_ATTACH); C(PTRACE_CONT); C(PTRACE_DETACH); C(PTRACE_TRACEME); C(PTRACE_GETREGS); C(PTRACE_PEEKDATA); default: int_arg(uregs_syscall_arg1(r)); break; } COMMA; int_arg(uregs_syscall_arg2(r)); COMMA; pointer_arg(uregs_syscall_arg3(r)); COMMA; pointer_arg(uregs_syscall_arg4(r)); break; case SYS_EXECVE: string_arg(pid, uregs_syscall_arg1(r)); COMMA; string_array_arg(pid, uregs_syscall_arg2(r)); COMMA; pointer_arg(uregs_syscall_arg3(r)); break; case SYS_SHM_OBTAIN: string_arg(pid, uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); break; case SYS_SHM_RELEASE: string_arg(pid, uregs_syscall_arg1(r)); break; case SYS_SIGNAL: signal_arg(uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); break; case SYS_SYSFUNC: switch (uregs_syscall_arg1(r)) { C(TOARU_SYS_FUNC_SYNC); C(TOARU_SYS_FUNC_LOGHERE); C(TOARU_SYS_FUNC_KDEBUG); C(TOARU_SYS_FUNC_INSMOD); C(TOARU_SYS_FUNC_SETHEAP); C(TOARU_SYS_FUNC_MMAP); C(TOARU_SYS_FUNC_THREADNAME); C(TOARU_SYS_FUNC_SETGSBASE); C(TOARU_SYS_FUNC_NPROC); C(TOARU_SYS_FUNC_CLEARICACHE); C(TOARU_SYS_FUNC_MUNMAP); default: int_arg(uregs_syscall_arg1(r)); break; } COMMA; pointer_arg(uregs_syscall_arg2(r)); break; case SYS_FSWAIT: int_arg(uregs_syscall_arg1(r)); COMMA; fds_arg(pid, uregs_syscall_arg1(r), uregs_syscall_arg2(r)); break; case SYS_FSWAIT2: int_arg(uregs_syscall_arg1(r)); COMMA; fds_arg(pid, uregs_syscall_arg1(r), uregs_syscall_arg2(r)); COMMA; int_arg(uregs_syscall_arg3(r)); break; case SYS_FSWAIT3: int_arg(uregs_syscall_arg1(r)); COMMA; fds_arg(pid, uregs_syscall_arg1(r), uregs_syscall_arg2(r)); COMMA; int_arg(uregs_syscall_arg3(r)); COMMA; pointer_arg(uregs_syscall_arg4(r)); break; case SYS_IOCTL: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); COMMA; pointer_arg(uregs_syscall_arg3(r)); break; case SYS_WAITPID: int_arg(uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); COMMA; int_arg(uregs_syscall_arg3(r)); /* TODO waitpid options */ break; case SYS_EXT: int_arg(uregs_syscall_arg1(r)); fprintf(logfile, ") = ?\n"); return; case SYS_UNAME: /* One output arg */ break; case SYS_SLEEPABS: uint_arg(uregs_syscall_arg1(r)); COMMA; uint_arg(uregs_syscall_arg2(r)); break; case SYS_SLEEP: uint_arg(uregs_syscall_arg1(r)); COMMA; uint_arg(uregs_syscall_arg2(r)); break; case SYS_PIPE: /* Arg is a pointer */ break; case SYS_DUP2: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; fd_arg(pid, uregs_syscall_arg2(r)); break; case SYS_FCNTL: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; fcntl_cmd_arg(uregs_syscall_arg2(r)); COMMA; int_arg(uregs_syscall_arg3(r)); break; case SYS_FCHMOD: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; mode_arg(uregs_syscall_arg2(r)); break; case SYS_FCHOWN: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); COMMA; int_arg(uregs_syscall_arg3(r)); break; case SYS_FTRUNCATE: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); break; case SYS_MOUNT: string_arg(pid, uregs_syscall_arg1(r)); COMMA; string_arg(pid, uregs_syscall_arg2(r)); COMMA; uint_arg(uregs_syscall_arg3(r)); COMMA; pointer_arg(uregs_syscall_arg4(r)); break; case SYS_UMASK: int_arg(uregs_syscall_arg1(r)); break; case SYS_UNLINK: string_arg(pid, uregs_syscall_arg1(r)); break; case SYS_GETTIMEOFDAY: /* two output args */ break; case SYS_SETTIMEOFDAY: struct_timeval_arg(pid, uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); break; case SYS_SIGACTION: signal_arg(uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); COMMA; pointer_arg(uregs_syscall_arg3(r)); /* sigaction output */ break; case SYS_SIGPENDING: /* output only */ break; case SYS_SIGPROCMASK: switch (uregs_syscall_arg1(r)) { C(SIG_BLOCK); C(SIG_UNBLOCK); C(SIG_SETMASK); default: int_arg(uregs_syscall_arg1(r)); break; } COMMA; sigset_ptr_arg(pid, uregs_syscall_arg2(r)); COMMA; /* one more after with old set */ break; case SYS_SIGSUSPEND: sigset_ptr_arg(pid, uregs_syscall_arg1(r)); break; case SYS_SIGWAIT: sigset_ptr_arg(pid, uregs_syscall_arg1(r)); COMMA; break; case SYS_SOCKET: sock_dom_arg(uregs_syscall_arg1(r)); COMMA; sock_typ_arg(uregs_syscall_arg2(r)); COMMA; sock_pro_arg(uregs_syscall_arg1(r), uregs_syscall_arg2(r), uregs_syscall_arg3(r)); break; case SYS_RECV: case SYS_SEND: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; msghdr_arg(pid, uregs_syscall_arg2(r)); COMMA; int_arg(uregs_syscall_arg3(r)); break; case SYS_LISTEN: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); break; case SYS_CONNECT: case SYS_ACCEPT: case SYS_BIND: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; sockaddr_arg(pid, uregs_syscall_arg2(r), uregs_syscall_arg3(r)); /* Consumes both */ break; case SYS_GETSOCKNAME: case SYS_GETPEERNAME: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; /* two output args */ break; case SYS_SHUTDOWN: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; int_arg(uregs_syscall_arg2(r)); /* TODO SHUT_... */ break; case SYS_SETSOCKOPT: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; sock_lvl_arg(uregs_syscall_arg2(r)); COMMA; sock_opt_arg(uregs_syscall_arg3(r)); COMMA; pointer_arg(uregs_syscall_arg4(r)); COMMA; /* TODO sockaddr in some contexts */ uint_arg(uregs_syscall_arg5(r)); break; case SYS_GETSOCKOPT: fd_arg(pid, uregs_syscall_arg1(r)); COMMA; sock_lvl_arg(uregs_syscall_arg2(r)); COMMA; sock_opt_arg(uregs_syscall_arg3(r)); COMMA; pointer_arg(uregs_syscall_arg4(r)); COMMA; /* TODO sockaddr in some contexts */ pointer_arg(uregs_syscall_arg5(r)); /* Note - differs from set */ break; /* These have no arguments: */ case SYS_YIELD: case SYS_FORK: case SYS_GETEUID: case SYS_GETPID: case SYS_GETUID: case SYS_REBOOT: case SYS_GETTID: case SYS_SETSID: case SYS_GETGID: case SYS_GETEGID: break; default: fprintf(logfile, "..."); break; } fflush(stdout); } static void finish_syscall(pid_t pid, int syscall, struct URegs * r) { if (syscall >= (int)sizeof(syscall_mask)) return; if (syscall >= 0 && !syscall_mask[syscall]) return; switch (syscall) { case -1: break; /* This is ptrace(PTRACE_TRACEME)... probably... */ /* read() returns data in second value */ case SYS_READ: buffer_arg(pid, uregs_syscall_arg2(r), uregs_syscall_result(r)); COMMA; uint_arg(uregs_syscall_arg3(r)); maybe_errno(r); break; case SYS_PREAD: buffer_arg(pid, uregs_syscall_arg2(r), uregs_syscall_result(r)); COMMA; uint_arg(uregs_syscall_arg3(r)); COMMA; int_arg(uregs_syscall_arg4(r)); maybe_errno(r); break; case SYS_GETHOSTNAME: string_arg(pid, uregs_syscall_arg1(r)); maybe_errno(r); break; case SYS_UNAME: struct_utsname_arg(pid, uregs_syscall_arg1(r)); maybe_errno(r); break; case SYS_PIPE: fds_arg(pid, 2, uregs_syscall_arg1(r)); maybe_errno(r); break; case SYS_GETTIMEOFDAY: struct_timeval_arg(pid, uregs_syscall_arg1(r)); COMMA; pointer_arg(uregs_syscall_arg2(r)); maybe_errno(r); break; /* sbrk() returns an address */ case SYS_SBRK: fprintf(logfile, ") = %#zx\n", uregs_syscall_result(r)); break; case SYS_EXECVE: if (r == NULL) fprintf(logfile, ") = 0\n"); else maybe_errno(r); break; case SYS_SIGPROCMASK: sigset_ptr_arg(pid, uregs_syscall_arg3(r)); maybe_errno(r); break; case SYS_SIGWAIT: signal_ptr_arg(pid, uregs_syscall_arg2(r)); maybe_errno(r); break; case SYS_GETSOCKNAME: case SYS_GETPEERNAME: sockaddrp_arg(pid, uregs_syscall_arg2(r), uregs_syscall_arg3(r)); /* Consumes both */ break; /* Most things return -errno, or positive valid result */ default: maybe_errno(r); break; } } static int usage(char * argv[]) { #define T_I "\033[3m" #define T_O "\033[0m" fprintf(stderr, "usage: %s [-o logfile] [-e trace=...] [-p PID] [command...]\n" " -o logfile " T_I "Write tracing output to a file." T_O "\n" " -h " T_I "Show this help text." T_O "\n" " -e trace=... " T_I "Set tracing options." T_O "\n" " -p PID " T_I "Trace an existing process." T_O "\n", argv[0]); return 1; } int main(int argc, char * argv[]) { logfile = stdout; pid_t p = 0; int opt; while ((opt = getopt(argc, argv, "ho:e:p:")) != -1) { switch (opt) { case 'p': p = atoi(optarg); break; case 'o': logfile = fopen(optarg, "w"); if (!logfile) { fprintf(stderr, "%s: %s: %s\n", argv[0], optarg, strerror(errno)); return 1; } break; case 'e': if (strstr(optarg,"trace=") == optarg) { /* First, disable everything. */ memset(syscall_mask, 0, sizeof(syscall_mask)); /* Now look at each comma-separated option */ char * option = optarg + 6; char * comma = strstr(option, ","); do { if (comma) *comma = '\0'; if (*option == '%') { /* Check for special options */ if (!strcmp(option+1,"net") || !strcmp(option+1,"network")) { int syscalls[] = { SYS_SOCKET, SYS_SETSOCKOPT, SYS_BIND, SYS_ACCEPT, SYS_LISTEN, SYS_CONNECT, SYS_GETSOCKOPT, SYS_RECV, SYS_SEND, SYS_SHUTDOWN, SYS_GETPEERNAME, SYS_GETSOCKNAME, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"file")) { int syscalls[] = { SYS_OPEN, SYS_STATF, SYS_LSTAT, SYS_ACCESS, SYS_EXECVE, SYS_GETCWD, SYS_CHDIR, SYS_MKDIR, SYS_SYMLINK, SYS_UNLINK, SYS_CHMOD, SYS_CHOWN, SYS_MOUNT, SYS_READLINK, SYS_RENAME, SYS_TRUNCATE, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"desc")) { int syscalls[] = { SYS_OPEN, SYS_READ, SYS_WRITE, SYS_CLOSE, SYS_STAT, SYS_FSWAIT, SYS_FSWAIT2, SYS_FSWAIT3, SYS_SEEK, SYS_IOCTL, SYS_PIPE, SYS_DUP2, SYS_READDIR, SYS_OPENPTY, SYS_PREAD, SYS_PWRITE, SYS_FCNTL, SYS_FCHMOD, SYS_FCHOWN, SYS_FTRUNCATE, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"memory")) { int syscalls[] = { SYS_SBRK, SYS_SHM_OBTAIN, SYS_SHM_RELEASE, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"ipc")) { int syscalls[] = { SYS_SHM_OBTAIN, SYS_SHM_RELEASE, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"signal")) { int syscalls[] = { SYS_SIGNAL, SYS_KILL, SYS_SIGACTION, SYS_SIGPENDING, SYS_SIGPROCMASK, SYS_SIGSUSPEND, SYS_SIGWAIT, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"process")) { int syscalls[] = { SYS_EXT, SYS_EXECVE, SYS_FORK, SYS_CLONE, SYS_WAITPID, SYS_KILL, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else if (!strcmp(option+1,"creds")) { int syscalls[] = { SYS_GETUID, SYS_GETGID, SYS_GETGROUPS, SYS_GETEGID, SYS_GETEUID, SYS_SETUID, SYS_SETGID, SYS_SETGROUPS, 0 }; for (int *i = syscalls; *i; i++) { syscall_mask[*i] = 1; } } else { fprintf(stderr, "%s: Unrecognized syscall group: %s\n", argv[0], option + 1); return 1; } } else { /* Check the list */ int set_something = 0; for (size_t i = 0; i < sizeof(syscall_names) / sizeof(*syscall_names); ++i) { if (syscall_names[i] && !strcmp(option,syscall_names[i])) { syscall_mask[i] = 1; set_something = 1; break; } } if (!set_something) { fprintf(stderr, "%s: Unrecognized syscall name: %s\n", argv[0], option); return 1; } } if (comma) { option = comma + 1; } } while (comma); } else { char * eq = strstr(optarg, "="); if (eq) *eq = '\0'; fprintf(stderr, "%s: Unrecognized -e option: %s\n", argv[0], optarg); return 1; } break; case 'h': return (usage(argv), 0); case '?': return usage(argv); } } if (!p && optind == argc) { return usage(argv); } if (!p) { p = fork(); if (!p) { if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) { fprintf(stderr, "%s: ptrace: %s\n", argv[0], strerror(errno)); return 1; } execvp(argv[optind], &argv[optind]); return 1; } signal(SIGINT, SIG_IGN); } else { if (ptrace(PTRACE_ATTACH, p, NULL, NULL) < 0) { fprintf(stderr, "%s: ptrace: %s\n", argv[0], strerror(errno)); return 1; } } int previous_syscall = -1; while (1) { int status = 0; pid_t res = waitpid(p, &status, WSTOPPED); if (res < 0) { fprintf(stderr, "%s: waitpid: %s\n", argv[0], strerror(errno)); } else { if (WIFSTOPPED(status)) { if (WSTOPSIG(status) == SIGTRAP) { struct URegs regs; ptrace(PTRACE_GETREGS, p, NULL, ®s); /* Event type */ int event = (status >> 16) & 0xFF; switch (event) { case PTRACE_EVENT_SYSCALL_ENTER: if (previous_syscall == SYS_EXECVE) finish_syscall(p,SYS_EXECVE,NULL); previous_syscall = uregs_syscall_num(®s); handle_syscall(p, ®s); break; case PTRACE_EVENT_SYSCALL_EXIT: finish_syscall(p, previous_syscall, ®s); previous_syscall = -1; break; default: fprintf(logfile, "Unknown event.\n"); break; } ptrace(PTRACE_CONT, p, NULL, NULL); } else { fprintf(logfile, "--- %s ---\n", signal_names[WSTOPSIG(status)]); ptrace(PTRACE_CONT, p, NULL, (void*)(uintptr_t)WSTOPSIG(status)); } } else if (WIFSIGNALED(status)) { fprintf(logfile, "+++ killed by %s +++\n", signal_names[WTERMSIG(status)]); return 0; } else if (WIFEXITED(status)) { fprintf(logfile, "+++ exited with %d +++\n", WEXITSTATUS(status)); return 0; } } } return 0; } ================================================ FILE: apps/strings.c ================================================ /** * @brief strings - print printable character sequences found in a file * * @copyright * 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 */ #include #include #include #include #include #include #include static char format = 0; static int min_chars = 4; static void read_file(FILE * f) { char buf[1024] = {0}; int ind = 0; size_t offset = 0; while (!feof(f)) { int c = fgetc(f); if (c < 0) break; if (c == '\n' || c == '\0') { if (ind >= min_chars) { switch (format) { case 'x': fprintf(stdout, "%lx ", offset - ind); break; case 'd': fprintf(stdout, "%lu ", offset - ind); break; default: break; } buf[ind] = '\0'; fprintf(stdout, "%s\n", buf); } ind = 0; offset++; continue; } if (!isprint(c)) { ind = 0; } else { if (ind < 1024) { buf[ind] = c; ind++; } } offset++; } } int main(int argc, char * argv[]) { int opt; int ret_val = 0; while ((opt = getopt(argc, argv, "an:t:")) != -1) { switch (opt) { case 'a': /* TODO: With ELF support, read only the string table * by default, unless this option is specified. */ break; case 'n': min_chars = atoi(optarg); break; case 't': format = optarg[0]; if (format != 'd' && format != 'x') { fprintf(stderr, "%s: format '%c' is not supported\n", argv[0], format); format = 0; } break; } } if (optind == argc) { read_file(stdin); } for (int i = optind; i < argc; ++i) { FILE * f = fopen(argv[i], "r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); ret_val = 1; continue; } read_file(f); fclose(f); } return ret_val; } ================================================ FILE: apps/stty.c ================================================ /** * @brief Set and display tty config bits. * * Surprisingly complete. * * We used to use Minix's stty, so I may have adopted some conventions from it * that aren't in other stty's, but I don't even remember any more. * * @copyright * 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 */ #include #include #include #include #include #include #include static int hide_defaults = 1; static int printed = 0; static void print_cc(struct termios * t, const char * lbl, int val, int def) { int c = t->c_cc[val]; if (hide_defaults && c == def) return; if (!c) { fprintf(stdout, "%s = ; ", lbl); } else if (c < 32) { fprintf(stdout, "%s = ^%c; ", lbl, '@' + c); } else if (c == 0x7F) { fprintf(stdout, "%s = ^?; ", lbl); } else { fprintf(stdout, "%s = %c; ", lbl, c); } printed = 1; } static void print_(int flags, const char * lbl, int val, int def) { int c = !!(flags & val); if (!hide_defaults || c != def) { fprintf(stdout, "%s%s ", c ? "" : "-", lbl); printed = 1; } } #define print_cflag(lbl,val,def) print_(t.c_cflag, lbl, val, def) #define print_iflag(lbl,val,def) print_(t.c_iflag, lbl, val, def) #define print_oflag(lbl,val,def) print_(t.c_oflag, lbl, val, def) #define print_lflag(lbl,val,def) print_(t.c_lflag, lbl, val, def) static int set_char_(struct termios *t, const char * lbl, int val, const char * cmp, const char * arg, int * i) { if (!strcmp(cmp, lbl)) { if (!arg) { fprintf(stderr, "%s: expected argument\n", lbl); exit(1); } /* Parse arg */ if (strlen(arg) == 1) { /* Assume raw character */ t->c_cc[val] = *arg; } else if (*arg == '^') { /* ^c, etc. */ int v = toupper(arg[1]); if (v == '?') { /* special case */ t->c_cc[val] = 0x7F; } else { t->c_cc[val] = v - '@'; } } else { /* Assume decimal for now */ int v = atoi(arg); t->c_cc[val] = v; } (*i)++; return 1; } return 0; } #define set_char(lbl,val) if (set_char_(&t, lbl, val, argv[i], argv[i+1], &i)) { i += 2; continue; } static int setunset_flag(tcflag_t * flag, const char * lbl, int val, const char * cmp) { if (*cmp == '-') { if (!strcmp(cmp+1, lbl)) { (*flag) = (*flag) & (~val); return 1; } } else { if (!strcmp(cmp, lbl)) { (*flag) = (*flag) | (val); return 1; } } return 0; } #define set_cflag(lbl,val) if (setunset_flag(&(t.c_cflag), lbl, val, argv[i])) { i++; continue; } #define set_iflag(lbl,val) if (setunset_flag(&(t.c_iflag), lbl, val, argv[i])) { i++; continue; } #define set_oflag(lbl,val) if (setunset_flag(&(t.c_oflag), lbl, val, argv[i])) { i++; continue; } #define set_lflag(lbl,val) if (setunset_flag(&(t.c_lflag), lbl, val, argv[i])) { i++; continue; } static int set_toggle_(tcflag_t * flag, const char * lbl, int base, int val, const char * cmp) { if (!strcmp(cmp, lbl)) { (*flag) = (*flag) & ~(base); (*flag) = (*flag) | (val); return 1; } return 0; } #define set_ctoggle(lbl,base,val) if (set_toggle_(&(t.c_cflag), lbl, base, val, argv[i])) { i++; continue; } #define set_otoggle(lbl,base,val) if (set_toggle_(&(t.c_oflag), lbl, base, val, argv[i])) { i++; continue; } static struct baud_table { const char * as_str; speed_t as_baud; } baud_rates[] = { { "0", B0 }, { "50", B50 }, { "75", B75 }, { "110", B110 }, { "134", B134 }, { "134.5", B134 }, { "150", B150 }, { "200", B200 }, { "300", B300 }, { "600", B600 }, { "1200", B1200 }, { "1800", B1800 }, { "2400", B2400 }, { "4800", B4800 }, { "9600", B9600 }, { "19200", B19200 }, { "38400", B38400 }, { "57600", B57600 }, { "115200", B115200 }, { "230400", B230400 }, { "460800", B460800 }, { "921600", B921600 }, }; static int set_baud_rate(struct termios * t, const char * arg) { for (size_t j = 0; j < sizeof(baud_rates) / sizeof(*baud_rates); ++j) { if (!strcmp(arg, baud_rates[j].as_str)) { cfsetospeed(t, baud_rates[j].as_baud); return 1; } } return 0; } static int print_baud_rate(struct termios * t) { speed_t ospeed = cfgetospeed(t); for (size_t j = 0; j < sizeof(baud_rates) / sizeof(*baud_rates); ++j) { if (ospeed == baud_rates[j].as_baud) { fprintf(stdout, "speed %s baud; ", baud_rates[j].as_str); return 1; } } return 0; } static int show_settings(int all) { struct termios t; tcgetattr(STDERR_FILENO, &t); print_baud_rate(&t); /* Baud rate */ /* Size */ struct winsize w; ioctl(STDERR_FILENO, TIOCGWINSZ, &w); fprintf(stdout, "rows %d; columns %d; ypixels %d; xpixels %d;\n", w.ws_row, w.ws_col, w.ws_ypixel, w.ws_xpixel); printed = 0; /* Keys */ print_cc(&t, "intr", VINTR, 3); print_cc(&t, "quit", VQUIT, 28); print_cc(&t, "erase", VERASE, 0x7F); print_cc(&t, "kill", VKILL, 21); print_cc(&t, "eof", VEOF, 4); print_cc(&t, "eol", VEOL, 0); if (printed) { fprintf(stdout, "\n"); printed = 0; } print_cc(&t, "start", VSTART, 17); print_cc(&t, "stop", VSTOP, 19); print_cc(&t, "susp", VSUSP, 26); print_cc(&t, "lnext", VLNEXT, 22); print_cc(&t, "werase",VWERASE, 23); /* MIN, TIME */ if (!hide_defaults || t.c_cc[VMIN] != 1) { fprintf(stdout, "min = %d; ", t.c_cc[VMIN]); printed = 1; } if (!hide_defaults || t.c_cc[VTIME] != 0) { fprintf(stdout, "time = %d; ", t.c_cc[VTIME]); printed = 1; } if (printed) { fprintf(stdout, "\n"); printed = 0; } switch ((t.c_cflag & CSIZE)) { case CS5: fprintf(stdout, "cs5 "); printed = 1; break; case CS6: fprintf(stdout, "cs6 "); printed = 1; break; case CS7: fprintf(stdout, "cs7 "); printed = 1; break; case CS8: if (!hide_defaults) { fprintf(stdout, "cs8 "); printed = 1; } break; } print_cflag("cstopb", CSTOPB, 0); print_cflag("cread", CREAD, 1); print_cflag("parenb", PARENB, 0); print_cflag("parodd", PARODD, 0); print_cflag("hupcl", HUPCL, 0); print_cflag("clocal", CLOCAL, 0); if (printed) { fprintf(stdout, "\n"); printed = 0; } print_iflag("brkint", BRKINT, 1); print_iflag("icrnl", ICRNL, 1); print_iflag("ignbrk", IGNBRK, 0); print_iflag("igncr", IGNCR, 0); print_iflag("ignpar", IGNPAR, 0); print_iflag("inlcr", INLCR, 0); print_iflag("inpck", INPCK, 0); print_iflag("istrip", ISTRIP, 0); print_iflag("ixany", IXANY, 0); print_iflag("ixoff", IXOFF, 0); print_iflag("ixon", IXON, 0); print_iflag("iuclc", IUCLC, 0); print_iflag("parmrk", PARMRK, 0); if (printed) { fprintf(stdout, "\n"); printed = 0; } print_oflag("opost", OPOST, 1); print_oflag("olcuc", OLCUC, 0); print_oflag("onlcr", ONLCR, 1); print_oflag("ocrnl", OCRNL, 0); print_oflag("onocr", ONOCR, 0); print_oflag("onlret", ONLRET, 0); print_oflag("ofill", OFILL, 0); print_oflag("ofdel", OFDEL, 0); switch ((t.c_oflag & CRDLY)) { case CR0: if (!hide_defaults) { fprintf(stdout, "cr0 "); printed = 1; } break; case CR1: fprintf(stdout, "cr1 "); printed = 1; break; case CR2: fprintf(stdout, "cr2 "); printed = 1; break; case CR3: fprintf(stdout, "cr3 "); printed = 1; break; } switch ((t.c_oflag & NLDLY)) { case NL0: if (!hide_defaults) { fprintf(stdout, "nl0 "); printed = 1; } break; case NL1: fprintf(stdout, "nl1 "); printed = 1; break; } switch ((t.c_oflag & TABDLY)) { case TAB0: if (!hide_defaults) { fprintf(stdout, "tab0 "); printed = 1; } break; case TAB1: fprintf(stdout, "tab1 "); printed = 1; break; case TAB2: fprintf(stdout, "tab2 "); printed = 1; break; case TAB3: fprintf(stdout, "tab3 "); printed = 1; break; } switch ((t.c_oflag & BSDLY)) { case BS0: if (!hide_defaults) { fprintf(stdout, "bs0 "); printed = 1; } break; case BS1: fprintf(stdout, "bs1 "); printed = 1; break; } switch ((t.c_oflag & FFDLY)) { case FF0: if (!hide_defaults) { fprintf(stdout, "ff0 "); printed = 1; } break; case FF1: fprintf(stdout, "ff1 "); printed = 1; break; } switch ((t.c_oflag & VTDLY)) { case VT0: if (!hide_defaults) { fprintf(stdout, "vt0"); printed = 1; } break; case VT1: fprintf(stdout, "vt1"); printed = 1; break; } if (printed) { fprintf(stdout, "\n"); printed = 0; } print_lflag("isig", ISIG, 1); print_lflag("icanon", ICANON, 1); print_lflag("xcase", XCASE, 0); print_lflag("echo", ECHO, 1); print_lflag("echoe", ECHOE, 1); print_lflag("echok", ECHOK, 1); print_lflag("echonl", ECHONL, 0); print_lflag("echoctl",ECHOCTL,1); print_lflag("noflsh", NOFLSH, 0); print_lflag("tostop", TOSTOP, 0); print_lflag("iexten", IEXTEN, 1); if (printed) { fprintf(stdout, "\n"); printed = 0; } return 0; } static void show_size(void) { struct winsize w; ioctl(STDERR_FILENO, TIOCGWINSZ, &w); fprintf(stdout, "%d %d\n", w.ws_row, w.ws_col); } int main(int argc, char * argv[]) { int i = 1; if (i < argc && !strcmp(argv[i], "-a")) { hide_defaults = 0; i++; } if (i == argc) { return show_settings(0); } struct termios t; tcgetattr(STDERR_FILENO, &t); while (i < argc) { if (!strcmp(argv[i], "sane")) { t.c_iflag = ICRNL | BRKINT; t.c_oflag = ONLCR | OPOST; t.c_lflag = ECHO | ECHOE | ECHOK | ICANON | ISIG | IEXTEN | ECHOCTL; t.c_cflag |= CREAD; t.c_cc[VEOF] = 4; /* ^D */ t.c_cc[VEOL] = 0; /* Not set */ t.c_cc[VERASE] = 0x7F; /* ^? */ t.c_cc[VINTR] = 3; /* ^C */ t.c_cc[VKILL] = 21; /* ^U */ t.c_cc[VMIN] = 1; t.c_cc[VQUIT] = 28; /* ^\ */ t.c_cc[VSTART] = 17; /* ^Q */ t.c_cc[VSTOP] = 19; /* ^S */ t.c_cc[VSUSP] = 26; /* ^Z */ t.c_cc[VTIME] = 0; t.c_cc[VLNEXT] = 22; /* ^V */ t.c_cc[VWERASE] = 23; /* ^W */ i++; continue; } if (!strcmp(argv[i], "raw")) { t.c_iflag = 0; /* no input processing */ t.c_oflag &= ~OPOST; /* no postprocessing of output */ t.c_lflag &= ~(ISIG | ICANON | XCASE); i++; continue; } if (!strcmp(argv[i], "cooked")) { t.c_iflag |= ICRNL | BRKINT; t.c_oflag |= OPOST; t.c_lflag |= ISIG | ICANON; i++; continue; } if (!strcmp(argv[i], "size")) { show_size(); i++; continue; } if (*argv[i] >= '0' && *argv[i] <= '9') { if (set_baud_rate(&t, argv[i])) { i++; continue; } } set_char("eof", VEOF); set_char("eol", VEOL); set_char("erase", VERASE); set_char("intr", VINTR); set_char("kill", VKILL); set_char("quit", VQUIT); set_char("start", VSTART); set_char("stop", VSTOP); set_char("susp", VSUSP); set_char("lnext", VLNEXT); set_char("vwerase",VWERASE); set_cflag("parenb", PARENB); set_cflag("parodd", PARODD); set_cflag("hupcl", HUPCL); set_cflag("hup", HUPCL); /* alias */ set_cflag("cstopb", CSTOPB); set_cflag("cread", CREAD); set_cflag("clocal", CLOCAL); set_ctoggle("cs5", CSIZE, CS5); set_ctoggle("cs6", CSIZE, CS6); set_ctoggle("cs7", CSIZE, CS7); set_ctoggle("cs8", CSIZE, CS8); set_iflag("ignbrk", IGNBRK); set_iflag("brkint", BRKINT); set_iflag("ignpar", IGNPAR); set_iflag("parmrk", PARMRK); set_iflag("inpck", INPCK); set_iflag("istrip", ISTRIP); set_iflag("inlcr", INLCR); set_iflag("igncr", IGNCR); set_iflag("icrnl", ICRNL); set_iflag("ixon", IXON); set_iflag("ixany", IXANY); set_iflag("ixoff", IXOFF); set_iflag("iuclc", IUCLC); set_oflag("olcuc", OLCUC); set_oflag("opost", OPOST); set_oflag("onlcr", ONLCR); set_oflag("ocrnl", OCRNL); set_oflag("onocr", ONOCR); set_oflag("onlret", ONLRET); set_oflag("ofill", OFILL); set_oflag("ofdel", OFDEL); set_otoggle("cr0", CRDLY, CR0); set_otoggle("cr1", CRDLY, CR1); set_otoggle("cr2", CRDLY, CR2); set_otoggle("cr3", CRDLY, CR3); set_otoggle("nl0", NLDLY, NL0); set_otoggle("nl1", NLDLY, NL1); set_otoggle("tab0", TABDLY, TAB0); set_otoggle("tab1", TABDLY, TAB1); set_otoggle("tab2", TABDLY, TAB2); set_otoggle("tab3", TABDLY, TAB3); set_otoggle("bs0", BSDLY, BS0); set_otoggle("bs1", BSDLY, BS1); set_otoggle("ff0", FFDLY, FF0); set_otoggle("ff1", FFDLY, FF1); set_otoggle("vt0", VTDLY, VT0); set_otoggle("vt1", VTDLY, VT1); set_lflag("isig", ISIG); set_lflag("icanon", ICANON); set_lflag("iexten", IEXTEN); set_lflag("echo", ECHO); set_lflag("echoe", ECHOE); set_lflag("echok", ECHOK); set_lflag("echonl", ECHONL); set_lflag("echoctl",ECHOCTL); set_lflag("noflsh", NOFLSH); set_lflag("tostop", TOSTOP); fprintf(stderr, "%s: invalid argument '%s'\n", argv[0], argv[i]); return 1; } tcsetattr(STDERR_FILENO, TCSADRAIN, &t); return 0; } ================================================ FILE: apps/sudo.c ================================================ /** * @brief sudo - Run processes as the root user, after authenticating. * * Our sudo supports cached authentication, so you don't need to keep * entering your password. * * Probably terribly insecure, but our main password auth function is * a plain text comparison, so *shrug*. * * @copyright * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #define MINUTES * 60 #define SUDO_TIME 5 MINUTES extern int setgroups(int size, const gid_t list[]); static int sudo_loop(int (*prompt_callback)(char * username, char * password, int failures, char * argv[]), char * argv[]) { int fails = 0; if (geteuid() != 0) { fprintf(stderr, "%s: effective uid is not 0\n", argv[0]); return 1; } struct stat buf; if (stat("/var/sudoers", &buf)) { mkdir("/var/sudoers", 0700); } while (1) { int need_password = 1; int need_sudoers = 1; uid_t me = getuid(); if (me == 0) { need_password = 0; need_sudoers = 0; } struct passwd * p = getpwuid(me); if (!p) { fprintf(stderr, "%s: unable to obtain username for real uid=%d\n", argv[0], getuid()); return 1; } char * username = strdup(p->pw_name); char token_file[64]; sprintf(token_file, "/var/sudoers/%d", me); /* TODO: Restrict to this session? */ if (need_password) { struct stat buf; if (!stat(token_file, &buf)) { /* check the time */ if (buf.st_mtime > (SUDO_TIME) && time(NULL) - buf.st_mtime < (SUDO_TIME)) { need_password = 0; } } } if (need_password) { char * password = calloc(sizeof(char) * 1024, 1); if (prompt_callback(username, password, fails, argv)) { free(username); free(password); return 1; } int uid = toaru_auth_check_pass(username, password); free(password); if (uid < 0) { free(username); fails++; if (fails == 3) { fprintf(stderr, "%s: %d incorrect password attempts\n", argv[0], fails); return 1; } fprintf(stderr, "Sorry, try again.\n"); continue; } } /* Determine if this user is in the sudoers file */ if (need_sudoers) { FILE * sudoers = fopen("/etc/sudoers","r"); if (!sudoers) { free(username); fprintf(stderr, "%s: /etc/sudoers is not available\n", argv[0]); return 1; } /* Read each line */ int in_sudoers = 0; while (!feof(sudoers)) { char line[1024]; fgets(line, 1024, sudoers); char * nl = strchr(line, '\n'); if (nl) { *nl = '\0'; } if (!strncmp(line,username,1024)) { in_sudoers = 1; break; } } fclose(sudoers); if (!in_sudoers) { fprintf(stderr, "%s is not in sudoers file.\n", username); free(username); return 1; } } free(username); /* Write a timestamp file */ FILE * f = fopen(token_file, "w"); if (!f) { fprintf(stderr, "%s: (warning) failed to create token file\n", argv[0]); } fclose(f); /* Set username to root */ putenv("USER=root"); /* Actually become root, so real user id = 0 */ setgid(0); setuid(0); setgroups(0,NULL); if (!strcmp(argv[1], "-s")) { argv[1] = getenv("SHELL"); } char ** args = &argv[1]; execvp(args[0], args); /* XXX: There are other things that can cause an exec to fail. */ fprintf(stderr, "%s: %s: command not found\n", argv[0], args[0]); return 1; } return 0; } static int basic_callback(char * username, char * password, int fails, char * argv[]) { fprintf(stderr, "[%s] password for %s: ", argv[0], username); fflush(stderr); /* 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); if (feof(stdin)) return 1; password[strlen(password)-1] = '\0'; tcsetattr(fileno(stdin), TCSAFLUSH, &old); fprintf(stderr, "\n"); return 0; } void usage(int argc, char * argv[]) { fprintf(stderr, "usage: %s [command]\n", argv[0]); } int main(int argc, char ** argv) { if (argc < 2) { usage(argc, argv); return 1; } return sudo_loop(basic_callback, argv); } ================================================ FILE: apps/sync.c ================================================ /** * @brief Wait for filesystem buffered writes to finish. * * And by "wait" and "to finish" I mean "tell the block device * to write everything because it doesn't do that asynchronously". * * Currently only syncs the block device that owns the current * working directory because I haven't set this up to iterate * over mounts yet. * * It will correctly wait until the sync finishes, though. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include int main(int argc, char * argv[]) { char * file = "."; if (argc > 1) { file = argv[1]; } int fd = open(file,O_RDONLY|O_DIRECTORY); if (fd < 0) { fd = open(file,O_RDONLY); } if (fd < 0) { fprintf(stderr, "sync: open: %s: %s (%d)\n", file, strerror(errno), fd); return 1; } int res = ioctl(fd, IOCTLSYNC, NULL); if (res < 0) { fprintf(stderr, "sync: ioctl: %s (%d)\n", strerror(errno), fd); return 1; } return 0; } ================================================ FILE: apps/sysfunc.c ================================================ /** * @brief Exceute "extended system function" syscalls. * * Most of these are deprecated, and the ones that are useful * to call manually are all behind other utilities. * * @copyright * 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 */ #include #include #include #include #include int main(int argc, char ** argv) { if (argc < 2) return 1; int ret = sysfunc(atoi(argv[1]), &argv[2]); if (ret < 0) { fprintf(stderr, "%s: %s\n", argv[0], strerror(errno)); } return ret; } ================================================ FILE: apps/sysinfo.c ================================================ /** * @brief Display system information. * * Similar to tools like 'screenfetch', this displays information * about ToaruOS, the current machine state, and the user's * configuration options, alongside a terminal-safe rendition * of the OS's logo. * * This is a bit overcomplicated as we used to show an elaborate * logo from a BMP/PNG, but our current logo can be nicely * represented with block characters so that's kind moot; maybe * this should be simplified... * * Uses a few other utilities: * hostname * uname -sr * uptime -p * msk count * sh -v * yutani-query resolution * font-tool -n * cpu-name.krk * free -ut * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include "toaru_logo.h" #define NUM_DATA_LINES 30 char data_lines[NUM_DATA_LINES][100]; const char * prog_lines[NUM_DATA_LINES] = {NULL}; #define C_A "\033[34;1m" #define C_O "\033[0m" void print_thing(int j) { printf("\033[0m %s", data_lines[j]); fflush(stdout); if (prog_lines[j]) { system(prog_lines[j]); } else { printf("\n"); } } static void reset(void) { printf("\033[0m"); } static int term_is_toaru = 0; static void foreground_color(uint32_t color) { printf(term_is_toaru ? "\033[38;6;%d;%d;%d;%dm" : "\033[38;2;%d;%d;%dm", (int)_RED(color), (int)_GRE(color), (int)_BLU(color), (int)_ALP(color)); } static void background_color(uint32_t color) { printf(term_is_toaru ? "\033[48;6;%d;%d;%d;%dm" : "\033[48;2;%d;%d;%dm", (int)_RED(color), (int)_GRE(color), (int)_BLU(color), (int)_ALP(color)); } int main(int argc, char * argv[]) { /* Prepare data */ char * user = getenv("USER"); char * wm_theme = getenv("WM_THEME"); char * term = getenv("TERM"); term_is_toaru = term && strstr(term,"toaru"); int i = 0; prog_lines[i] = "hostname"; sprintf(data_lines[i++], C_A "%s" C_O "@" C_A, user); prog_lines[i] = ". /etc/os-release; echo ${PRETTY_NAME}"; sprintf(data_lines[i++], C_A "OS: " C_O); prog_lines[i] = "uname -sr"; sprintf(data_lines[i++], C_A "Kernel: " C_O); prog_lines[i] = "uptime -p"; sprintf(data_lines[i++], C_A "Uptime: " C_O); prog_lines[i] = "msk count"; sprintf(data_lines[i++], C_A "Packages: " C_O); prog_lines[i] = "esh -v"; sprintf(data_lines[i++], C_A "Shell: " C_O); prog_lines[i] = "yutani-query resolution"; sprintf(data_lines[i++], C_A "Resolution: " C_O); /* no command */ sprintf(data_lines[i++], C_A "WM: " C_O "Yutani"); /* from environment */ sprintf(data_lines[i++], C_A "WM Theme: " C_O "%s", wm_theme); prog_lines[i] = "font-tool -n"; sprintf(data_lines[i++], C_A "Font: " C_O); prog_lines[i] = "cpu-name.krk"; sprintf(data_lines[i++], C_A "CPU: " C_O); prog_lines[i] = "free -ut"; sprintf(data_lines[i++], C_A "RAM: " C_O); int j = 0; for (unsigned int y = 0; y < gimp_image.height; y += 2) { for (unsigned int x = 0; x < gimp_image.width; x += 1) { unsigned char r_t = gimp_image.pixel_data[(x + y * gimp_image.width) * 4]; unsigned char g_t = gimp_image.pixel_data[(x + y * gimp_image.width) * 4 + 1]; unsigned char b_t = gimp_image.pixel_data[(x + y * gimp_image.width) * 4 + 2]; unsigned char a_t = gimp_image.pixel_data[(x + y * gimp_image.width) * 4 + 3]; unsigned char r_b = 0; unsigned char g_b = 0; unsigned char b_b = 0; unsigned char a_b = 0; if (y != gimp_image.height - 1) { r_b = gimp_image.pixel_data[(x + (y + 1) * gimp_image.width) * 4]; g_b = gimp_image.pixel_data[(x + (y + 1) * gimp_image.width) * 4 + 1]; b_b = gimp_image.pixel_data[(x + (y + 1) * gimp_image.width) * 4 + 2]; a_b = gimp_image.pixel_data[(x + (y + 1) * gimp_image.width) * 4 + 3]; } uint32_t out = alpha_blend_rgba( rgba(0,0,0,TERM_DEFAULT_OPAC), premultiply(rgba(r_t, g_t, b_t, a_t))); uint32_t back = alpha_blend_rgba( rgba(0,0,0,TERM_DEFAULT_OPAC), premultiply(rgba(r_b, g_b, b_b, a_b))); /* Are both cells transparent? */ if (a_t == 0 && a_b == 0) { reset(); printf(" "); continue; } if (a_b == 0) { reset(); foreground_color(out); printf("▀"); } else if (a_t == 0) { reset(); foreground_color(back); printf("▄"); } else { foreground_color(back); background_color(out); printf("▄"); } } if (j < i) { print_thing(j); j++; } else { printf("\033[0m\n"); } } while (j < i) { for (int x = 0; x < (int)gimp_image.width; x++) { printf(" "); } print_thing(j); j++; } } ================================================ FILE: apps/t_mbstowcs.c ================================================ /** * @brief Test tool for libc UTF8 decoding * * @copyright * 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 */ #include #include #include int main(int argc, char * argv[]) { size_t req = mbstowcs(NULL, argv[1], 0); wchar_t * dest = malloc(sizeof(wchar_t) * req); mbstowcs(dest, argv[1], req+1); for (size_t i = 0; i < req; ++i) { char tmp[8]; wchar_t in[] = {dest[i], L'\0'}; wcstombs(tmp, in, 8); fprintf(stdout, "U+%4x %s\n", dest[i], tmp); } return 0; } ================================================ FILE: apps/tar.c ================================================ /** * @brief tar - extract archives * * This is a very minimal and incomplete implementation of tar. * It supports on ustar-formatted archives, and its arguments * must be the - forms. As of writing, creating archives is not * supported. Decompression is supported by piping through gunzip. * * @copyright * 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-2020 K. Lange */ #include #include #include #include #include #include #include #include #include #include struct ustar { char filename[100]; char mode[8]; char ownerid[8]; char groupid[8]; char size[12]; char mtime[12]; char checksum[8]; char type[1]; char link[100]; char ustar[6]; char version[2]; char owner[32]; char group[32]; char dev_major[8]; char dev_minor[8]; char prefix[155]; char padding[12]; }; static struct ustar * extract_file(FILE * f) { static struct ustar _ustar; if (fread(&_ustar, 1, sizeof(struct ustar), f) != sizeof(struct ustar)) { fprintf(stderr, "failed to read file\n"); return NULL; } if (_ustar.filename[0] == 0) { return NULL; } return &_ustar; } static unsigned int interpret_mode(struct ustar * file) { return ((file->mode[0] - '0') << 18) | ((file->mode[1] - '0') << 15) | ((file->mode[2] - '0') << 12) | ((file->mode[3] - '0') << 9) | ((file->mode[4] - '0') << 6) | ((file->mode[5] - '0') << 3) | ((file->mode[6] - '0') << 0); } static unsigned int interpret_size(struct ustar * file) { if (file->size[0] != '0') { fprintf(stderr, "\033[3;32mWarning:\033[0;3m File is too big.\033[0m\n"); } return ((file->size[ 0] - '0') << 30) | ((file->size[ 1] - '0') << 27) | ((file->size[ 2] - '0') << 24) | ((file->size[ 3] - '0') << 21) | ((file->size[ 4] - '0') << 18) | ((file->size[ 5] - '0') << 15) | ((file->size[ 6] - '0') << 12) | ((file->size[ 7] - '0') << 9) | ((file->size[ 8] - '0') << 6) | ((file->size[ 9] - '0') << 3) | ((file->size[10] - '0') << 0); } static const char * type_to_string(char type) { static char unknown[100]; switch (type) { case '\0': case '0': return "Normal file"; case '1': return "Hard link (unsupported)"; case '2': return "Symolic link"; case '3': return "Character special (unsupported)"; case '4': return "Block special (unsupported)"; case '5': return "Directory"; case '6': return "FIFO (unsupported)"; case 'g': return "Extended header"; case 'x': return "Extended preheader"; default: sprintf(unknown, "Uknown: %c", type); return unknown; } } #if 0 static void dump_file(struct ustar * file) { fprintf(stdout, "\033[1m%.155s%.100s\033[0m\n", file->prefix, file->filename); fprintf(stdout, "%c - %s\n", file->type[0], type_to_string(file->type[0])); fprintf(stdout, "File size: %u\n", interpret_size(file)); } #endif #define CHUNK_SIZE 4096 static void write_file(struct ustar * file, FILE * f, FILE * mf, char * name) { size_t length = interpret_size(file); char buf[CHUNK_SIZE]; while (length > CHUNK_SIZE) { fread( buf, 1, CHUNK_SIZE, f); fwrite(buf, 1, CHUNK_SIZE, mf); length -= CHUNK_SIZE; } if (length > 0) { fread( buf, 1, length, f); fwrite(buf, 1, length, mf); } fflush(mf); if (mf != stdout) { fclose(mf); chmod(name, interpret_mode(file)); } } static void _seek_forward(FILE * f, size_t amount) { for (size_t i = 0; i < amount; ++i) { fgetc(f); } } static void usage(char * argv[]) { fprintf(stderr, "tar - extract ustar archives\n" "\n" "usage: %s [-ctxvaf] [name]\n" "\n" " -f \033[3mfile archive to open\033[0m\n" " -x \033[3mextract\033[0m\n" "\n", argv[0]); } static int matches_files(int argc, char * argv[], int optind, char * filename) { while (optind < argc) { if (!strcmp(argv[optind], filename)) return 1; optind++; } return 0; } int main(int argc, char * argv[]) { int opt; char * fname = NULL; int verbose = 0; int action = 0; int compressed = 0; int to_stdout = 0; int only_matches = 0; #define TAR_ACTION_EXTRACT 1 #define TAR_ACTION_CREATE 2 #define TAR_ACTION_LIST 3 if (argc > 1 && argv[1][0] != '-') { switch(argv[1][0]) { case 'x': action = TAR_ACTION_EXTRACT; break; case 'c': action = TAR_ACTION_CREATE; break; case 't': action = TAR_ACTION_LIST; break; default: usage(argv); return 1; } /* Go through the rest of the argument */ int expect_file = 0; for (int i = 1; argv[1][i]; ++i) { switch (argv[1][i]) { case 'f': expect_file = 1; break; case 'v': verbose = 1; break; case 'z': compressed = 1; break; case 'O': to_stdout = 1; break; default: fprintf(stderr, "%s: %c: unrecognized option\n", argv[0], argv[1][i]); return 1; } } optind = 2; if (expect_file) { if (argc < 3) { fprintf(stderr, "%s: filename expected after arguments\n", argv[0]); return 1; } optind = 3; fname = argv[2]; } goto _skip_getopt; } while ((opt = getopt(argc, argv, "?ctxzvaf:O")) != -1) { switch (opt) { case 'c': if (action) { fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt); return 1; } action = TAR_ACTION_CREATE; break; case 'f': fname = optarg; break; case 'x': if (action) { fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt); return 1; } action = TAR_ACTION_EXTRACT; break; case 't': if (action) { fprintf(stderr, "%s: %c: already specified action\n", argv[0], opt); return 1; } action = TAR_ACTION_LIST; break; case 'v': verbose = 1; break; case 'z': compressed = 1; break; case 'O': to_stdout = 1; break; case '?': usage(argv); return 1; default: fprintf(stderr, "%s: unsupported option '%c'\n", argv[0], opt); return 1; } } _skip_getopt: (void)0; if (!fname) { fname = "-"; } if (optind < argc) { only_matches = 1; } if (action == TAR_ACTION_EXTRACT || action == TAR_ACTION_LIST) { FILE * f; if (!strcmp(fname,"-")) { f = stdin; } else { f = fopen(fname,"r"); } if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], fname, strerror(errno)); return 1; } if (compressed) { int fds[2]; pipe(fds); int child = fork(); if (child == 0) { /* Close the read end */ close(fds[0]); /* Put f's fd into stdin */ dup2(fileno(f), STDIN_FILENO); /* Make stdout the pipe */ dup2(fds[1], STDOUT_FILENO); /* Execeute gzunzip */ char * args[] = {"gunzip","-c",NULL}; exit(execvp("gunzip",args)); } else if (child < 0) { fprintf(stderr, "%s: failed to fork gunzip for compressed archive\n", argv[0]); return 1; } /* Reattach f to pipe */ close(fds[1]); f = fdopen(fds[0], "r"); } char tmpname[1024] = {0}; int last_was_long = 0; while (!feof(f)) { struct ustar * file = extract_file(f); if (!file) { break; } if (action == TAR_ACTION_LIST) { if (verbose) { fprintf(stdout, "%10d %c %.155s%.100s\n", interpret_size(file), file->type[0], file->prefix, file->filename); } else { fprintf(stdout, "%.155s%.100s\n", file->prefix, file->filename); } _seek_forward(f, interpret_size(file)); } else if (action == TAR_ACTION_EXTRACT) { if (verbose) { fprintf(stdout, "%.155s%.100s\n", file->prefix, file->filename); } char name[1025] = {0}; if (last_was_long) { strncat(name, tmpname, 1024); last_was_long = 0; } else { strncat(name, file->prefix, 167); strncat(name, file->filename, 512); } if (file->type[0] == '0' || file->type[0] == 0) { FILE * mf = to_stdout ? stdout : fopen(name,"w"); if (!mf) { fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno)); _seek_forward(f, interpret_size(file)); } else { if (!only_matches || matches_files(argc,argv,optind,name)) { write_file(file,f,mf,name); } } } else if (file->type[0] == '5') { if (!to_stdout) { if (name[strlen(name)-1] == '/') { name[strlen(name)-1] = '\0'; } if (strlen(name)) { if (!only_matches || matches_files(argc,argv,optind,name)) { if (mkdir(name, 0777) < 0) { if (errno != EEXIST) { fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno)); } } } } } } else if (file->type[0] == '1') { if (!to_stdout && (!only_matches || matches_files(argc,argv,optind,name))) { char tmp[356] = {0}; strncat(tmp, file->link, 355); FILE * mf = fopen(name,"w"); if (!mf) { fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, strerror(errno)); } else { FILE * source = fopen(tmp, "r"); if (!source) { fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, tmp, strerror(errno)); } else { while (!feof(source)) { char buf[4096]; ssize_t r = fread(buf, 1, 4096, source); fwrite(buf, 1, r, mf); } fclose(source); } fclose(mf); chmod(name, interpret_mode(file)); } } _seek_forward(f, interpret_size(file)); } else if (file->type[0] == '2') { if (!to_stdout && (!only_matches || matches_files(argc,argv,optind,name))) { char tmp[356] = {0}; strncat(tmp, file->link, 355); if (symlink(tmp, name) < 0) { fprintf(stderr, "%s: %s: %s: %s: %s\n", argv[0], fname, name, tmp, strerror(errno)); } } _seek_forward(f, interpret_size(file)); } else if (file->type[0] == 'L') { /* This is a GNU Long Name block; store its contents as a file name */ size_t s = interpret_size(file); fread(tmpname, 1, s, f); tmpname[s] = '\0'; last_was_long = 1; } else { fprintf(stderr, "%s: %s: %s: %s\n", argv[0], fname, name, type_to_string(file->type[0])); _seek_forward(f, interpret_size(file)); } } size_t file_size = interpret_size(file); if (file_size % 512) { _seek_forward(f, 512 - (file_size % 512)); } } } else { fprintf(stderr, "%s: unsupported action\n", argv[0]); return 1; } return 0; } ================================================ FILE: apps/tee.c ================================================ /** * @brief tee - copy stdin to stdout and to specified files * * @copyright * 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 */ #include #include #include #include #include #include #include int main(int argc, char * argv[]) { int append = 0; int ret_val = 0; int opt; while ((opt = getopt(argc, argv, "ai")) != -1) { switch (opt) { case 'a': append = 1; break; case 'i': signal(SIGINT, SIG_IGN); break; } } int file_count = argc - optind; FILE ** files = malloc(sizeof(FILE *) * file_count); for (int i = 0, j = optind; j < argc && i < file_count; j++) { files[i] = fopen(argv[j], append ? "a" : "w"); if (!files[i]) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[j], strerror(errno)); ret_val = 1; file_count--; continue; } else { i++; } } while (!feof(stdin)) { int c = fgetc(stdin); if (c >= 0) { fputc(c, stdout); for (int i = 0; i < file_count; ++i) { fputc(c, files[i]); } } } for (int i = 0; i < file_count; ++i) { fclose(files[i]); } return ret_val; } ================================================ FILE: apps/terminal-font.h ================================================ /** * @file apps/terminal-font.h * @brief Fallback font used to render text where the TrueType renderer would be inappropriate. * * Used variously by Terminal, splash-log, and the kernel. * * This is baked bitmap version of Deja Vu Sans Mono. * * @copyright * Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. * * Bitstream Vera Fonts Copyright * ------------------------------ * * Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this * license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the * Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell * copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject * to the following conditions: * * The above copyright and trademark notices and this permission notice shall be included in all copies of one * or more of the Font Software typefaces. * * The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or * characters in the Fonts may be modified and additional glyphs or or characters may be added to the Fonts, * only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". * * This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified * and is distributed under the "Bitstream Vera" names. * * The Font Software may be sold as part of a larger software package but no copy of one or more of the Font * Software typefaces may be sold by itself. * * THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT * LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, * PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT * SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. * * Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be * used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior * written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, * contact: fonts at gnome dot org. */ #define LARGE_FONT_CELL_WIDTH 8 #define LARGE_FONT_CELL_HEIGHT 17 #define LARGE_FONT_MASK 7 uint8_t large_font[][17] = { /* 0 32 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 1 9786 */ { /* */ /* */ /* */ /* */ /* */ /* ##### */ /* # # */ /* # # # # */ /* # # # # */ /* # ## # */ /* # # */ /* #### */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x42, 0xa5, 0xa5, 0x9a, 0x42, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 2 9787 */ { /* */ /* */ /* */ /* */ /* */ /* ##### */ /* # ## # */ /* ## ## ## */ /* ######## */ /* ## ## # */ /* ###### */ /* #### */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x5a, 0xdb, 0xff, 0xda, 0x7e, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 3 9829 */ { /* */ /* */ /* */ /* */ /* ## ## */ /* ######## */ /* ######## */ /* ######## */ /* ###### */ /* #### */ /* ## */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x66, 0xff, 0xff, 0xff, 0x7e, 0x3c, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 4 9830 */ { /* */ /* */ /* */ /* */ /* */ /* ## */ /* #### */ /* ##### */ /* ###### */ /* #### */ /* ## */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x7c, 0x7e, 0x3c, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 5 9827 */ { /* */ /* */ /* */ /* */ /* ## */ /* #### */ /* #### */ /* ## */ /* ###### */ /* ######## */ /* ## ### */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x7e, 0xff, 0x6e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 6 9824 */ { /* */ /* */ /* */ /* */ /* */ /* ## */ /* ### */ /* #### */ /* ###### */ /* ###### */ /* ### ## */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x38, 0x3c, 0x7e, 0x7e, 0x76, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 7 8226 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* #### */ /* #### */ /* ## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 8 9688 */ { /* */ /* */ /* */ /* ######## */ /* ######## */ /* ######## */ /* ### ### */ /* ## ## */ /* ## ## */ /* ### ### */ /* ######## */ /* ######## */ /* ######## */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xe7, 0xc3, 0xc3, 0xe7, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, }, /* 9 9675 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* #### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* #### */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x42, 0x81, 0x81, 0x81, 0x81, 0x42, 0x3c, 0x00, 0x00, 0x00, }, /* 10 9689 */ { /* */ /* */ /* */ /* ######## */ /* ######## */ /* ######## */ /* ## ## */ /* # #### # */ /* # #### # */ /* # #### # */ /* # #### # */ /* # #### # */ /* # #### # */ /* ## ## */ /* ######## */ /* ######## */ /* */ 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xc3, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xc3, 0xff, 0xff, 0x00, }, /* 11 9794 */ { /* */ /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* #### */ /* # # */ /* # # */ /* # # */ /* #### */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x05, 0x78, 0x84, 0x84, 0x84, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 12 9792 */ { /* */ /* */ /* */ /* */ /* */ /* #### */ /* # # */ /* # # */ /* # # */ /* #### */ /* # */ /* # */ /* ### */ /* # */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x42, 0x42, 0x42, 0x3c, 0x10, 0x10, 0x38, 0x10, 0x00, 0x00, 0x00, }, /* 13 9834 */ { /* */ /* */ /* */ /* */ /* # */ /* # ## */ /* # # */ /* # */ /* # */ /* # */ /* # */ /* ### */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x08, 0x16, 0x12, 0x10, 0x10, 0x10, 0x10, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, }, /* 14 9835 */ { /* */ /* */ /* */ /* */ /* ## */ /* # ### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ## # */ /* ## ## */ /* ## */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x18, 0x2e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x62, 0x66, 0x06, 0x00, 0x00, 0x00, }, /* 15 9788 */ { /* */ /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # */ /* # # # # */ /* # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0x28, 0x10, 0xaa, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 16 9658 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* #### */ /* ####### */ /* ##### */ /* ## */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xf0, 0xfe, 0xf8, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 17 9668 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* ##### */ /* ######## */ /* ##### */ /* ## */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1f, 0xff, 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 18 8597 */ { /* */ /* */ /* */ /* */ /* # */ /* ### */ /* # # # */ /* # */ /* # */ /* # # # */ /* ### */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x28, 0x10, 0x10, 0x2a, 0x1c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 19 8252 */ { /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x00, 0x44, 0x44, 0x00, 0x00, 0x00, 0x00, }, /* 20 182 */ { /* */ /* */ /* */ /* */ /* ###### */ /* ##### # */ /* ##### # */ /* ##### # */ /* ### # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3f, 0x7d, 0x7d, 0x7d, 0x1d, 0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x00, 0x00, }, /* 21 167 */ { /* */ /* */ /* */ /* */ /* #### */ /* # */ /* # */ /* # ## */ /* # ## */ /* ## # */ /* ## # */ /* # */ /* # */ /* #### */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3c, 0x40, 0x20, 0x58, 0x4c, 0x64, 0x34, 0x08, 0x04, 0x78, 0x00, 0x00, 0x00, }, /* 22 9644 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* ######## */ /* ######## */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 23 8616 */ { /* */ /* */ /* */ /* */ /* # */ /* ### */ /* # # # */ /* # */ /* # */ /* # # # */ /* ### */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x28, 0x10, 0x10, 0x2a, 0x1c, 0x08, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 24 8593 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* ### */ /* # # # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x1c, 0x2a, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, }, /* 25 8595 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # # # */ /* ### */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08, 0x2a, 0x1c, 0x08, 0x00, 0x00, 0x00, 0x00, }, /* 26 8594 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* # */ /* ####### */ /* # */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0xfe, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 27 8592 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* # */ /* ####### */ /* # */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x40, 0xfe, 0x40, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 28 8735 */ { /* */ /* */ /* */ /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ###### */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 29 8596 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* ####### */ /* # # */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 0xfe, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 30 9650 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* ## */ /* ## */ /* #### */ /* #### */ /* ###### */ /* ###### */ /* ######## */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x18, 0x18, 0x3c, 0x3c, 0x7e, 0x7e, 0xff, 0x00, 0x00, 0x00, }, /* 31 9660 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ####### */ /* ###### */ /* ##### */ /* #### */ /* ### */ /* ## */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x7e, 0x7c, 0x3c, 0x38, 0x18, 0x10, 0x00, 0x00, 0x00, 0x00, }, /* 32 32 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 33 33 */ { /* */ /* */ /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, /* 34 34 */ { /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x28, 0x28, 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 35 35 */ { /* */ /* */ /* */ /* # # */ /* # # */ /* # ## */ /* ####### */ /* # # */ /* # # */ /* ####### */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x12, 0x12, 0x16, 0x7f, 0x24, 0x24, 0xfe, 0x28, 0x48, 0x48, 0x00, 0x00, 0x00, 0x00, }, /* 36 36 */ { /* */ /* */ /* */ /* */ /* # */ /* ##### */ /* # # # */ /* # # */ /* ### */ /* ### */ /* # # */ /* # # # */ /* ##### */ /* # */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x08, 0x3e, 0x49, 0x48, 0x38, 0x0e, 0x09, 0x49, 0x3e, 0x08, 0x08, 0x00, 0x00, }, /* 37 37 */ { /* */ /* */ /* */ /* */ /* ## */ /* # # */ /* # # */ /* ## # */ /* ### */ /* ## ## */ /* # # */ /* # # */ /* ## */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x60, 0x90, 0x90, 0x62, 0x1c, 0x66, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00, 0x00, }, /* 38 38 */ { /* */ /* */ /* */ /* */ /* ### */ /* # */ /* # */ /* ## */ /* # # # */ /* # ## # */ /* # # # */ /* ## # */ /* #### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x20, 0x20, 0x30, 0x49, 0x4d, 0x45, 0x62, 0x3d, 0x00, 0x00, 0x00, 0x00, }, /* 39 39 */ { /* */ /* */ /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 40 40 */ { /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ 0x00, 0x00, 0x04, 0x08, 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x08, 0x08, 0x04, 0x00, 0x00, 0x00, }, /* 41 41 */ { /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ 0x00, 0x00, 0x20, 0x10, 0x10, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x10, 0x20, 0x00, 0x00, 0x00, }, /* 42 42 */ { /* */ /* */ /* */ /* 12345678 */ /* # */ /* # # # */ /* # # # */ /* ### */ /* # # # */ /* # # # */ /* # */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x92, 0x54, 0x38, 0x54, 0x92, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 43 43 */ { /* */ /* */ /* */ /* */ /* */ /* # */ /* # */ /* # */ /* ####### */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0xfe, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 44 44 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* ## */ /* # */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x10, 0x20, 0x00, 0x00, }, /* 45 45 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 46 46 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* ## */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, }, /* 47 47 */ { /* */ /* */ /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x02, 0x04, 0x04, 0x08, 0x08, 0x10, 0x10, 0x10, 0x20, 0x20, 0x40, 0x00, 0x00, }, /* 48 48 */ { /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # # */ /* # # */ /* # # # */ /* # # */ /* # # */ /* # # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x49, 0x41, 0x41, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, }, /* 49 49 */ { /* */ /* */ /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e, 0x00, 0x00, 0x00, 0x00, }, /* 50 50 */ { /* */ /* */ /* */ /* */ /* ##### */ /* # ## */ /* # */ /* # */ /* # */ /* ## */ /* ## */ /* # */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3e, 0x43, 0x01, 0x01, 0x02, 0x0c, 0x18, 0x20, 0x7f, 0x00, 0x00, 0x00, 0x00, }, /* 51 51 */ { /* */ /* */ /* */ /* */ /* ##### */ /* # # */ /* # */ /* ## */ /* ### */ /* ## */ /* # */ /* # ## */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3e, 0x41, 0x01, 0x03, 0x1c, 0x03, 0x01, 0x43, 0x3e, 0x00, 0x00, 0x00, 0x00, }, /* 52 52 */ { /* */ /* */ /* */ /* */ /* ## */ /* # # */ /* ## # */ /* # # */ /* # # */ /* # # */ /* ####### */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x06, 0x0a, 0x1a, 0x12, 0x22, 0x42, 0x7f, 0x02, 0x02, 0x00, 0x00, 0x00, 0x00, }, /* 53 53 */ { /* */ /* */ /* */ /* */ /* ###### */ /* # */ /* # */ /* ##### */ /* ## */ /* # */ /* # */ /* # ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7e, 0x40, 0x40, 0x7c, 0x03, 0x01, 0x01, 0x43, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 54 54 */ { /* */ /* */ /* */ /* */ /* #### */ /* # # */ /* # */ /* # #### */ /* ## ## */ /* # # */ /* # # */ /* # ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1e, 0x21, 0x40, 0x5e, 0x63, 0x41, 0x41, 0x23, 0x1e, 0x00, 0x00, 0x00, 0x00, }, /* 55 55 */ { /* */ /* */ /* */ /* */ /* ####### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ## */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7f, 0x02, 0x02, 0x04, 0x04, 0x08, 0x18, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, }, /* 56 56 */ { /* */ /* */ /* */ /* */ /* ##### */ /* ## ## */ /* # # */ /* ## ## */ /* ##### */ /* # # */ /* # # */ /* # # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3e, 0x63, 0x41, 0x63, 0x3e, 0x41, 0x41, 0x41, 0x3e, 0x00, 0x00, 0x00, 0x00, }, /* 57 57 */ { /* */ /* */ /* */ /* */ /* #### */ /* ## # */ /* # # */ /* # # */ /* ## ## */ /* #### # */ /* # */ /* # # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3c, 0x62, 0x41, 0x41, 0x63, 0x3d, 0x01, 0x42, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 58 58 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* ## */ /* */ /* */ /* */ /* ## */ /* ## */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, }, /* 59 59 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* ## */ /* */ /* */ /* */ /* ## */ /* ## */ /* # */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x10, 0x20, 0x00, 0x00, }, /* 60 60 */ { /* */ /* */ /* */ /* */ /* */ /* # */ /* ### */ /* ## */ /* # */ /* ## */ /* ### */ /* # */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1c, 0x60, 0x80, 0x60, 0x1c, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 61 61 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ####### */ /* */ /* */ /* ####### */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 62 62 */ { /* */ /* */ /* */ /* */ /* */ /* # */ /* ### */ /* ## */ /* # */ /* ## */ /* ### */ /* # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x70, 0x0c, 0x02, 0x0c, 0x70, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 63 63 */ { /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # */ /* # */ /* # */ /* # */ /* */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x38, 0x44, 0x04, 0x08, 0x10, 0x10, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, /* 64 64 */ { /* */ /* */ /* */ /* */ /* #### */ /* ## ## */ /* # # */ /* # ### */ /* # # # */ /* # # # */ /* # # # */ /* # ### */ /* # */ /* ## */ /* #### */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1e, 0x33, 0x21, 0x47, 0x49, 0x49, 0x49, 0x47, 0x20, 0x30, 0x1e, 0x00, 0x00, }, /* 65 65 */ { /* */ /* */ /* */ /* */ /* # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ##### */ /* ## ## */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x08, 0x14, 0x14, 0x14, 0x22, 0x22, 0x3e, 0x63, 0x41, 0x00, 0x00, 0x00, 0x00, }, /* 66 66 */ { /* */ /* */ /* */ /* */ /* ###### */ /* # # */ /* # # */ /* # # */ /* ###### */ /* # # */ /* # # */ /* # # */ /* ###### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7e, 0x41, 0x41, 0x41, 0x7e, 0x41, 0x41, 0x41, 0x7e, 0x00, 0x00, 0x00, 0x00, }, /* 67 67 */ { /* */ /* */ /* */ /* */ /* #### */ /* # # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1e, 0x21, 0x40, 0x40, 0x40, 0x40, 0x40, 0x21, 0x1e, 0x00, 0x00, 0x00, 0x00, }, /* 68 68 */ { /* */ /* */ /* */ /* */ /* ##### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7c, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 69 69 */ { /* */ /* */ /* */ /* */ /* ####### */ /* # */ /* # */ /* # */ /* ####### */ /* # */ /* # */ /* # */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7f, 0x40, 0x40, 0x40, 0x7f, 0x40, 0x40, 0x40, 0x7f, 0x00, 0x00, 0x00, 0x00, }, /* 70 70 */ { /* */ /* */ /* */ /* */ /* ####### */ /* # */ /* # */ /* # */ /* ####### */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7f, 0x40, 0x40, 0x40, 0x7f, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, }, /* 71 71 */ { /* */ /* */ /* */ /* */ /* #### */ /* # # */ /* # */ /* # */ /* # ## */ /* # # */ /* # # */ /* # # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1e, 0x21, 0x40, 0x40, 0x43, 0x41, 0x41, 0x21, 0x1e, 0x00, 0x00, 0x00, 0x00, }, /* 72 72 */ { /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* ####### */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x7f, 0x41, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00, }, /* 73 73 */ { /* */ /* */ /* */ /* */ /* ##### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 74 74 */ { /* */ /* */ /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x44, 0x38, 0x00, 0x00, 0x00, 0x00, }, /* 75 75 */ { /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* ### */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x42, 0x44, 0x48, 0x50, 0x70, 0x48, 0x44, 0x44, 0x42, 0x00, 0x00, 0x00, 0x00, }, /* 76 76 */ { /* */ /* */ /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x7f, 0x00, 0x00, 0x00, 0x00, }, /* 77 77 */ { /* */ /* */ /* */ /* */ /* ## ## */ /* ## ## */ /* # # # # */ /* # # # # */ /* # # # # */ /* # # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x63, 0x63, 0x55, 0x55, 0x55, 0x49, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00, }, /* 78 78 */ { /* */ /* */ /* */ /* */ /* ## # */ /* ## # */ /* # # # */ /* # # # */ /* # # # */ /* # # # */ /* # # # */ /* # ## */ /* # ## */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x61, 0x61, 0x51, 0x51, 0x49, 0x45, 0x45, 0x43, 0x43, 0x00, 0x00, 0x00, 0x00, }, /* 79 79 */ { /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, }, /* 80 80 */ { /* */ /* */ /* */ /* */ /* ###### */ /* # ## */ /* # # */ /* # # */ /* # ## */ /* ###### */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7e, 0x43, 0x41, 0x41, 0x43, 0x7e, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, }, /* 81 81 */ { /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # ## */ /* #### */ /* ## */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x23, 0x1e, 0x06, 0x02, 0x00, 0x00, }, /* 82 82 */ { /* */ /* */ /* */ /* */ /* ###### */ /* # ## */ /* # # */ /* # # */ /* ###### */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7e, 0x43, 0x41, 0x41, 0x7e, 0x42, 0x41, 0x41, 0x41, 0x00, 0x00, 0x00, 0x00, }, /* 83 83 */ { /* */ /* */ /* */ /* */ /* ##### */ /* ## # */ /* # */ /* ## */ /* ##### */ /* ## */ /* # */ /* # ## */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3e, 0x61, 0x40, 0x60, 0x3e, 0x03, 0x01, 0x43, 0x3e, 0x00, 0x00, 0x00, 0x00, }, /* 84 84 */ { /* */ /* */ /* */ /* */ /* ####### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, /* 85 85 */ { /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x3e, 0x00, 0x00, 0x00, 0x00, }, /* 86 86 */ { /* */ /* */ /* */ /* */ /* # # */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x41, 0x63, 0x22, 0x22, 0x22, 0x14, 0x14, 0x14, 0x08, 0x00, 0x00, 0x00, 0x00, }, /* 87 87 */ { /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # ## # */ /* # ## # */ /* # ## # */ /* ## ## */ /* ## ## */ /* ## ## */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x81, 0x81, 0x81, 0x5a, 0x5a, 0x5a, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, }, /* 88 88 */ { /* */ /* */ /* */ /* */ /* ## ## */ /* # # */ /* # # */ /* ### */ /* # */ /* # # */ /* ## ## */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x63, 0x22, 0x14, 0x1c, 0x08, 0x14, 0x36, 0x22, 0x41, 0x00, 0x00, 0x00, 0x00, }, /* 89 89 */ { /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x82, 0x44, 0x28, 0x28, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, /* 90 90 */ { /* */ /* */ /* */ /* */ /* ####### */ /* ## */ /* ## */ /* # */ /* # */ /* # */ /* ## */ /* ## */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7f, 0x03, 0x06, 0x04, 0x08, 0x10, 0x30, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, }, /* 91 91 */ { /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ### */ /* */ /* */ /* */ 0x00, 0x00, 0x1c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1c, 0x00, 0x00, 0x00, }, /* 92 92 */ { /* */ /* */ /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x40, 0x20, 0x20, 0x10, 0x10, 0x10, 0x08, 0x08, 0x04, 0x04, 0x02, 0x00, 0x00, }, /* 93 93 */ { /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ### */ /* */ /* */ /* */ 0x00, 0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x38, 0x00, 0x00, 0x00, }, /* 94 94 */ { /* */ /* */ /* */ /* */ /* # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 95 95 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, }, /* 96 96 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 97 97 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # */ /* ##### */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x3e, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 98 98 */ { /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x7c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 99 99 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # */ /* # */ /* # */ /* # # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x40, 0x40, 0x40, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, }, /* 100 100 */ { /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x3e, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3e, 0x00, 0x00, 0x00, 0x00, }, /* 101 101 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* #### */ /* ## ## */ /* # # */ /* ###### */ /* # */ /* ## # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x42, 0x7e, 0x40, 0x62, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 102 102 */ { /* */ /* */ /* ## */ /* # */ /* # */ /* # */ /* ##### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x0c, 0x10, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, /* 103 103 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* ### # */ /* # */ /* # # */ /* ### */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3a, 0x02, 0x22, 0x1c, 0x00, }, /* 104 104 */ { /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # ### */ /* ## # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x5c, 0x62, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00, 0x00, 0x00, }, /* 105 105 */ { /* */ /* */ /* # */ /* */ /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 106 106 */ { /* */ /* */ /* # */ /* */ /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ### */ /* */ 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x38, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x70, 0x00, }, /* 107 107 */ { /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # # */ /* # # */ /* # # */ /* ### */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x40, 0x40, 0x40, 0x40, 0x44, 0x48, 0x50, 0x70, 0x48, 0x44, 0x42, 0x00, 0x00, 0x00, 0x00, }, /* 108 108 */ { /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0e, 0x00, 0x00, 0x00, 0x00, }, /* 109 109 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ### ## */ /* # # # */ /* # # # */ /* # # # */ /* # # # */ /* # # # */ /* # # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x00, 0x00, 0x00, 0x00, }, /* 110 110 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # ### */ /* ## # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x62, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00, 0x00, 0x00, }, /* 111 111 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* #### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 112 112 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* ##### */ /* # */ /* # */ /* # */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x7c, 0x40, 0x40, 0x40, 0x00, }, /* 113 113 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* ### # */ /* # */ /* # */ /* # */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3a, 0x02, 0x02, 0x02, 0x00, }, /* 114 114 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* #### */ /* ## # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x32, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, }, /* 115 115 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* #### */ /* # # */ /* # */ /* #### */ /* # */ /* # # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x42, 0x40, 0x3c, 0x02, 0x42, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 116 116 */ { /* */ /* */ /* */ /* */ /* # */ /* # */ /* ###### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x7e, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0e, 0x00, 0x00, 0x00, 0x00, }, /* 117 117 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 118 118 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* ## ## */ /* # # */ /* # # */ /* #### */ /* ## */ /* ## */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x66, 0x24, 0x24, 0x3c, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, }, /* 119 119 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # ## # */ /* # ## # */ /* # ## # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x81, 0x5a, 0x5a, 0x5a, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, }, /* 120 120 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* ## */ /* ## */ /* ## */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x24, 0x18, 0x18, 0x18, 0x24, 0x42, 0x00, 0x00, 0x00, 0x00, }, /* 121 121 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ## */ /* # */ /* # */ /* # */ /* # */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x22, 0x24, 0x24, 0x14, 0x18, 0x08, 0x08, 0x10, 0x20, 0x00, }, /* 122 122 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ###### */ /* # */ /* # */ /* ## */ /* # */ /* # */ /* ###### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x02, 0x04, 0x18, 0x20, 0x40, 0x7e, 0x00, 0x00, 0x00, 0x00, }, /* 123 123 */ { /* */ /* */ /* ## */ /* # */ /* # */ /* # */ /* # */ /* ## */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ## */ /* */ /* */ /* */ 0x00, 0x00, 0x0c, 0x10, 0x10, 0x10, 0x10, 0x60, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0c, 0x00, 0x00, 0x00, }, /* 124 124 */ { /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, }, /* 125 125 */ { /* */ /* */ /* ## */ /* # */ /* # */ /* # */ /* # */ /* ## */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ## */ /* */ /* */ /* */ 0x00, 0x00, 0x60, 0x10, 0x10, 0x10, 0x10, 0x0c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x60, 0x00, 0x00, 0x00, }, /* 126 126 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ### # */ /* # ## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 127 8962 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ###### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x42, 0x42, 0x42, 0x42, 0x7e, 0x00, 0x00, 0x00, 0x00, }, /* 128 199 */ { /* */ /* */ /* */ /* */ /* #### */ /* # # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # # */ /* #### */ /* # */ /* # */ /* ### */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1e, 0x21, 0x40, 0x40, 0x40, 0x40, 0x40, 0x21, 0x1e, 0x04, 0x04, 0x1c, 0x00, }, /* 129 252 */ { /* */ /* */ /* */ /* # # */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 130 233 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* #### */ /* ## ## */ /* # # */ /* ###### */ /* # */ /* ## # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x3c, 0x66, 0x42, 0x7e, 0x40, 0x62, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 131 226 */ { /* */ /* */ /* */ /* ## */ /* # # */ /* */ /* ### */ /* # # */ /* # */ /* ##### */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x18, 0x24, 0x00, 0x1c, 0x22, 0x02, 0x3e, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 132 228 */ { /* */ /* */ /* */ /* # # */ /* */ /* */ /* ### */ /* # # */ /* # */ /* ##### */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x1c, 0x22, 0x02, 0x3e, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 133 224 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* ### */ /* # # */ /* # */ /* ##### */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x1c, 0x22, 0x02, 0x3e, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 134 229 */ { /* */ /* ## */ /* # # */ /* # # */ /* ## */ /* */ /* ### */ /* # # */ /* # */ /* ##### */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x18, 0x24, 0x24, 0x18, 0x00, 0x1c, 0x22, 0x02, 0x3e, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 135 231 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # */ /* # */ /* # */ /* # # */ /* ### */ /* # */ /* # */ /* ### */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x40, 0x40, 0x40, 0x22, 0x1c, 0x04, 0x04, 0x1c, 0x00, }, /* 136 234 */ { /* */ /* */ /* */ /* ## */ /* # # */ /* */ /* #### */ /* ## ## */ /* # # */ /* ###### */ /* # */ /* ## # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x18, 0x24, 0x00, 0x3c, 0x66, 0x42, 0x7e, 0x40, 0x62, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 137 235 */ { /* */ /* */ /* */ /* # # */ /* */ /* */ /* #### */ /* ## ## */ /* # # */ /* ###### */ /* # */ /* ## # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x3c, 0x66, 0x42, 0x7e, 0x40, 0x62, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 138 232 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* #### */ /* ## ## */ /* # # */ /* ###### */ /* # */ /* ## # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x3c, 0x66, 0x42, 0x7e, 0x40, 0x62, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 139 239 */ { /* */ /* */ /* */ /* # # */ /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 140 238 */ { /* */ /* */ /* */ /* ## */ /* # # */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x30, 0x48, 0x00, 0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 141 236 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 142 196 */ { /* */ /* */ /* # # */ /* */ /* # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ##### */ /* ## ## */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x14, 0x00, 0x08, 0x14, 0x14, 0x14, 0x22, 0x22, 0x3e, 0x63, 0x41, 0x00, 0x00, 0x00, 0x00, }, /* 143 197 */ { /* */ /* ### */ /* # # */ /* # # */ /* # */ /* # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ##### */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x1c, 0x14, 0x14, 0x08, 0x08, 0x14, 0x14, 0x14, 0x22, 0x3e, 0x22, 0x41, 0x00, 0x00, 0x00, 0x00, }, /* 144 201 */ { /* */ /* # */ /* # */ /* */ /* ####### */ /* # */ /* # */ /* # */ /* ####### */ /* # */ /* # */ /* # */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x08, 0x10, 0x00, 0x7f, 0x40, 0x40, 0x40, 0x7f, 0x40, 0x40, 0x40, 0x7f, 0x00, 0x00, 0x00, 0x00, }, /* 145 230 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ## ## */ /* # # */ /* # # */ /* ###### */ /* # # */ /* # # */ /* ## ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0x12, 0x12, 0x7e, 0x50, 0x50, 0x6e, 0x00, 0x00, 0x00, 0x00, }, /* 146 198 */ { /* */ /* */ /* */ /* */ /* ##### */ /* # # */ /* # # */ /* # # */ /* # ### */ /* # # */ /* #### */ /* # # */ /* # ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3e, 0x28, 0x28, 0x28, 0x4e, 0x48, 0x78, 0x88, 0x8e, 0x00, 0x00, 0x00, 0x00, }, /* 147 244 */ { /* */ /* */ /* */ /* ## */ /* # # */ /* */ /* #### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x18, 0x24, 0x00, 0x3c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 148 246 */ { /* */ /* */ /* */ /* # # */ /* */ /* */ /* #### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x3c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 149 242 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* #### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x3c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 150 251 */ { /* */ /* */ /* */ /* ## */ /* # # */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x18, 0x24, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 151 249 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 152 255 */ { /* */ /* */ /* */ /* # # */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ## */ /* # */ /* # */ /* # */ /* ## */ /* */ 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x42, 0x22, 0x24, 0x24, 0x14, 0x18, 0x08, 0x08, 0x10, 0x30, 0x00, }, /* 153 214 */ { /* */ /* */ /* # # */ /* */ /* ### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x14, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x41, 0x41, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, }, /* 154 220 */ { /* */ /* */ /* # # */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x14, 0x00, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x3e, 0x00, 0x00, 0x00, 0x00, }, /* 155 162 */ { /* */ /* */ /* */ /* */ /* # */ /* # */ /* ### */ /* # # # */ /* # # */ /* # # */ /* # # */ /* # # # */ /* ### */ /* # */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x38, 0x54, 0x50, 0x50, 0x50, 0x54, 0x38, 0x10, 0x10, 0x00, 0x00, }, /* 156 163 */ { /* */ /* */ /* */ /* */ /* ### */ /* # */ /* # */ /* # */ /* #### */ /* # */ /* # */ /* # */ /* ###### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x20, 0x20, 0x20, 0x78, 0x20, 0x20, 0x20, 0xfc, 0x00, 0x00, 0x00, 0x00, }, /* 157 165 */ { /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* ### ### */ /* # */ /* ####### */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x82, 0x44, 0x28, 0xee, 0x10, 0xfe, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, /* 158 8359 */ { /* */ /* */ /* */ /* */ /* #### */ /* # ## */ /* # ###### */ /* # ## # */ /* #### # */ /* # # # */ /* # # # */ /* # # # */ /* # ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0xf0, 0xb0, 0xbf, 0xb4, 0xf4, 0x92, 0x91, 0x91, 0x8e, 0x00, 0x00, 0x00, 0x00, }, /* 159 402 */ { /* */ /* */ /* */ /* ### */ /* # */ /* # */ /* ###### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ 0x00, 0x00, 0x00, 0x0e, 0x10, 0x10, 0x7e, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x20, 0x00, }, /* 160 225 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* ### */ /* # # */ /* # */ /* ##### */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x1c, 0x22, 0x02, 0x3e, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 161 237 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* ### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x70, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7c, 0x00, 0x00, 0x00, 0x00, }, /* 162 243 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* #### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x3c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 163 250 */ { /* */ /* */ /* */ /* # */ /* # */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # ## */ /* ### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x46, 0x3a, 0x00, 0x00, 0x00, 0x00, }, /* 164 241 */ { /* */ /* */ /* */ /* ## # */ /* # ## */ /* */ /* # ### */ /* ## # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x34, 0x2c, 0x00, 0x5c, 0x62, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00, 0x00, 0x00, }, /* 165 209 */ { /* */ /* ### # */ /* # ### */ /* */ /* ## # */ /* ## # */ /* # # # */ /* # # # */ /* # # # */ /* # # # */ /* # # # */ /* # ## */ /* # ## */ /* */ /* */ /* */ /* */ 0x00, 0x3a, 0x2e, 0x00, 0x61, 0x61, 0x51, 0x51, 0x49, 0x45, 0x45, 0x43, 0x43, 0x00, 0x00, 0x00, 0x00, }, /* 166 170 */ { /* */ /* */ /* */ /* */ /* #### */ /* # */ /* #### */ /* # # */ /* ##### */ /* */ /* ##### */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x3c, 0x02, 0x1e, 0x22, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 167 186 */ { /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # # */ /* # # */ /* ### */ /* */ /* ##### */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 168 191 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* # */ /* */ /* # */ /* # */ /* ## */ /* ## */ /* # */ /* # # */ /* ### */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x10, 0x10, 0x30, 0x60, 0x40, 0x44, 0x38, 0x00, }, /* 169 8976 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ###### */ /* # */ /* # */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 170 172 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ####### */ /* # */ /* # */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 171 189 */ { /* */ /* */ /* */ /* ## */ /* # */ /* # */ /* # */ /* ### ## */ /* ### */ /* ## */ /* #### */ /* # */ /* # */ /* # */ /* #### */ /* */ /* */ 0x00, 0x00, 0x00, 0x60, 0x20, 0x20, 0x20, 0x76, 0x38, 0xc0, 0x1e, 0x02, 0x04, 0x08, 0x1e, 0x00, 0x00, }, /* 172 188 */ { /* */ /* */ /* */ /* ## */ /* # */ /* # */ /* # */ /* ### ## */ /* ### */ /* ## */ /* # */ /* ## */ /* # # */ /* #### */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x60, 0x20, 0x20, 0x20, 0x76, 0x38, 0xc0, 0x04, 0x0c, 0x14, 0x1e, 0x04, 0x00, 0x00, }, /* 173 161 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* # */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, }, /* 174 171 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* ## ## */ /* ## ## */ /* ## ## */ /* ## ## */ /* # # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x36, 0x6c, 0x6c, 0x36, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 175 187 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* ## ## */ /* ## ## */ /* ## ## */ /* ## ## */ /* # # */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x6c, 0x36, 0x36, 0x6c, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 176 9617 */ { /* */ /* # # */ /* */ /* # # */ /* */ /* # # */ /* */ /* # # */ /* */ /* # # */ /* */ /* # # */ /* */ /* # # */ /* */ /* # # */ /* */ 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, }, /* 177 9618 */ { /* ## # # */ /* # # ## */ /* ## # # */ /* ## # # */ /* # # ## */ /* ## # # */ /* ## # # */ /* # # ## */ /* # # ## */ /* ## # # */ /* # # ## */ /* # # ## */ /* ## # # */ /* # # ## */ /* # # ## */ /* ## # # */ /* # # ## */ 0x69, 0x96, 0x69, 0x69, 0x96, 0x69, 0x69, 0x96, 0x96, 0x69, 0x96, 0x96, 0x69, 0x96, 0x96, 0x69, 0x96, }, /* 178 9619 */ { /* ######## */ /* ### ### */ /* ######## */ /* ## ### # */ /* ######## */ /* ### ### */ /* ######## */ /* ## ### # */ /* ######## */ /* ### ### */ /* ######## */ /* ## ### # */ /* ######## */ /* ### ### */ /* ######## */ /* ## ### # */ /* ######## */ 0xff, 0x77, 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff, 0x77, 0xff, 0xdd, 0xff, }, /* 179 9474 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 180 9508 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* #### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xf0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 181 9569 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* #### */ /* # */ /* #### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xf0, 0x10, 0xf0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 182 9570 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ### # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xe8, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 183 9558 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 184 9557 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* #### */ /* # */ /* #### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x10, 0xf0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 185 9571 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ### # */ /* # */ /* ### # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xe8, 0x08, 0xe8, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 186 9553 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 187 9559 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* # */ /* ### # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x08, 0xe8, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 188 9565 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ### # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xe8, 0x08, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 189 9564 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ##### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 190 9563 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* #### */ /* # */ /* #### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xf0, 0x10, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 191 9488 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* #### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 192 9492 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 193 9524 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ######## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 194 9516 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 195 9500 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 196 9472 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 197 9532 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ######## */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xff, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 198 9566 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* # */ /* ##### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 199 9567 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # #### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x2f, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 200 9562 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # #### */ /* # */ /* ###### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x2f, 0x20, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 201 9556 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ###### */ /* # */ /* # #### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x20, 0x2f, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 202 9577 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ### #### */ /* */ /* ######## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xef, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 203 9574 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* */ /* ### #### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xef, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 204 9568 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # #### */ /* # */ /* # #### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x2f, 0x20, 0x2f, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 205 9552 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* */ /* ######## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 206 9580 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ### #### */ /* */ /* ### #### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xef, 0x00, 0xef, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 207 9575 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ######## */ /* */ /* ######## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 208 9576 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ######## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 209 9572 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* */ /* ######## */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0xff, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 210 9573 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 211 9561 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ###### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 212 9560 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ##### */ /* # */ /* ##### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1f, 0x10, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 213 9554 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* # */ /* ##### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x10, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 214 9555 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ###### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 215 9579 */ { /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ######## */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0xff, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, }, /* 216 9578 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ######## */ /* # */ /* ######## */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xff, 0x10, 0xff, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 217 9496 */ { /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* #### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 218 9484 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, }, /* 219 9608 */ { /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, /* 220 9604 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, }, /* 221 9612 */ { /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, }, /* 222 9616 */ { /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ /* #### */ 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, }, /* 223 9600 */ { /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 224 945 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* #### # */ /* # # # */ /* ## # */ /* ## # */ /* # # */ /* # ## */ /* #### # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x4a, 0xc4, 0xc4, 0x44, 0x4c, 0x7a, 0x00, 0x00, 0x00, 0x00, }, /* 225 223 */ { /* */ /* */ /* ### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # ### */ /* # ## */ /* # # */ /* # # */ /* # ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x38, 0x44, 0x44, 0x48, 0x50, 0x50, 0x5c, 0x46, 0x42, 0x42, 0x5c, 0x00, 0x00, 0x00, 0x00, }, /* 226 915 */ { /* */ /* */ /* */ /* */ /* ####### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, }, /* 227 960 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ###### */ /* ## # */ /* ## # */ /* ## # */ /* ## # */ /* ## # */ /* ## ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x64, 0x64, 0x64, 0x64, 0x64, 0x67, 0x00, 0x00, 0x00, 0x00, }, /* 228 931 */ { /* */ /* */ /* */ /* */ /* ####### */ /* ## */ /* ## */ /* # */ /* # */ /* # */ /* ## */ /* ## */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x7f, 0x60, 0x30, 0x10, 0x08, 0x10, 0x30, 0x60, 0x7f, 0x00, 0x00, 0x00, 0x00, }, /* 229 963 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* ## # */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x64, 0x42, 0x42, 0x42, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 230 181 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # ## */ /* ####### */ /* # */ /* # */ /* # */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x46, 0x7f, 0x40, 0x40, 0x40, 0x00, }, /* 231 964 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ## */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x10, 0x10, 0x10, 0x10, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, }, /* 232 934 */ { /* */ /* */ /* */ /* */ /* ### */ /* # */ /* ##### */ /* # # # */ /* # # # */ /* # # # */ /* ##### */ /* # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x38, 0x10, 0x7c, 0x92, 0x92, 0x92, 0x7c, 0x10, 0x38, 0x00, 0x00, 0x00, 0x00, }, /* 233 920 */ { /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # # */ /* # # */ /* # ### # */ /* # # */ /* # # */ /* # # */ /* ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x5d, 0x41, 0x41, 0x22, 0x1c, 0x00, 0x00, 0x00, 0x00, }, /* 234 937 */ { /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* # # */ /* ### ### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x41, 0x41, 0x41, 0x63, 0x22, 0x77, 0x00, 0x00, 0x00, 0x00, }, /* 235 948 */ { /* */ /* */ /* */ /* #### */ /* ## */ /* ## */ /* #### */ /* ## ## */ /* # # */ /* # # */ /* # # */ /* ## ## */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x3c, 0x60, 0x60, 0x3c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 236 8734 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ## ## */ /* # ## # */ /* # # # */ /* # ## # */ /* ## ## */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6c, 0xb2, 0x92, 0xb2, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 237 966 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ##### */ /* ## # ## */ /* # # # */ /* # # # */ /* # # # */ /* ## # ## */ /* ##### */ /* # */ /* # */ /* # */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x6b, 0x49, 0x49, 0x49, 0x6b, 0x3e, 0x08, 0x08, 0x08, 0x00, }, /* 238 949 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* #### */ /* # */ /* # */ /* ### */ /* # */ /* # */ /* #### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x40, 0x40, 0x38, 0x40, 0x40, 0x3c, 0x00, 0x00, 0x00, 0x00, }, /* 239 8745 */ { /* */ /* */ /* */ /* */ /* */ /* # */ /* #### */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x3c, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x00, 0x00, 0x00, 0x00, }, /* 240 8801 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ###### */ /* */ /* ###### */ /* */ /* */ /* ###### */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 241 177 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* # */ /* # */ /* ####### */ /* # */ /* # */ /* */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0xfe, 0x10, 0x10, 0x00, 0xfe, 0x00, 0x00, 0x00, 0x00, }, /* 242 8805 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* #### */ /* ## */ /* ### */ /* ## */ /* */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x3c, 0x03, 0x1c, 0x60, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, }, /* 243 8804 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* #### */ /* ## */ /* ### */ /* ## */ /* */ /* ####### */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x1e, 0x60, 0x1c, 0x03, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, }, /* 244 8992 */ { /* */ /* ### */ /* # # */ /* # */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ 0x00, 0x0e, 0x0a, 0x08, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, }, /* 245 8993 */ { /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* ## */ /* # */ /* # */ /* # */ /* # */ /* # # */ /* ## */ /* */ 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x10, 0x10, 0x10, 0x10, 0x50, 0x60, 0x00, }, /* 246 247 */ { /* */ /* */ /* */ /* */ /* */ /* ## */ /* ## */ /* */ /* ######## */ /* */ /* ## */ /* ## */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0xff, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 247 8776 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ### # */ /* # ### */ /* ### # */ /* # ## */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x47, 0x39, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 248 176 */ { /* */ /* */ /* */ /* */ /* ## */ /* # # */ /* # # */ /* ## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 249 8729 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* #### */ /* #### */ /* ## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3c, 0x3c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 250 183 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* ## */ /* ## */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 251 8730 */ { /* */ /* */ /* # */ /* # */ /* # */ /* # */ /* # */ /* ## # */ /* # # */ /* # # */ /* ## */ /* # */ /* # */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x02, 0x02, 0x04, 0x04, 0x04, 0xc8, 0x28, 0x28, 0x30, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, }, /* 252 8319 */ { /* */ /* */ /* */ /* */ /* */ /* ###### */ /* # # */ /* # # */ /* # # */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x24, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 253 178 */ { /* */ /* */ /* */ /* */ /* ### */ /* # # */ /* # */ /* # */ /* #### */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x1c, 0x24, 0x08, 0x10, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, /* 254 9632 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* ######## */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, }, /* 255 32 */ { /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ /* */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, }; ================================================ FILE: apps/terminal-palette.h ================================================ /** * @file apps/terminal-palette.h * @brief Terminal color palette * * Provides the color table for both the basic 16 colors (here, * chosen from the Tango design documents), as well as the other * colors making up the basic 256 color palette derived from xterm. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2018 K. Lange */ #define PALETTE_COLORS 256 uint32_t term_colors[PALETTE_COLORS] = { /* black */ 0x000000, /* red */ 0xcc0000, /* green */ 0x3e9a06, /* brown */ 0xc4a000, /* navy */ 0x3465a4, /* purple */ 0x75507b, /* d cyan */ 0x06989a, /* gray */ 0xeeeeec, /* d gray */ 0x555753, /* red */ 0xef2929, /* green */ 0x8ae234, /* yellow */ 0xfce94f, /* blue */ 0x729fcf, /* magenta*/ 0xad7fa8, /* cyan */ 0x34e2e2, /* white */ 0xFFFFFF, 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f, 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af, 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff, 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f, 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af, 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff, 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f, 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af, 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff, 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f, 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af, 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff, 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f, 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af, 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff, 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f, 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af, 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff, 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f, 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af, 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff, 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f, 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af, 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff, 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f, 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af, 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff, 0x080808, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e, 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e, 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada, 0xe4e4e4, 0xeeeeee, }; ================================================ FILE: apps/terminal-vga.c ================================================ /** * @brief Virtual terminal emulator, for VGA text mode. * * Basicall the same as @ref terminal.c but outputs to the VGA * text mode buffer instead of managing a graphical window. * * Supports >16 colors by using a dumb closest-match approach. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vga-palette.h" #define USE_BELL 0 /* master and slave pty descriptors */ static int fd_master, fd_slave; static FILE * terminal; static ssize_t term_width = 80; /* Width of the terminal (in cells) */ static ssize_t term_height = 25; /* Height of the terminal (in cells) */ static uint16_t csr_x = 0; /* Cursor X */ static uint16_t csr_y = 0; /* Cursor Y */ static uint16_t csr_h = 0; static term_cell_t * term_buffer = NULL; /* The terminal cell buffer */ static term_cell_t * term_buffer_a = NULL; static term_cell_t * term_buffer_b = NULL; static int active_buffer = 0; static int _orig_x = 0; static int _orig_y = 0; static uint32_t _orig_fg = 7; static uint32_t _orig_bg = 0; static uint32_t current_fg = 7; /* Current foreground color */ static uint32_t current_bg = 0; /* Current background color */ static uint8_t cursor_on = 1; /* Whether or not the cursor should be rendered */ static uint8_t _login_shell = 0; /* Whether we're going to display a login shell or not */ static uint64_t mouse_ticks = 0; static int selection = 0; static int selection_start_x = 0; static int selection_start_y = 0; static int selection_end_x = 0; static int selection_end_y = 0; static char * selection_text = NULL; #define char_width 1 #define char_height 1 term_state_t * ansi_state = NULL; void reinit(void); /* Defined way further down */ void term_redraw_cursor(); void term_clear(); void dump_buffer(); static uint64_t get_ticks(void) { struct timeval now; gettimeofday(&now, NULL); return (uint64_t)now.tv_sec * 1000000LL + (uint64_t)now.tv_usec; } static int color_distance(uint32_t a, uint32_t b) { int a_r = (a & 0xFF0000) >> 16; int a_g = (a & 0xFF00) >> 8; int a_b = (a & 0xFF); int b_r = (b & 0xFF0000) >> 16; int b_g = (b & 0xFF00) >> 8; int b_b = (b & 0xFF); int distance = 0; distance += abs(a_r - b_r) * 3; distance += abs(a_g - b_g) * 6; distance += abs(a_b - b_b) * 10; return distance; } static uint32_t vga_base_colors[] = { 0x000000, 0xAA0000, 0x00AA00, 0xAA5500, 0x0000AA, 0xAA00AA, 0x00AAAA, 0xAAAAAA, 0x555555, 0xFF5555, 0x55AA55, 0xFFFF55, 0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF, }; #if 0 static int is_gray(uint32_t a) { int a_r = (a & 0xFF0000) >> 16; int a_g = (a & 0xFF00) >> 8; int a_b = (a & 0xFF); return (a_r == a_g && a_g == a_b); } #endif static int best_match(uint32_t a) { int best_distance = INT32_MAX; int best_index = 0; for (int j = 0; j < 16; ++j) { int distance = color_distance(a, vga_base_colors[j]); if (distance < best_distance) { best_index = j; best_distance = distance; } } return best_index; } volatile int exit_application = 0; /* Returns the lower of two shorts */ uint16_t min(uint16_t a, uint16_t b) { return (a < b) ? a : b; } /* Returns the higher of two shorts */ uint16_t max(uint16_t a, uint16_t b) { return (a > b) ? a : b; } void set_title(char * c) { /* Do nothing */ } static void cell_redraw(uint16_t x, uint16_t y); static void cell_redraw_inverted(uint16_t x, uint16_t y); int is_in_selection(int x, int y) { if (!selection) return 0; if (selection_end_y < selection_start_y) { if (y == selection_end_y) { return (x >= selection_end_x); } else if (y == selection_start_y) { return (x <= selection_start_x); } else { return (y > selection_end_y && y < selection_start_y); } } else if (selection_end_y > selection_start_y) { if (y == selection_start_y) { return (x >= selection_start_x); } else if (y == selection_end_y) { return (x <= selection_end_x); } else { return (y > selection_start_y && y < selection_end_y); } } else if (selection_end_y == selection_start_y) { if (y != selection_end_y) return 0; if (selection_start_x > selection_end_x) { return (x >= selection_end_x && x <= selection_start_x); } else if (selection_start_x < selection_end_x) { return (x >= selection_start_x && x <= selection_end_x); } else { return x == selection_start_x; } } return 0; } void iterate_selection(void (*func)(uint16_t x, uint16_t y)) { if (!selection) return; if (selection_end_y < selection_start_y) { for (int x = selection_end_x; x < term_width; ++x) { func(x, selection_end_y); } for (int y = selection_end_y + 1; y < selection_start_y; ++y) { for (int x = 0; x < term_width; ++x) { func(x, y); } } for (int x = 0; x <= selection_start_x; ++x) { func(x, selection_start_y); } } else if (selection_start_y == selection_end_y) { if (selection_start_x > selection_end_x) { for (int x = selection_end_x; x <= selection_start_x; ++x) { func(x, selection_start_y); } } else { for (int x = selection_start_x; x <= selection_end_x; ++x) { func(x, selection_start_y); } } } else { for (int x = selection_start_x; x < term_width; ++x) { func(x, selection_start_y); } for (int y = selection_start_y + 1; y < selection_end_y; ++y) { for (int x = 0; x < term_width; ++x) { func(x, y); } } for (int x = 0; x <= selection_end_x; ++x) { func(x, selection_end_y); } } } void redraw_selection(void) { iterate_selection(cell_redraw_inverted); } static term_cell_t * cell_at(uint16_t x, uint16_t y) { return (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t)); } static void mark_cell(uint16_t x, uint16_t y) { term_cell_t * c = cell_at(x,y); if (c) { c->flags |= 0x200; } } static void mark_selection(void) { iterate_selection(mark_cell); } static void red_cell(uint16_t x, uint16_t y) { term_cell_t * c = cell_at(x,y); if (c) { if (c->flags & 0x200) { c->flags &= ~(0x200); } else { c->flags |= 0x400; } } } static void flip_selection(void) { iterate_selection(red_cell); for (int y = 0; y < term_height; ++y) { for (int x = 0; x < term_width; ++x) { term_cell_t * c = cell_at(x,y); if (c) { if (c->flags & 0x200) cell_redraw(x,y); if (c->flags & 0x400) cell_redraw_inverted(x,y); c->flags &= ~(0x600); } } } } static int _selection_count = 0; static int _selection_i = 0; static int to_eight(uint32_t codepoint, char * out) { memset(out, 0x00, 7); if (codepoint < 0x0080) { out[0] = (char)codepoint; } else if (codepoint < 0x0800) { out[0] = 0xC0 | (codepoint >> 6); out[1] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x10000) { out[0] = 0xE0 | (codepoint >> 12); out[1] = 0x80 | ((codepoint >> 6) & 0x3F); out[2] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x200000) { out[0] = 0xF0 | (codepoint >> 18); out[1] = 0x80 | ((codepoint >> 12) & 0x3F); out[2] = 0x80 | ((codepoint >> 6) & 0x3F); out[3] = 0x80 | ((codepoint) & 0x3F); } else if (codepoint < 0x4000000) { out[0] = 0xF8 | (codepoint >> 24); out[1] = 0x80 | (codepoint >> 18); out[2] = 0x80 | ((codepoint >> 12) & 0x3F); out[3] = 0x80 | ((codepoint >> 6) & 0x3F); out[4] = 0x80 | ((codepoint) & 0x3F); } else { out[0] = 0xF8 | (codepoint >> 30); out[1] = 0x80 | ((codepoint >> 24) & 0x3F); out[2] = 0x80 | ((codepoint >> 18) & 0x3F); out[3] = 0x80 | ((codepoint >> 12) & 0x3F); out[4] = 0x80 | ((codepoint >> 6) & 0x3F); out[5] = 0x80 | ((codepoint) & 0x3F); } return strlen(out); } void count_selection(uint16_t x, uint16_t y) { term_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t)); if (((uint32_t *)cell)[0] != 0x00000000) { char tmp[7]; _selection_count += to_eight(cell->c, tmp); } if (x == term_width - 1) { _selection_count++; } } void write_selection(uint16_t x, uint16_t y) { term_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t)); if (((uint32_t *)cell)[0] != 0x00000000) { char tmp[7]; int count = to_eight(cell->c, tmp); for (int i = 0; i < count; ++i) { selection_text[_selection_i] = tmp[i]; _selection_i++; } } if (x == term_width - 1) { selection_text[_selection_i] = '\n';; _selection_i++; } } char * copy_selection(void) { _selection_count = 0; iterate_selection(count_selection); if (selection_text) { free(selection_text); } if (_selection_count == 0) { return NULL; } selection_text = malloc(_selection_count + 1); selection_text[_selection_count] = '\0'; _selection_i = 0; iterate_selection(write_selection); if (selection_text[_selection_count-1] == '\n') { /* Don't end on a line feed */ selection_text[_selection_count-1] = '\0'; } return selection_text; } static volatile int input_buffer_lock = 0; static int input_buffer_semaphore[2]; static list_t * input_buffer_queue = NULL; struct input_data { size_t len; char data[]; }; void * handle_input_writing(void * unused) { (void)unused; while (1) { /* Read one byte from semaphore; as long as semaphore has data, * there is another input blob to write to the TTY */ char tmp[1]; int c = read(input_buffer_semaphore[0],tmp,1); if (c > 0) { /* Retrieve blob */ spin_lock(&input_buffer_lock); node_t * blob = list_dequeue(input_buffer_queue); spin_unlock(&input_buffer_lock); /* No blobs? This shouldn't happen, but just in case, just continue */ if (!blob) { continue; } /* Write blob data to the tty */ struct input_data * value = blob->value; write(fd_master, value->data, value->len); free(blob->value); free(blob); } else { /* The pipe has closed, terminal is exiting */ break; } } return NULL; } static void write_input_buffer(char * data, size_t len) { struct input_data * d = malloc(sizeof(struct input_data) + len); d->len = len; memcpy(&d->data, data, len); spin_lock(&input_buffer_lock); list_insert(input_buffer_queue, d); spin_unlock(&input_buffer_lock); write(input_buffer_semaphore[1], d, 1); } void handle_input(char c) { write_input_buffer(&c, 1); } void handle_input_s(char * c) { size_t len = strlen(c); write_input_buffer(c, len); } unsigned short * textmemptr = NULL; unsigned short * basecopy = NULL; unsigned short * flipcopy = NULL; void placech(unsigned char c, int x, int y, int attr) { unsigned int where = y * term_width + x; unsigned int att = (c | (attr << 8)); basecopy[where] = att; } static void maybe_write_screen(void) { /* This says "maybe_" but we always draw whatever * needs drawing... */ for (int i = 0; i < term_width * term_height; ++i) { if (basecopy[i] != flipcopy[i]) { textmemptr[i] = flipcopy[i] = basecopy[i]; } } } /* ANSI-to-VGA */ char vga_to_ansi[] = { 0, 4, 2, 6, 1, 5, 3, 7, 8,12,10,14, 9,13,11,15 }; #include "ununicode.h" void term_write_char( uint32_t val, uint16_t x, uint16_t y, uint32_t fg, uint32_t bg, uint8_t flags ) { if (val == L'▏') val = 179; else if (val > 128) val = ununicode(val); if (fg > 256) { fg = best_match(fg); } if (bg > 256) { bg = best_match(bg); } if (fg > 16) { fg = vga_colors[fg]; } if (bg > 16) { bg = vga_colors[bg]; } if (fg == 16) fg = 0; if (bg == 16) bg = 0; placech(val, x, y, (vga_to_ansi[fg] & 0xF) | (vga_to_ansi[bg] << 4)); } static void cell_set(uint16_t x, uint16_t y, uint32_t c, uint32_t fg, uint32_t bg, uint8_t flags) { if (x >= term_width || y >= term_height) return; term_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t)); cell->c = c; cell->fg = fg; cell->bg = bg; cell->flags = flags; } static void cell_redraw(uint16_t x, uint16_t y) { if (x >= term_width || y >= term_height) return; term_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t)); if (((uint32_t *)cell)[0] == 0x00000000) { term_write_char(' ', x * char_width, y * char_height, TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS); } else { term_write_char(cell->c, x * char_width, y * char_height, cell->fg, cell->bg, cell->flags); } } static void cell_redraw_inverted(uint16_t x, uint16_t y) { if (x >= term_width || y >= term_height) return; term_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t)); if (((uint32_t *)cell)[0] == 0x00000000) { term_write_char(' ', x * char_width, y * char_height, TERM_DEFAULT_BG, TERM_DEFAULT_FG, TERM_DEFAULT_FLAGS | ANSI_SPECBG); } else { term_write_char(cell->c, x * char_width, y * char_height, cell->bg, cell->fg, cell->flags | ANSI_SPECBG); } } #if 0 static void cell_redraw_box(uint16_t x, uint16_t y) { if (x >= term_width || y >= term_height) return; term_cell_t * cell = (term_cell_t *)((uintptr_t)term_buffer + (y * term_width + x) * sizeof(term_cell_t)); if (((uint32_t *)cell)[0] == 0x00000000) { term_write_char(' ', x * char_width, y * char_height, TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS | ANSI_BORDER); } else { term_write_char(cell->c, x * char_width, y * char_height, cell->fg, cell->bg, cell->flags | ANSI_BORDER); } } #endif void render_cursor() { if (!cursor_on) return; cell_redraw_inverted(csr_x, csr_y); } static uint8_t cursor_flipped = 0; void draw_cursor() { if (!cursor_on) return; mouse_ticks = get_ticks(); cursor_flipped = 0; render_cursor(); } void term_redraw_all() { /* Redraw to a temp buffer */ for (uint16_t y = 0; y < term_height; ++y) { for (uint16_t x = 0; x < term_width; ++x) { cell_redraw(x,y); } } } void term_shift_region(int top, int height, int how_much) { if (how_much == 0) return; int destination, source; int count, new_top, new_bottom; if (how_much > height) { count = 0; new_top = top; new_bottom = top + height; } else if (how_much > 0) { destination = term_width * top; source = term_width * (top + how_much); count = height - how_much; new_top = top + height - how_much; new_bottom = top + height; } else if (how_much < 0) { destination = term_width * (top - how_much); source = term_width * top; count = height + how_much; new_top = top; new_bottom = top - how_much; } /* Move from top+how_much to top */ if (count) { memmove(term_buffer + destination, term_buffer + source, count * term_width * sizeof(term_cell_t)); } /* Clear new lines at bottom */ for (int i = new_top; i < new_bottom; ++i) { for (uint16_t x = 0; x < term_width; ++x) { cell_set(x, i, ' ', current_fg, current_bg, ansi_state->flags); } } term_redraw_all(); } void term_scroll(int how_much) { term_shift_region(0,term_height,how_much); } void insert_delete_lines(int how_many) { if (how_many == 0) return; if (how_many > 0) { /* Insert lines is equivalent to scrolling from the current line */ term_shift_region(csr_y,term_height-csr_y,-how_many); } else { term_shift_region(csr_y,term_height-csr_y,-how_many); } } int is_wide(uint32_t codepoint) { if (codepoint < 256) return 0; return wcwidth(codepoint) == 2; } static void undraw_cursor(void) { cell_redraw(csr_x, csr_y); } static void normalize_x(int setting_lcf) { if (csr_x >= term_width) { csr_x = term_width - 1; if (setting_lcf) { csr_h = 1; } } } static void normalize_y(void) { if (csr_y == term_height) { term_scroll(1); csr_y = term_height - 1; } } void term_write(char c) { static uint32_t codepoint = 0; static uint32_t unicode_state = 0; if (!decode(&unicode_state, &codepoint, (uint8_t)c)) { uint32_t o = codepoint; codepoint = 0; switch (c) { case '\a': /* boop */ return; case '\r': undraw_cursor(); csr_x = csr_h = 0; draw_cursor(); return; case '\t': undraw_cursor(); csr_x += (8 - csr_x % 8); normalize_x(0); draw_cursor(); return; case '\v': case '\f': case '\n': undraw_cursor(); csr_h = 0; ++csr_y; normalize_y(); draw_cursor(); return; case '\b': if (csr_x > 0) { undraw_cursor(); --csr_x; draw_cursor(); } csr_h = 0; return; default: { int wide = is_wide(o); uint8_t flags = ansi_state->flags; undraw_cursor(); if (csr_h || (wide && csr_x == term_width - 1)) { csr_x = csr_h = 0; ++csr_y; normalize_y(); } if (wide) { flags = flags | ANSI_WIDE; } cell_set(csr_x,csr_y, o, current_fg, current_bg, flags); cell_redraw(csr_x,csr_y); csr_x++; if (wide && csr_x != term_width) { cell_set(csr_x, csr_y, 0xFFFF, current_fg, current_bg, ansi_state->flags); cell_redraw(csr_x,csr_y); cell_redraw(csr_x-1,csr_y); csr_x++; } normalize_x(1); draw_cursor(); return; } } } else if (unicode_state == UTF8_REJECT) { unicode_state = 0; codepoint = 0; } } void term_set_csr(int x, int y) { cell_redraw(csr_x,csr_y); if (x < 0) x = 0; if (x >= term_width) x = term_width - 1; if (y < 0) y = 0; if (y >= term_height) y = term_height - 1; csr_x = x; csr_y = y; csr_h = 0; draw_cursor(); } int term_get_csr_x() { return csr_x; } int term_get_csr_y() { return csr_y; } void term_set_csr_show(int on) { cursor_on = on; if (on) { draw_cursor(); } } void term_set_colors(uint32_t fg, uint32_t bg) { current_fg = fg; current_bg = bg; } void term_redraw_cursor() { if (term_buffer) { draw_cursor(); } } void flip_cursor() { if (cursor_flipped) { cell_redraw(csr_x, csr_y); } else { render_cursor(); } cursor_flipped = 1 - cursor_flipped; } void term_set_cell(int x, int y, uint32_t c) { cell_set(x, y, c, current_fg, current_bg, ansi_state->flags); cell_redraw(x, y); } void term_redraw_cell(int x, int y) { if (x < 0 || y < 0 || x >= term_width || y >= term_height) return; cell_redraw(x,y); } void term_clear(int i) { if (i == 2) { /* Oh dear */ csr_x = 0; csr_y = 0; csr_h = 0; memset((void *)term_buffer, 0x00, term_width * term_height * sizeof(term_cell_t)); term_redraw_all(); } else if (i == 0) { for (int x = csr_x; x < term_width; ++x) { term_set_cell(x, csr_y, ' '); } for (int y = csr_y + 1; y < term_height; ++y) { for (int x = 0; x < term_width; ++x) { term_set_cell(x, y, ' '); } } } else if (i == 1) { for (int y = 0; y < csr_y; ++y) { for (int x = 0; x < term_width; ++x) { term_set_cell(x, y, ' '); } } for (int x = 0; x < csr_x; ++x) { term_set_cell(x, csr_y, ' '); } } } pid_t child_pid = 0; void key_event(int ret, key_event_t * event) { if (ret) { /* Special keys */ if ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) && (event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) && (event->keycode == 'c')) { if (selection) { /* Copy selection */ copy_selection(); } return; } if ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) && (event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) && (event->keycode == 'v')) { /* Paste selection */ if (selection_text) { if (ansi_state->paste_mode) { handle_input_s("\033[200~"); handle_input_s(selection_text); handle_input_s("\033[201~"); } else { handle_input_s(selection_text); } } return; } if (event->modifiers & KEY_MOD_LEFT_ALT || event->modifiers & KEY_MOD_RIGHT_ALT) { handle_input('\033'); } if ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) && event->key == '\t') { handle_input_s("\033[Z"); return; } /* ENTER = reads as linefeed, should be carriage return */ if (event->keycode == 10) { handle_input('\r'); return; } /* BACKSPACE = reads as ^H, should be ^? */ if (event->keycode == 8) { handle_input(0x7F); return; } handle_input(event->key); } else { if (event->action == KEY_ACTION_UP) return; switch (event->keycode) { case KEY_F1: handle_input_s("\033OP"); break; case KEY_F2: handle_input_s("\033OQ"); break; case KEY_F3: handle_input_s("\033OR"); break; case KEY_F4: handle_input_s("\033OS"); break; case KEY_F5: handle_input_s("\033[15~"); break; case KEY_F6: handle_input_s("\033[17~"); break; case KEY_F7: handle_input_s("\033[18~"); break; case KEY_F8: handle_input_s("\033[19~"); break; case KEY_F9: handle_input_s("\033[20~"); break; case KEY_F10: handle_input_s("\033[21~"); break; case KEY_F11: handle_input_s("\033[23~"); break; case KEY_F12: handle_input_s("\033[24~"); break; case KEY_ARROW_UP: if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[6A"); } else if (event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[5A"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[4A"); } else if (event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[3A"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT) { handle_input_s("\033[2A"); } else { handle_input_s("\033[A"); } break; case KEY_ARROW_DOWN: if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[6B"); } else if (event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[5B"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[4B"); } else if (event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[3B"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT) { handle_input_s("\033[2B"); } else { handle_input_s("\033[B"); } break; case KEY_ARROW_RIGHT: if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[6C"); } else if (event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[5C"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[4C"); } else if (event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[3C"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT) { handle_input_s("\033[2C"); } else { handle_input_s("\033[C"); } break; case KEY_ARROW_LEFT: if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[6D"); } else if (event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[5D"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[4D"); } else if (event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[3D"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT) { handle_input_s("\033[2D"); } else { handle_input_s("\033[D"); } break; case KEY_PAGE_UP: handle_input_s("\033[5~"); break; case KEY_PAGE_DOWN: handle_input_s("\033[6~"); break; case KEY_HOME: handle_input_s("\033[H"); break; case KEY_END: handle_input_s("\033[F"); break; case KEY_DEL: handle_input_s("\033[3~"); break; } } } void usage(char * argv[]) { printf( "VGA Terminal Emulator\n" "\n" "usage: %s [-b] [-F] [-h]\n" "\n" " -h --help \033[3mShow this help message.\033[0m\n" "\n", argv[0]); } int unsupported_int(void) { return 0; } void unsupported(int x, int y, char * data) { } #define SWAP(T,a,b) do { T _a = a; a = b; b = _a; } while(0); static void term_switch_buffer(int buffer) { if (buffer != 0 && buffer != 1) return; if (buffer != active_buffer) { active_buffer = buffer; term_buffer = active_buffer == 0 ? term_buffer_a : term_buffer_b; SWAP(int, csr_x, _orig_x); SWAP(int, csr_y, _orig_y); SWAP(uint32_t, current_fg, _orig_fg); SWAP(uint32_t, current_bg, _orig_bg); term_redraw_all(); } } static void full_reset(void) { /* Reset everything */ csr_x = 0; csr_y = 0; csr_h = 0; /* Huh, why don't we haven't an _orig_h - surely hold should be saved? */ _orig_x = 0; _orig_y = 0; current_fg = TERM_DEFAULT_FG; current_bg = TERM_DEFAULT_BG; _orig_fg = TERM_DEFAULT_FG; _orig_bg = TERM_DEFAULT_BG; active_buffer = 0; term_buffer = term_buffer_a; /* Clear both buffers to 0 */ memset((void *)term_buffer_a, 0x00, term_width * term_height * sizeof(term_cell_t)); memset((void *)term_buffer_b, 0x00, term_width * term_height * sizeof(term_cell_t)); /* Enable cursor */ cursor_on = 1; } term_callbacks_t term_callbacks = { term_write, term_set_colors, term_set_csr, term_get_csr_x, term_get_csr_y, term_set_cell, term_clear, term_scroll, term_redraw_cursor, handle_input_s, set_title, unsupported, unsupported_int, unsupported_int, term_set_csr_show, term_switch_buffer, insert_delete_lines, full_reset, }; void reinit(void) { if (term_buffer) { /* Do nothing */ } else { term_buffer_a = malloc(sizeof(term_cell_t) * term_width * term_height); memset(term_buffer_a, 0x0, sizeof(term_cell_t) * term_width * term_height); term_buffer_b = malloc(sizeof(term_cell_t) * term_width * term_height); memset(term_buffer_b, 0x0, sizeof(term_cell_t) * term_width * term_height); term_buffer = term_buffer_a; basecopy = malloc(sizeof(unsigned short) * term_width * term_height); memset(basecopy, 0, sizeof(unsigned short) * term_width * term_height); flipcopy = malloc(sizeof(unsigned short) * term_width * term_height); memset(flipcopy, 0, sizeof(unsigned short) * term_width * term_height); } ansi_state = ansi_init(ansi_state, term_width, term_height, &term_callbacks); term_redraw_all(); } void maybe_flip_cursor(void) { uint64_t ticks = get_ticks(); if (ticks > mouse_ticks + 600000LL) { mouse_ticks = ticks; flip_cursor(); } } void check_for_exit(void) { if (exit_application) return; pid_t pid = waitpid(-1, NULL, WNOHANG); if (pid != child_pid) return; /* Clean up */ exit_application = 1; /* Exit */ char exit_message[] = "[Process terminated]\n"; write(fd_slave, exit_message, sizeof(exit_message)); close(input_buffer_semaphore[1]); } static int mouse_x = 0; static int mouse_y = 0; static int last_mouse_buttons = 0; static int mouse_is_dragging = 0; #define MOUSE_X_R 820 #define MOUSE_Y_R 2621 static int old_x = 0; static int old_y = 0; static void mouse_event(int button, int x, int y) { if (ansi_state->mouse_on & TERMEMU_MOUSE_SGR) { char buf[100]; sprintf(buf,"\033[<%d;%d;%d%c", button == 3 ? 0 : button, x+1, y+1, button == 3 ? 'm' : 'M'); handle_input_s(buf); } else { char buf[7]; sprintf(buf, "\033[M%c%c%c", button + 32, x + 33, y + 33); handle_input_s(buf); } } static void redraw_mouse(void) { /* Redraw previous cursor position */ if (is_in_selection(old_x, old_y)) { cell_redraw_inverted(old_x, old_y); } else { cell_redraw(old_x, old_y); } term_cell_t * cell = term_buffer + (mouse_y * term_width + mouse_x); int current_background = cell->bg; if (is_in_selection(mouse_x, mouse_y)) { current_background = (((uint32_t *)cell)[0] == 0) ? TERM_DEFAULT_FG : cell->fg; } /* Get new cursor position character */ int cursor_color = (current_background == 12) ? 15 : 12; term_write_char(L'▲', mouse_x, mouse_y, cursor_color, current_background, 0); old_x = mouse_x; old_y = mouse_y; } static unsigned int button_state = 0; void handle_mouse_event(mouse_device_packet_t * packet) { if (mouse_x < 0) mouse_x = 0; if (mouse_y < 0) mouse_y = 0; if (mouse_x >= term_width) mouse_x = term_width - 1; if (mouse_y >= term_height) mouse_y = term_height - 1; static uint64_t last_click = 0; if (ansi_state->mouse_on & TERMEMU_MOUSE_ENABLE) { /* TODO: Handle shift */ if (packet->buttons & MOUSE_SCROLL_UP) { mouse_event(32+32, mouse_x, mouse_y); } else if (packet->buttons & MOUSE_SCROLL_DOWN) { mouse_event(32+32+1, mouse_x, mouse_y); } if (packet->buttons != button_state) { if (packet->buttons & LEFT_CLICK && !(button_state & LEFT_CLICK)) mouse_event(0, mouse_x, mouse_y); if (packet->buttons & MIDDLE_CLICK && !(button_state & MIDDLE_CLICK)) mouse_event(1, mouse_x, mouse_y); if (packet->buttons & RIGHT_CLICK && !(button_state & MIDDLE_CLICK)) mouse_event(2, mouse_x, mouse_y); if (!(packet->buttons & LEFT_CLICK) && (button_state & LEFT_CLICK)) mouse_event(3, mouse_x, mouse_y); if (!(packet->buttons & MIDDLE_CLICK) && (button_state & MIDDLE_CLICK)) mouse_event(3, mouse_x, mouse_y); if (!(packet->buttons & RIGHT_CLICK) && (button_state & MIDDLE_CLICK)) mouse_event(3, mouse_x, mouse_y); button_state = packet->buttons; } else if (ansi_state->mouse_on & TERMEMU_MOUSE_DRAG) { if (old_x != mouse_x || old_y != mouse_y) { if (button_state & LEFT_CLICK) mouse_event(32, mouse_x, mouse_y); if (button_state & MIDDLE_CLICK) mouse_event(33, mouse_x, mouse_y); if (button_state & RIGHT_CLICK) mouse_event(34, mouse_x, mouse_y); } } redraw_mouse(); return; } if (mouse_is_dragging) { if (packet->buttons & LEFT_CLICK) { mark_selection(); selection_end_x = mouse_x; selection_end_y = mouse_y; selection = 1; flip_selection(); } else { mouse_is_dragging = 0; } } else { if (packet->buttons & LEFT_CLICK) { term_redraw_all(); uint64_t now = get_ticks(); if (now - last_click < 500000UL && (mouse_x == selection_start_x && mouse_y == selection_start_y)) { /* Double click */ while (selection_start_x > 0) { term_cell_t * c = cell_at(selection_start_x-1, selection_start_y); if (!c || c->c == ' ' || !c->c) break; selection_start_x--; } while (selection_end_x < term_width - 1) { term_cell_t * c = cell_at(selection_end_x+1, selection_end_y); if (!c || c->c == ' ' || !c->c) break; selection_end_x++; } selection = 1; } else { last_click = get_ticks(); selection_start_x = mouse_x; selection_start_y = mouse_y; selection_end_x = mouse_x; selection_end_y = mouse_y; selection = 0; } redraw_selection(); mouse_is_dragging = 1; } else { redraw_mouse(); } } } static int rel_mouse_x = 0; static int rel_mouse_y = 0; void handle_mouse(mouse_device_packet_t * packet) { rel_mouse_x += packet->x_difference; rel_mouse_y -= packet->y_difference; mouse_x = rel_mouse_x / 20; mouse_y = rel_mouse_y / 40; handle_mouse_event(packet); } void handle_mouse_abs(mouse_device_packet_t * packet) { mouse_x = packet->x_difference / MOUSE_X_R; mouse_y = packet->y_difference / MOUSE_Y_R; rel_mouse_x = mouse_x * 20; rel_mouse_y = mouse_y * 40; handle_mouse_event(packet); } static int input_stopped = 0; void sig_suspend_input(int sig) { (void)sig; char exit_message[] = "[Input stopped]\n"; write(fd_slave, exit_message, sizeof(exit_message)); input_stopped = 1; signal(SIGUSR2, sig_suspend_input); } int main(int argc, char ** argv) { _login_shell = 0; static struct option long_opts[] = { {"login", no_argument, 0, 'l'}, {"help", no_argument, 0, 'h'}, {0,0,0,0} }; /* Read some arguments */ int index, c; while ((c = getopt_long(argc, argv, "hl", long_opts, &index)) != -1) { switch (c) { case 'l': _login_shell = 1; break; case 'h': usage(argv); return 0; break; case '?': break; default: break; } } int vga_text_fd = open("/dev/vga0", 0, 0); if (vga_text_fd < 0) return 1; ioctl(vga_text_fd, IO_VID_WIDTH, &term_width); ioctl(vga_text_fd, IO_VID_HEIGHT, &term_height); ioctl(vga_text_fd, IO_VID_ADDR, &textmemptr); putenv("TERM=toaru-vga"); openpty(&fd_master, &fd_slave, NULL, NULL, NULL); terminal = fdopen(fd_slave, "w"); struct winsize w; w.ws_row = term_height; w.ws_col = term_width; w.ws_xpixel = 0; w.ws_ypixel = 0; ioctl(fd_master, TIOCSWINSZ, &w); reinit(); pthread_t input_buffer_thread; pipe(input_buffer_semaphore); input_buffer_queue = list_create(); pthread_create(&input_buffer_thread, NULL, handle_input_writing, NULL); fflush(stdin); system("cursor-off"); signal(SIGUSR2, sig_suspend_input); int pid = getpid(); uint32_t f = fork(); if (getpid() != pid) { setsid(); dup2(fd_slave, 0); dup2(fd_slave, 1); dup2(fd_slave, 2); ioctl(STDIN_FILENO, TIOCSCTTY, &(int){1}); tcsetpgrp(STDIN_FILENO, getpid()); if (argv[optind] != NULL) { char * tokens[] = {argv[optind], NULL}; execvp(tokens[0], tokens); fprintf(stderr, "Failed to launch requested startup application.\n"); } else { if (_login_shell) { char * tokens[] = {"/bin/login-loop",NULL}; execvp(tokens[0], tokens); exit(1); } else { char * shell = getenv("SHELL"); if (!shell) shell = "/bin/sh"; /* fallback */ char * tokens[] = {shell,NULL}; execvp(tokens[0], tokens); exit(1); } } exit_application = 1; return 1; } else { child_pid = f; int kfd = open("/dev/kbd", O_RDONLY); key_event_t event; int vmmouse = 0; mouse_device_packet_t packet; int mfd = open("/dev/mouse", O_RDONLY); int amfd = open("/dev/absmouse", O_RDONLY); if (amfd == -1) { amfd = open("/dev/vmmouse", O_RDONLY); vmmouse = 1; } key_event_state_t kbd_state = {0}; /* Prune any keyboard input we got before the terminal started. */ struct stat s; fstat(kfd, &s); for (unsigned int i = 0; i < s.st_size; i++) { char tmp[1]; read(kfd, tmp, 1); } int fds[] = {fd_master, kfd, mfd, amfd}; #define BUF_SIZE 4096 unsigned char buf[4096]; while (!exit_application) { int res[] = {0,0,0,0}; fswait3(amfd == -1 ? 3 : 4,fds,200,res); check_for_exit(); if (input_stopped) continue; maybe_flip_cursor(); if (res[0]) { int r = read(fd_master, buf, BUF_SIZE); for (int i = 0; i < r; ++i) { ansi_put(ansi_state, buf[i]); } } if (res[1]) { int r = read(kfd, buf, 1); for (int i = 0; i < r; ++i) { if (kbd_scancode(&kbd_state, buf[i], &event)) { key_event(event.action == KEY_ACTION_DOWN && event.key, &event); } } } if (res[2]) { /* mouse event */ int r = read(mfd, (char *)&packet, sizeof(mouse_device_packet_t)); if (r > 0) { last_mouse_buttons = packet.buttons; handle_mouse(&packet); } } if (amfd != -1 && res[3]) { int r = read(amfd, (char *)&packet, sizeof(mouse_device_packet_t)); if (r > 0) { if (!vmmouse) { packet.buttons = last_mouse_buttons & 0xF; } else { last_mouse_buttons = packet.buttons; } handle_mouse_abs(&packet); } } maybe_write_screen(); } } close(input_buffer_semaphore[1]); return 0; } ================================================ FILE: apps/terminal.c ================================================ /** * @brief Virtual terminal emulator. * * Provides a graphical character cell terminal with support for * antialiased text, basic Unicode, bitmap fallbacks, nearly * complete ANSI escape sequence support, 256- and 24-bit color, * scrollback, selection, alternate screens, and various scroll * methods. * * @copyright * 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-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TRACE_APP_NAME "terminal" #include #include #include #include #include #include #include #include #include #include #include /* 16- and 256-color palette */ #include "terminal-palette.h" /* Bitmap font */ #include "terminal-font.h" /* Show help text */ static void usage(char * argv[]) { printf( "Terminal Emulator\n" "\n" "usage: %s [-Fbxn] [-s SCALE] [-g WIDTHxHEIGHT] [COMMAND...]\n" "\n" " -F --fullscreen \033[3mRun in fullscreen (background) mode.\033[0m\n" " -b --bitmap \033[3mUse the integrated bitmap font.\033[0m\n" " -s --scale \033[3mScale the font in antialiased mode by a given amount.\033[0m\n" " -h --help \033[3mShow this help message.\033[0m\n" " -x --grid \033[3mMake resizes round to nearest match for character cell size.\033[0m\n" " -n --no-frame \033[3mDisable decorations.\033[0m\n" " -g --geometry \033[3mSet requested terminal size WIDTHxHEIGHT\033[0m\n" " -B --blurred \033[3mBlur background behind terminal.\033[0m\n" " -S --scrollback \033[3mSet the scrollback buffer size, 0 for unlimited.\033[0m\n" "\n" " This terminal emulator provides basic support for VT220 escapes and\n" " XTerm extensions, including 256 color support and font effects.\n", argv[0]); } /* master and slave pty descriptors */ static int fd_master, fd_slave; static FILE * terminal; static pid_t child_pid = 0; static int scale_fonts = 0; /* Whether fonts should be scaled */ static float font_scaling = 1.0; /* How much they should be scaled by */ static uint16_t term_width = 0; /* Width of the terminal (in cells) */ static uint16_t term_height = 0; /* Height of the terminal (in cells) */ static uint16_t font_size = 16; /* Font size according to tt library */ static uint16_t char_width = 8; /* Width of a cell in pixels */ static uint16_t char_height = 17; /* Height of a cell in pixels */ static uint16_t char_offset = 0; /* Offset of the font within the cell */ static int csr_x = 0; /* Cursor X */ static int csr_y = 0; /* Cursor Y */ static int csr_h = 0; /* Cursor last column hold flag */ static uint32_t current_fg = 7; /* Current foreground color */ static uint32_t current_bg = 0; /* Current background color */ static term_cell_t * term_buffer = NULL; /* The active terminal cell buffer */ static term_cell_t * term_buffer_a = NULL; /* The main buffer */ static term_cell_t * term_buffer_b = NULL; /* The secondary buffer */ static term_cell_t * term_mirror = NULL; /* What we want to draw */ static term_cell_t * term_display = NULL; /* What we think we've drawn already */ static term_state_t * ansi_state = NULL; /* ANSI parser library state */ static int active_buffer = 0; static int _orig_x = 0; static int _orig_y = 0; static uint32_t _orig_fg = 7; static uint32_t _orig_bg = 0; static bool cursor_on = 1; /* Whether or not the cursor should be rendered */ static bool _fullscreen = 0; /* Whether or not we are running in fullscreen mode (GUI only) */ static bool _no_frame = 0; /* Whether to disable decorations or not */ static bool _use_aa = 1; /* Whether or not to use best-available anti-aliased renderer */ static bool _free_size = 1; /* Disable rounding when resized */ static struct TT_Font * _tt_font_normal = NULL; static struct TT_Font * _tt_font_bold = NULL; static struct TT_Font * _tt_font_oblique = NULL; static struct TT_Font * _tt_font_bold_oblique = NULL; static struct TT_Font * _tt_font_fallback = NULL; static struct TT_Font * _tt_font_japanese = NULL; static list_t * images_list = NULL; static int menu_bar_height = 24; /* Text selection information */ static int selection = 0; static int selection_start_x = 0; static int selection_start_y = 0; static int selection_end_x = 0; static int selection_end_y = 0; static char * selection_text = NULL; static int _selection_count = 0; static int _selection_i = 0; /* Mouse state */ static int last_mouse_x = -1; static int last_mouse_y = -1; static int button_state = 0; static unsigned long long mouse_ticks = 0; static yutani_window_t * window = NULL; /* GUI window */ static yutani_t * yctx = NULL; /* Window flip bounds */ static int32_t l_x = INT32_MAX; static int32_t l_y = INT32_MAX; static int32_t r_x = -1; static int32_t r_y = -1; static uint32_t window_width = 640; static uint32_t window_height = 480; static bool window_position_set = 0; static int32_t window_left = 0; static int32_t window_top = 0; #define TERMINAL_TITLE_SIZE 512 static char terminal_title[TERMINAL_TITLE_SIZE]; static size_t terminal_title_length = 0; static gfx_context_t * ctx; static struct MenuList * menu_right_click = NULL; static void render_decors(void); static void term_clear(int i); static void reinit(void); static void term_redraw_cursor(); static int decor_left_width = 0; static int decor_top_height = 0; static int decor_right_width = 0; static int decor_bottom_height = 0; static int decor_width = 0; static int decor_height = 0; struct scrollback_row { unsigned short width; term_cell_t cells[]; }; static size_t max_scrollback = 10000; static list_t * scrollback_list = NULL; static int scrollback_offset = 0; /* Menu bar entries */ struct menu_bar terminal_menu_bar = {0}; struct menu_bar_entries terminal_menu_entries[] = { {"File", "file"}, {"Edit", "edit"}, {"View", "view"}, {"Help", "help"}, {NULL, NULL}, }; /* We need to track these so we can update their states*/ static struct MenuEntry * _menu_toggle_borders_context = NULL; static struct MenuEntry * _menu_toggle_borders_bar = NULL; static struct MenuEntry * _menu_exit = NULL; static struct MenuEntry * _menu_copy = NULL; static struct MenuEntry * _menu_paste = NULL; static struct MenuEntry * _menu_scale_075 = NULL; static struct MenuEntry * _menu_scale_100 = NULL; static struct MenuEntry * _menu_scale_150 = NULL; static struct MenuEntry * _menu_scale_200 = NULL; static struct MenuEntry * _menu_set_zoom = NULL; /* Trigger to exit the terminal when the child process dies or * we otherwise receive an exit signal */ static volatile int exit_application = 0; static void cell_redraw(uint16_t x, uint16_t y); static void cell_redraw_inverted(uint16_t x, uint16_t y); static void cell_redraw_offset(uint16_t x, uint16_t y); static void cell_redraw_offset_inverted(uint16_t x, uint16_t y); static void update_bounds(void); static void update_scale_menu(void); static uint64_t get_ticks(void) { struct timeval now; gettimeofday(&now, NULL); return (uint64_t)now.tv_sec * 1000000LL + (uint64_t)now.tv_usec; } static void display_flip(void) { if (l_x != INT32_MAX && l_y != INT32_MAX) { flip(ctx); yutani_flip_region(yctx, window, l_x, l_y, r_x - l_x, r_y - l_y); l_x = INT32_MAX; l_y = INT32_MAX; r_x = -1; r_y = -1; } } /* Returns the lower of two shorts */ static int32_t min(int32_t a, int32_t b) { return (a < b) ? a : b; } /* Returns the higher of two shorts */ static int32_t max(int32_t a, int32_t b) { return (a > b) ? a : b; } /* * Convert codepoint to UTF-8 * * Returns length of byte sequence written. */ static int to_eight(uint32_t codepoint, char * out) { memset(out, 0x00, 7); if (codepoint < 0x0080) { out[0] = (char)codepoint; } else if (codepoint < 0x0800) { out[0] = 0xC0 | (codepoint >> 6); out[1] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x10000) { out[0] = 0xE0 | (codepoint >> 12); out[1] = 0x80 | ((codepoint >> 6) & 0x3F); out[2] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x200000) { out[0] = 0xF0 | (codepoint >> 18); out[1] = 0x80 | ((codepoint >> 12) & 0x3F); out[2] = 0x80 | ((codepoint >> 6) & 0x3F); out[3] = 0x80 | ((codepoint) & 0x3F); } else if (codepoint < 0x4000000) { out[0] = 0xF8 | (codepoint >> 24); out[1] = 0x80 | (codepoint >> 18); out[2] = 0x80 | ((codepoint >> 12) & 0x3F); out[3] = 0x80 | ((codepoint >> 6) & 0x3F); out[4] = 0x80 | ((codepoint) & 0x3F); } else { out[0] = 0xF8 | (codepoint >> 30); out[1] = 0x80 | ((codepoint >> 24) & 0x3F); out[2] = 0x80 | ((codepoint >> 18) & 0x3F); out[3] = 0x80 | ((codepoint >> 12) & 0x3F); out[4] = 0x80 | ((codepoint >> 6) & 0x3F); out[5] = 0x80 | ((codepoint) & 0x3F); } return strlen(out); } /* Set the terminal title string */ static void set_title(char * c) { int len = min(TERMINAL_TITLE_SIZE, strlen(c)+1); memcpy(terminal_title, c, len); terminal_title[len-1] = '\0'; terminal_title_length = len - 1; render_decors(); } /* Call a function for each selected cell */ static void iterate_selection(void (*func)(uint16_t x, uint16_t y)) { if (!selection) return; if (selection_end_y < selection_start_y) { for (int x = selection_end_x; x < term_width; ++x) { func(x, selection_end_y); } for (int y = selection_end_y + 1; y < selection_start_y; ++y) { for (int x = 0; x < term_width; ++x) { func(x, y); } } for (int x = 0; x <= selection_start_x; ++x) { func(x, selection_start_y); } } else if (selection_start_y == selection_end_y) { if (selection_start_x > selection_end_x) { for (int x = selection_end_x; x <= selection_start_x; ++x) { func(x, selection_start_y); } } else { for (int x = selection_start_x; x <= selection_end_x; ++x) { func(x, selection_start_y); } } } else { for (int x = selection_start_x; x < term_width; ++x) { func(x, selection_start_y); } for (int y = selection_start_y + 1; y < selection_end_y; ++y) { for (int x = 0; x < term_width; ++x) { func(x, y); } } for (int x = 0; x <= selection_end_x; ++x) { func(x, selection_end_y); } } } /* Redraw the selection with the selection hint (inversion) */ static void redraw_selection(void) { iterate_selection(cell_redraw_offset_inverted); } static term_cell_t * cell_at(uint16_t x, uint16_t _y) { int y = _y; y -= scrollback_offset; if (y >= 0) { return &term_buffer[y * term_width + x]; } else { node_t * node = scrollback_list->tail; for (; y < -1; y++) { if (!node) break; node = node->prev; } if (node) { struct scrollback_row * row = (struct scrollback_row *)node->value; if (row && x < row->width) { return &row->cells[x]; } } } return NULL; } static void mark_cell(uint16_t x, uint16_t y) { term_cell_t * c = cell_at(x,y); if (c) { c->flags |= 0x200; } } static void mark_selection(void) { iterate_selection(mark_cell); } static void red_cell(uint16_t x, uint16_t y) { term_cell_t * c = cell_at(x,y); if (c) { if (c->flags & 0x200) { c->flags &= ~(0x200); } else { c->flags |= 0x400; } } } static void flip_selection(void) { iterate_selection(red_cell); for (int y = 0; y < term_height; ++y) { for (int x = 0; x < term_width; ++x) { term_cell_t * c = cell_at(x,y); if (c) { if (c->flags & 0x200) cell_redraw_offset(x,y); if (c->flags & 0x400) cell_redraw_offset_inverted(x,y); c->flags &= ~(0x600); } } } } /* Figure out how long the UTF-8 selection string should be. */ static void count_selection(uint16_t x, uint16_t _y) { int y = _y; y -= scrollback_offset; if (y >= 0) { term_cell_t * cell = &term_buffer[y * term_width + x]; if (!(cell->flags & ANSI_EXT_IMG)) { if (((uint32_t *)cell)[0] != 0x00000000) { char tmp[7]; _selection_count += to_eight(cell->c, tmp); } } } else { node_t * node = scrollback_list->tail; for (; y < -1; y++) { if (!node) break; node = node->prev; } if (node) { struct scrollback_row * row = (struct scrollback_row *)node->value; if (row && x < row->width) { term_cell_t * cell = &row->cells[x]; if (cell && ((uint32_t *)cell)[0] != 0x00000000) { char tmp[7]; _selection_count += to_eight(cell->c, tmp); } } } } if (x == term_width - 1) { _selection_count++; } } /* Fill the selection text buffer with the selected text. */ void write_selection(uint16_t x, uint16_t _y) { int y = _y; y -= scrollback_offset; if (y >= 0) { term_cell_t * cell = &term_buffer[y * term_width + x]; if (!(cell->flags & ANSI_EXT_IMG)) { if (((uint32_t *)cell)[0] != 0x00000000 && cell->c != 0xFFFF) { char tmp[7]; int count = to_eight(cell->c, tmp); for (int i = 0; i < count; ++i) { selection_text[_selection_i] = tmp[i]; _selection_i++; } } } } else { node_t * node = scrollback_list->tail; for (; y < -1; y++) { if (!node) break; node = node->prev; } if (node) { struct scrollback_row * row = (struct scrollback_row *)node->value; if (row && x < row->width) { term_cell_t * cell = &row->cells[x]; if (cell && ((uint32_t *)cell)[0] != 0x00000000 && cell->c != 0xFFFF) { char tmp[7]; int count = to_eight(cell->c, tmp); for (int i = 0; i < count; ++i) { selection_text[_selection_i] = tmp[i]; _selection_i++; } } } } } if (x == term_width - 1) { selection_text[_selection_i] = '\n';; _selection_i++; } } /* Copy the selection text to the clipboard. */ static char * copy_selection(void) { _selection_count = 0; iterate_selection(count_selection); if (selection_text) { free(selection_text); } if (_selection_count == 0) { return NULL; } selection_text = malloc(_selection_count + 1); selection_text[_selection_count] = '\0'; _selection_i = 0; iterate_selection(write_selection); if (selection_text[_selection_count-1] == '\n') { /* Don't end on a line feed */ selection_text[_selection_count-1] = '\0'; } yutani_set_clipboard(yctx, selection_text); return selection_text; } static volatile int input_buffer_lock = 0; static int input_buffer_semaphore[2]; static list_t * input_buffer_queue = NULL; struct input_data { size_t len; char data[]; }; void * handle_input_writing(void * unused) { (void)unused; while (1) { /* Read one byte from semaphore; as long as semaphore has data, * there is another input blob to write to the TTY */ char tmp[1]; int c = read(input_buffer_semaphore[0],tmp,1); if (c > 0) { /* Retrieve blob */ spin_lock(&input_buffer_lock); node_t * blob = list_dequeue(input_buffer_queue); spin_unlock(&input_buffer_lock); /* No blobs? This shouldn't happen, but just in case, just continue */ if (!blob) { continue; } /* Write blob data to the tty */ struct input_data * value = blob->value; write(fd_master, value->data, value->len); free(blob->value); free(blob); } else { /* The pipe has closed, terminal is exiting */ break; } } return NULL; } static void write_input_buffer(char * data, size_t len) { struct input_data * d = malloc(sizeof(struct input_data) + len); d->len = len; memcpy(&d->data, data, len); spin_lock(&input_buffer_lock); list_insert(input_buffer_queue, d); spin_unlock(&input_buffer_lock); write(input_buffer_semaphore[1], d, 1); } /* Stuffs a string into the stdin of the terminal's child process * Useful for things like the ANSI DSR command. */ static void input_buffer_stuff(char * str) { size_t len = strlen(str); write_input_buffer(str, len); } /* Redraw the decorations */ static void render_decors(void) { /* Don't draw decorations or bother advertising the window if in "fullscreen mode" */ if (_fullscreen) return; if (!_no_frame) { /* Draw the decorations */ render_decorations(window, ctx, terminal_title_length ? terminal_title : "Terminal"); /* Update menu bar position and size */ terminal_menu_bar.x = decor_left_width; terminal_menu_bar.y = decor_top_height; terminal_menu_bar.width = window_width; terminal_menu_bar.window = window; /* Redraw the menu bar */ menu_bar_render(&terminal_menu_bar, ctx); } /* Advertise the window icon to the panel. */ yutani_window_advertise_icon(yctx, window, terminal_title_length ? terminal_title : "Terminal", "utilities-terminal"); /* * Flip the whole window * We do this regardless of whether we drew decorations to catch * a case where decorations are toggled. */ l_x = 0; l_y = 0; r_x = window->width; r_y = window->height; display_flip(); } /* Set a pixel in the terminal cell area */ static inline void term_set_point(uint16_t x, uint16_t y, uint32_t color ) { GFX(ctx, (x+decor_left_width),(y+decor_top_height+menu_bar_height)) = color; } static void _fill_region(uint32_t _bg, uint16_t x, uint16_t y, uint16_t width, uint16_t height) { for (uint8_t i = 0; i < height; ++i) { for (uint8_t j = 0; j < width; ++j) { term_set_point(x+j,y+i,_bg); } } } /* Draw a partial block character. */ static void draw_semi_block(int c, int x, int y, uint32_t fg, uint32_t bg) { bg = premultiply(bg); fg = alpha_blend_rgba(bg, premultiply(fg)); _fill_region(bg, x, y, char_width, char_height); if (c == 0x2580) { for (uint8_t i = 0; i < char_height / 2; ++i) { for (uint8_t j = 0; j < char_width; ++j) { term_set_point(x+j,y+i,fg); } } } else if (c >= 0x2589) { c -= 0x2588; int width = char_width - ((c * char_width) / 8); for (uint8_t i = 0; i < char_height; ++i) { for (uint8_t j = 0; j < width; ++j) { term_set_point(x+j, y+i, fg); } } } else { c -= 0x2580; int height = char_height - ((c * char_height) / 8); for (uint8_t i = height; i < char_height; ++i) { for (uint8_t j = 0; j < char_width; ++j) { term_set_point(x+j, y+i,fg); } } } } static void draw_box_drawing(int c, int x, int y, uint32_t fg, uint32_t bg) { bg = premultiply(bg); fg = alpha_blend_rgba(bg, premultiply(fg)); _fill_region(bg, x, y, char_width, char_height); int lineheight = char_height / 16; int linewidth = char_width / 8; lineheight = lineheight < 1 ? 1 : lineheight; linewidth = linewidth < 1 ? 1 : linewidth; int mid_x = char_width / 2 - linewidth / 2; int mid_y = char_height / 2 - lineheight / 2; int extra_x = (mid_x * 2 < char_width) ? char_width - mid_x * 2 : 0; int extra_y = (mid_y * 2 < char_height) ? char_height - mid_y * 2 : 0; #define UP _fill_region(fg, x + mid_x, y, linewidth, mid_y + lineheight) #define DOWN _fill_region(fg, x + mid_x, y + mid_y, linewidth, mid_y + extra_y) #define LEFT _fill_region(fg, x, y + mid_y, mid_x + linewidth, lineheight) #define RIGHT _fill_region(fg, x + mid_x, y + mid_y, mid_x + extra_x, lineheight) #define VERT _fill_region(fg, x + mid_x, y, linewidth, char_height) #define HORI _fill_region(fg, x, y + mid_y, char_width, lineheight) switch (c) { case 0x2500: HORI; break; case 0x2502: VERT; break; case 0x250c: RIGHT; DOWN; break; case 0x2510: LEFT; DOWN; break; case 0x2514: UP; RIGHT; break; case 0x2518: UP; LEFT; break; case 0x251c: VERT; RIGHT; break; case 0x2524: VERT; LEFT; break; case 0x252c: HORI; DOWN; break; case 0x2534: UP; HORI; break; case 0x253c: HORI; VERT; break; case 0x2574: LEFT; break; case 0x2575: UP; break; case 0x2576: RIGHT; break; case 0x2577: DOWN; break; } } #include "apps/ununicode.h" struct GlyphCacheEntry { struct TT_Font * font; sprite_t * sprite; uint32_t size; uint32_t glyph; uint32_t color; }; static struct GlyphCacheEntry glyph_cache[1024]; static unsigned long _hits = 0; static unsigned long _misses = 0; static unsigned long _wrongcolor = 0; static void _menu_action_cache_stats(struct MenuEntry * self) { char msg[400]; unsigned long count = 0; unsigned long size = 0; for (int i = 0; i < 1024; ++i) { if (glyph_cache[i].sprite) { count++; size += glyph_cache[i].sprite->width * glyph_cache[i].sprite->height * 4; } } snprintf(msg, 400, "Hits: %lu\n" "Misses: %lu\n" "Wrong color: %lu\n" "Populated cache entries: %lu\n" "Size of sprites: %lu\n", _hits, _misses, _wrongcolor, count, size); write(fd_slave, msg, strlen(msg)); } static void _menu_action_clear_cache(struct MenuEntry * self) { for (int i = 0; i < 1024; ++i) { if (glyph_cache[i].sprite) { sprite_free(glyph_cache[i].sprite); } } memset(glyph_cache,0,sizeof(glyph_cache)); } static 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) { unsigned int hash = (((uintptr_t)_font >> 8) ^ (glyph * size)) & 1023; struct GlyphCacheEntry * entry = &glyph_cache[hash]; if (entry->font != _font || entry->size != size || entry->glyph != glyph) { if (entry->sprite) sprite_free(entry->sprite); int wide = (flags & ANSI_WIDE) ? 2 : 1; tt_set_size(_font, size); entry->font = _font; entry->size = size; entry->glyph = glyph; entry->sprite = create_sprite(char_width * wide, char_height, ALPHA_EMBEDDED); entry->color = _ALP(fg) == 255 ? fg : 0xFFFFFFFF; gfx_context_t * _ctx = init_graphics_sprite(entry->sprite); draw_fill(_ctx, 0); tt_draw_glyph(_ctx, entry->font, 0, char_offset, glyph, entry->color); free(_ctx); _misses++; } else { _hits++; } if (entry->color != fg) { _wrongcolor++; draw_sprite_alpha_paint(ctx, entry->sprite, x, y, 1.0, fg); } else { draw_sprite(ctx, entry->sprite, x, y); } } /* Write a character to the window. */ static void term_write_char(uint32_t val, uint16_t x, uint16_t y, uint32_t fg, uint32_t bg, uint8_t flags) { uint32_t _fg, _bg; /* Select foreground color from palette. */ if (fg < PALETTE_COLORS) { _fg = term_colors[fg]; _fg |= 0xFF << 24; } else { _fg = fg; } /* Select background color from aplette. */ if (bg < PALETTE_COLORS) { _bg = term_colors[bg]; if (flags & ANSI_SPECBG) { _bg |= 0xFF << 24; } else { _bg |= TERM_DEFAULT_OPAC << 24; } } else { _bg = bg; } if (_fullscreen) { _bg |= 0xFF << 24; } switch (val) { /* Line drawing */ case 0x2500: case 0x2502: case 0x250c: case 0x2510: case 0x2514: case 0x2518: case 0x251c: case 0x2524: case 0x252c: case 0x2534: case 0x253c: case 0x2574: case 0x2575: case 0x2576: case 0x2577: draw_box_drawing(val, x, y, _fg, _bg); goto _extra_stuff; /* Semi-filled blocks */ case 0x2580 ... 0x258f: { draw_semi_block(val, x, y, _fg, _bg); goto _extra_stuff; } /* Instead of checker, does 50% opacity fill */ case 0x2591: case 0x2592: case 0x2593: _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); goto _extra_stuff; default: break; } /* Draw glyphs */ if (_use_aa) { if (val == 0xFFFF) return; for (uint8_t i = 0; i < char_height; ++i) { for (uint8_t j = 0; j < char_width; ++j) { term_set_point(x+j,y+i,_bg); } } if (flags & ANSI_WIDE) { for (uint8_t i = 0; i < char_height; ++i) { for (uint8_t j = char_width; j < 2 * char_width; ++j) { term_set_point(x+j,y+i,_bg); } } } if (val < 32 || val == ' ') { goto _extra_stuff; } struct TT_Font * _font = _tt_font_normal; if (flags & ANSI_BOLD && flags & ANSI_ITALIC) { _font = _tt_font_bold_oblique; } else if (flags & ANSI_BOLD) { _font = _tt_font_bold; } else if (flags & ANSI_ITALIC) { _font = _tt_font_oblique; } unsigned int glyph = tt_glyph_for_codepoint(_font, val); /* Try the regular sans serif font as a fallback */ if (!glyph) { int nglyph = tt_glyph_for_codepoint(_tt_font_fallback, val); if (nglyph) { _font = _tt_font_fallback; glyph = nglyph; } } /* Try the VL Gothic, if it's installed and this is a reasonably high codepoint */ if (!glyph && _tt_font_japanese && val >= 0x2E80) { int nglyph = tt_glyph_for_codepoint(_tt_font_japanese, val); if (nglyph) { _font = _tt_font_japanese; glyph = nglyph; } } int _x = x + decor_left_width; int _y = y + decor_top_height + menu_bar_height; draw_cached_glyph(ctx, _font, font_size, _x,_y, glyph, _fg, flags); } else { /* Convert other unicode characters. */ if (val > 128) { val = ununicode(val); } /* Draw using the bitmap font. */ uint8_t * c = large_font[val]; for (uint8_t i = 0; i < char_height; ++i) { for (uint8_t j = 0; j < char_width; ++j) { if (c[i] & (1 << (LARGE_FONT_MASK-j))) { term_set_point(x+j,y+i,_fg); } else { term_set_point(x+j,y+i,_bg); } } } } /* Draw additional text elements, like underlines and cross-outs. */ _extra_stuff: if (flags & ANSI_UNDERLINE) { for (uint8_t i = 0; i < char_width; ++i) { term_set_point(x + i, y + char_height - 1, _fg); } } if (flags & ANSI_CROSS) { for (uint8_t i = 0; i < char_width; ++i) { term_set_point(x + i, y + char_height - 7, _fg); } } if (flags & ANSI_BORDER) { for (uint8_t i = 0; i < char_height; ++i) { term_set_point(x , y + i, _fg); term_set_point(x + (char_width - 1), y + i, _fg); } for (uint8_t j = 0; j < char_width; ++j) { term_set_point(x + j, y, _fg); term_set_point(x + j, y + (char_height - 1), _fg); } } /* Calculate the bounds of the updated region of the window */ l_x = min(l_x, decor_left_width + x); l_y = min(l_y, decor_top_height+menu_bar_height + y); if (flags & ANSI_WIDE) { r_x = max(r_x, decor_left_width + x + char_width * 2); r_y = max(r_y, decor_top_height+menu_bar_height + y + char_height * 2); } else { r_x = max(r_x, decor_left_width + x + char_width); r_y = max(r_y, decor_top_height+menu_bar_height + y + char_height); } } static void term_mirror_set(uint16_t x, uint16_t y, uint32_t val, uint32_t fg, uint32_t bg, uint8_t flags) { if (x >= term_width || y >= term_height) return; term_cell_t * cell = &term_mirror[y * term_width + x]; cell->c = val; cell->fg = fg; cell->bg = bg; cell->flags = flags; } static void term_mirror_copy(uint16_t x, uint16_t y, term_cell_t * from) { if (x >= term_width || y >= term_height) return; term_cell_t * cell = &term_mirror[y * term_width + x]; if (!from->c && !from->fg && !from->bg) { cell->c = ' '; cell->fg = TERM_DEFAULT_FG; cell->bg = TERM_DEFAULT_BG; cell->flags = from->flags; } else { *cell = *from; } } static void term_mirror_copy_inverted(uint16_t x, uint16_t y, term_cell_t * from) { if (x >= term_width || y >= term_height) return; term_cell_t * cell = &term_mirror[y * term_width + x]; if (!from->c && !from->fg && !from->bg) { cell->c = ' '; cell->fg = TERM_DEFAULT_BG; cell->bg = TERM_DEFAULT_FG; cell->flags = from->flags; } else if (from->flags & ANSI_EXT_IMG) { cell->c = ' '; cell->fg = from->fg; cell->bg = from->bg; cell->flags = from->flags | ANSI_SPECBG; } else { cell->c = from->c; cell->fg = from->bg; cell->bg = from->fg; cell->flags = from->flags | ANSI_SPECBG; } } /* Set a terminal cell */ static void cell_set(uint16_t x, uint16_t y, uint32_t c, uint32_t fg, uint32_t bg, uint32_t flags) { /* Avoid setting cells out of range. */ if (x >= term_width || y >= term_height) return; /* Calculate the cell position in the terminal buffer */ term_cell_t * cell = &term_buffer[y * term_width + x]; /* Set cell attributes */ cell->c = c; cell->fg = fg; cell->bg = bg; cell->flags = flags; } /* Redraw an embedded image cell */ static void redraw_cell_image(uint16_t x, uint16_t y, term_cell_t * cell, int inverted) { /* Avoid setting cells out of range. */ if (x >= term_width || y >= term_height) return; /* Draw the image data */ uint32_t * data = (uint32_t *)((uintptr_t)cell->bg << 32 | cell->fg); if (inverted) { for (uint32_t yy = 0; yy < char_height; ++yy) { for (uint32_t xx = 0; xx < char_width; ++xx) { uint32_t alpha = 0xFF000000 & *data; uint32_t color = 0xFFFFFF - (*data & 0xFFFFFF); term_set_point(x * char_width + xx, y * char_height + yy, color | alpha); data++; } } } else { for (uint32_t yy = 0; yy < char_height; ++yy) { for (uint32_t xx = 0; xx < char_width; ++xx) { term_set_point(x * char_width + xx, y * char_height + yy, *data); data++; } } } /* Update bounds */ l_x = min(l_x, decor_left_width + x * char_width); l_y = min(l_y, decor_top_height+menu_bar_height + y * char_height); r_x = max(r_x, decor_left_width + x * char_width + char_width); r_y = max(r_y, decor_top_height+menu_bar_height + y * char_height + char_height); } static void maybe_flip_display(int force) { static uint64_t last_refresh; uint64_t ticks = get_ticks(); if (!force) { if (ticks < last_refresh + 33330L) { return; } } last_refresh = ticks; for (unsigned int y = 0; y < term_height; ++y) { for (unsigned int x = 0; x < term_width; ++x) { term_cell_t * cell_m = &term_mirror[y * term_width + x]; term_cell_t * cell_d = &term_display[y * term_width + x]; if (memcmp(cell_m, cell_d, sizeof(term_cell_t))) { *cell_d = *cell_m; if (cell_m->flags & ANSI_EXT_IMG) { redraw_cell_image(x,y,cell_m,cell_m->flags & ANSI_SPECBG); } else { term_write_char(cell_m->c, x * char_width, y * char_height, cell_m->fg, cell_m->bg, cell_m->flags); } } } } display_flip(); } static void cell_redraw_offset(uint16_t x, uint16_t _y) { int y = _y; int i = y; y -= scrollback_offset; if (y >= 0) { term_mirror_copy(x,i,&term_buffer[y * term_width + x]); } else { node_t * node = scrollback_list->tail; for (; y < -1; y++) { if (!node) break; node = node->prev; } if (node) { struct scrollback_row * row = (struct scrollback_row *)node->value; if (row && x < row->width) { term_mirror_copy(x,i,&row->cells[x]); } else { term_mirror_set(x,i,' ',TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS); } } } } static void cell_redraw_offset_inverted(uint16_t x, uint16_t _y) { int y = _y; int i = y; y -= scrollback_offset; if (y >= 0) { term_mirror_copy_inverted(x,i,&term_buffer[y * term_width + x]); } else { node_t * node = scrollback_list->tail; for (; y < -1; y++) { if (!node) break; node = node->prev; } if (node) { struct scrollback_row * row = (struct scrollback_row *)node->value; if (row && x < row->width) { term_mirror_copy_inverted(x,i,&row->cells[x]); } else { term_mirror_set(x, i, ' ', TERM_DEFAULT_BG, TERM_DEFAULT_FG, TERM_DEFAULT_FLAGS|ANSI_SPECBG); } } } } /* Redraw a text cell normally. */ static void cell_redraw(uint16_t x, uint16_t y) { if (x >= term_width || y >= term_height) return; term_mirror_copy(x,y,&term_buffer[y * term_width + x]); } /* Redraw text cell inverted. */ static void cell_redraw_inverted(uint16_t x, uint16_t y) { /* Avoid cells out of range. */ if (x >= term_width || y >= term_height) return; term_mirror_copy_inverted(x,y,&term_buffer[y * term_width + x]); } /* Redraw text cell with a surrounding box (used by cursor) */ static void cell_redraw_box(uint16_t x, uint16_t y) { if (x >= term_width || y >= term_height) return; term_cell_t cell = term_buffer[y * term_width + x]; cell.flags |= ANSI_BORDER; term_mirror_copy(x,y,&cell); } /* Draw the cursor cell */ static void render_cursor() { if (!cursor_on) return; if (!window->focused) { /* An unfocused terminal should draw an unfilled box. */ cell_redraw_box(csr_x, csr_y); } else { /* A focused terminal draws a solid box. */ cell_redraw_inverted(csr_x, csr_y); } } static uint8_t cursor_flipped = 0; /* A soft request to draw the cursor. */ static void draw_cursor() { if (!cursor_on) return; cursor_flipped = 0; render_cursor(); } /* Timer callback to flip (flash) the cursor */ static void maybe_flip_cursor(void) { uint64_t ticks = get_ticks(); if (ticks > mouse_ticks + 600000LL) { mouse_ticks = ticks; if (scrollback_offset != 0) { return; /* Don't flip cursor while drawing scrollback */ } if (window->focused && cursor_flipped) { cell_redraw(csr_x, csr_y); } else { render_cursor(); } cursor_flipped = 1 - cursor_flipped; } } /* Draw all cells. Duplicates code from cell_redraw to avoid unecessary bounds checks. */ static void term_redraw_all() { for (int i = 0; i < term_height; i++) { for (int x = 0; x < term_width; ++x) { term_mirror_copy(x,i,&term_buffer[i * term_width + x]); } } } static void _menu_action_redraw(struct MenuEntry * self) { term_redraw_all(); } /* Remove no-longer-visible image cell data. */ static void flush_unused_images(void) { if (!images_list->length) return; list_t * tmp = list_create(); /* Go through scrollback, too */ if (scrollback_list) { foreach(node, scrollback_list) { struct scrollback_row * row = (struct scrollback_row *)node->value; for (unsigned int x = 0; x < row->width; ++x) { term_cell_t * cell = &row->cells[x]; if (cell->flags & ANSI_EXT_IMG) { uint32_t * data = (uint32_t *)((uintptr_t)cell->bg << 32 | cell->fg); list_insert(tmp, data); } } } } for (int y = 0; y < term_height; ++y) { for (int x = 0; x < term_width; ++x) { term_cell_t * cell = &term_buffer_a[y * term_width + x]; if (cell->flags & ANSI_EXT_IMG) { uint32_t * data = (uint32_t *)((uintptr_t)cell->bg << 32 | cell->fg); list_insert(tmp, data); } } } for (int y = 0; y < term_height; ++y) { for (int x = 0; x < term_width; ++x) { term_cell_t * cell = &term_buffer_b[y * term_width + x]; if (cell->flags & ANSI_EXT_IMG) { uint32_t * data = (uint32_t *)((uintptr_t)cell->bg << 32 | cell->fg); list_insert(tmp, data); } } } foreach(node, images_list) { if (!list_find(tmp, node->value)) { free(node->value); } } list_free(images_list); images_list = tmp; } static void term_shift_region(int top, int height, int how_much) { if (how_much == 0) return; int destination, source; int count, new_top, new_bottom; if (how_much > height) { count = 0; new_top = top; new_bottom = top + height; } else if (how_much > 0) { destination = term_width * top; source = term_width * (top + how_much); count = height - how_much; new_top = top + height - how_much; new_bottom = top + height; } else if (how_much < 0) { destination = term_width * (top - how_much); source = term_width * top; count = height + how_much; new_top = top; new_bottom = top - how_much; } /* Move from top+how_much to top */ if (count) { memmove(term_buffer + destination, term_buffer + source, count * term_width * sizeof(term_cell_t)); memmove(term_mirror + destination, term_mirror + source, count * term_width * sizeof(term_cell_t)); } l_x = 0; l_y = 0; r_x = window->width; r_y = window->height; /* Clear new lines at bottom */ for (int i = new_top; i < new_bottom; ++i) { for (uint16_t x = 0; x < term_width; ++x) { cell_set(x, i, ' ', current_fg, current_bg, ansi_state->flags); cell_redraw(x, i); } } } /* Scroll the terminal up or down. */ static void term_scroll(int how_much) { term_shift_region(0, term_height, how_much); /* Remove image data for image cells that are no longer on screen. */ flush_unused_images(); } static void insert_delete_lines(int how_many) { if (how_many == 0) return; if (how_many > 0) { /* Insert lines is equivalent to scrolling from the current line */ term_shift_region(csr_y,term_height-csr_y,-how_many); } else { term_shift_region(csr_y,term_height-csr_y,-how_many); } } /* Is this a wide character? (does wcwidth == 2) */ static int is_wide(uint32_t codepoint) { if (codepoint < 256) return 0; return wcwidth(codepoint) == 2; } /* Save the row that is about to be scrolled offscreen into the scrollback buffer. */ static void save_scrollback(void) { /* If the scrollback is already full, remove the oldest element. */ struct scrollback_row * row = NULL; node_t * n = NULL; if (max_scrollback && scrollback_list->length == max_scrollback) { n = list_dequeue(scrollback_list); row = n->value; if (row->width < term_width) { free(row); row = NULL; } } if (!row) { row = malloc(sizeof(struct scrollback_row) + sizeof(term_cell_t) * term_width); row->width = term_width; } if (!n) { list_insert(scrollback_list, row); } else { n->value = row; list_append(scrollback_list, n); } for (int i = 0; i < term_width; ++i) { term_cell_t * cell = &term_buffer[i]; memcpy(&row->cells[i], cell, sizeof(term_cell_t)); } } /* Draw the scrollback. */ static void redraw_scrollback(void) { if (!scrollback_offset) { term_redraw_all(); return; } if (scrollback_offset < term_height) { for (int i = scrollback_offset; i < term_height; i++) { int y = i - scrollback_offset; for (int x = 0; x < term_width; ++x) { term_mirror_copy(x,i,&term_buffer[y * term_width + x]); } } node_t * node = scrollback_list->tail; for (int i = 0; i < scrollback_offset; ++i) { struct scrollback_row * row = (struct scrollback_row *)node->value; int y = scrollback_offset - 1 - i; int width = row->width; if (width > term_width) { width = term_width; } else { for (int x = row->width; x < term_width; ++x) { term_mirror_set(x, y, ' ', TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS); } } for (int x = 0; x < width; ++x) { term_mirror_copy(x,y,&row->cells[x]); } node = node->prev; } } else { node_t * node = scrollback_list->tail; for (int i = 0; i < scrollback_offset - term_height; ++i) { node = node->prev; } for (int i = scrollback_offset - term_height; i < scrollback_offset; ++i) { struct scrollback_row * row = (struct scrollback_row *)node->value; int y = scrollback_offset - 1 - i; int width = row->width; if (width > term_width) { width = term_width; } else { for (int x = row->width; x < term_width; ++x) { term_mirror_set(x, y, ' ', TERM_DEFAULT_FG, TERM_DEFAULT_BG, TERM_DEFAULT_FLAGS); } } for (int x = 0; x < width; ++x) { term_mirror_copy(x,y,&row->cells[x]); } node = node->prev; } } } static void undraw_cursor(void) { cell_redraw(csr_x, csr_y); } static void normalize_x(int setting_lcf) { if (csr_x >= term_width) { csr_x = term_width - 1; if (setting_lcf) { csr_h = 1; } } } static void normalize_y(void) { if (csr_y == term_height) { save_scrollback(); term_scroll(1); csr_y = term_height - 1; } } /* * ANSI callback for writing characters. * Parses some things (\n\r, etc.) itself that should probably * be moved into the ANSI library. */ static void term_write(char c) { static uint32_t unicode_state = 0; static uint32_t codepoint = 0; if (!decode(&unicode_state, &codepoint, (uint8_t)c)) { uint32_t o = codepoint; codepoint = 0; switch (c) { case '\a': /* boop */ return; case '\r': undraw_cursor(); csr_x = csr_h = 0; draw_cursor(); return; case '\t': undraw_cursor(); csr_x += (8 - csr_x % 8); normalize_x(0); draw_cursor(); return; case '\v': case '\f': case '\n': undraw_cursor(); csr_h = 0; ++csr_y; normalize_y(); draw_cursor(); return; case '\b': if (csr_x > 0) { undraw_cursor(); --csr_x; draw_cursor(); } csr_h = 0; return; default: { int wide = is_wide(o); uint8_t flags = ansi_state->flags; undraw_cursor(); if (csr_h || (wide && csr_x == term_width - 1)) { csr_x = csr_h = 0; ++csr_y; normalize_y(); } if (wide) { flags = flags | ANSI_WIDE; } cell_set(csr_x,csr_y, o, current_fg, current_bg, flags); cell_redraw(csr_x,csr_y); csr_x++; if (wide && csr_x != term_width) { cell_set(csr_x, csr_y, 0xFFFF, current_fg, current_bg, ansi_state->flags); cell_redraw(csr_x,csr_y); cell_redraw(csr_x-1,csr_y); csr_x++; } normalize_x(1); draw_cursor(); return; } } } else if (unicode_state == UTF8_REJECT) { unicode_state = 0; codepoint = 0; } } /* ANSI callback to set cursor position */ static void term_set_csr(int x, int y) { cell_redraw(csr_x,csr_y); if (x < 0) x = 0; if (x >= term_width) x = term_width - 1; if (y < 0) y = 0; if (y >= term_height) y = term_height - 1; csr_x = x; csr_y = y; csr_h = 0; draw_cursor(); } /* ANSI callback to get cursor x position */ static int term_get_csr_x(void) { return csr_x; } /* ANSI callback to get cursor y position */ static int term_get_csr_y(void) { return csr_y; } /* ANSI callback to set cell image data. */ static void term_set_cell_contents(int x, int y, char * data) { char * cell_data = malloc(char_width * char_height * sizeof(uint32_t)); memcpy(cell_data, data, char_width * char_height * sizeof(uint32_t)); list_insert(images_list, cell_data); cell_set(x, y, ' ', (uintptr_t)(cell_data) & 0xFFFFFFFF, (uintptr_t)(cell_data) >> 32, ANSI_EXT_IMG); } /* ANSI callback to get character cell width */ static int term_get_cell_width(void) { return char_width; } /* ANSI callback to get character cell height */ static int term_get_cell_height(void) { return char_height; } /* ANSI callback to set cursor visibility */ static void term_set_csr_show(int on) { cursor_on = on; if (on) { draw_cursor(); } } /* ANSI callback to set the foreground/background colors. */ static void term_set_colors(uint32_t fg, uint32_t bg) { current_fg = fg; current_bg = bg; } /* ANSI callback to force the cursor to draw */ static void term_redraw_cursor() { if (term_buffer) { draw_cursor(); } } /* ANSI callback to set a cell to a codepoint (only ever used to set spaces) */ static void term_set_cell(int x, int y, uint32_t c) { cell_set(x, y, c, current_fg, current_bg, ansi_state->flags); cell_redraw(x, y); } /* ANSI callback to clear the terminal. */ static void term_clear(int i) { if (i == 2) { /* Clear all */ csr_x = 0; csr_y = 0; csr_h = 0; memset((void *)term_buffer, 0x00, term_width * term_height * sizeof(term_cell_t)); if (!_no_frame) { render_decors(); } term_redraw_all(); } else if (i == 0) { /* Clear after cursor */ for (int x = csr_x; x < term_width; ++x) { term_set_cell(x, csr_y, ' '); } for (int y = csr_y + 1; y < term_height; ++y) { for (int x = 0; x < term_width; ++x) { term_set_cell(x, y, ' '); } } } else if (i == 1) { /* Clear before cursor */ for (int y = 0; y < csr_y; ++y) { for (int x = 0; x < term_width; ++x) { term_set_cell(x, y, ' '); } } for (int x = 0; x < csr_x; ++x) { term_set_cell(x, csr_y, ' '); } } else if (i == 3) { /* Clear scrollback */ if (scrollback_list) { while (scrollback_list->length) { node_t * n = list_dequeue(scrollback_list); free(n->value); free(n); } scrollback_offset = 0; } } flush_unused_images(); } #define SWAP(T,a,b) do { T _a = a; a = b; b = _a; } while(0); static void term_switch_buffer(int buffer) { if (buffer != 0 && buffer != 1) return; if (buffer != active_buffer) { active_buffer = buffer; term_buffer = active_buffer == 0 ? term_buffer_a : term_buffer_b; SWAP(int, csr_x, _orig_x); SWAP(int, csr_y, _orig_y); SWAP(uint32_t, current_fg, _orig_fg); SWAP(uint32_t, current_bg, _orig_bg); term_redraw_all(); } } static void full_reset(void) { /* Reset everything */ csr_x = 0; csr_y = 0; csr_h = 0; /* Huh, why don't we haven't an _orig_h - surely hold should be saved? */ _orig_x = 0; _orig_y = 0; current_fg = TERM_DEFAULT_FG; current_bg = TERM_DEFAULT_BG; _orig_fg = TERM_DEFAULT_FG; _orig_bg = TERM_DEFAULT_BG; active_buffer = 0; term_buffer = term_buffer_a; /* Clear both buffers to 0 */ memset((void *)term_buffer_a, 0x00, term_width * term_height * sizeof(term_cell_t)); memset((void *)term_buffer_b, 0x00, term_width * term_height * sizeof(term_cell_t)); /* Clear the mirror; do not clear the display buffer */ memset((void *)term_mirror, 0x00, term_width * term_height * sizeof(term_cell_t)); /* Enable cursor */ cursor_on = 1; /* Clear title */ terminal_title_length = 0; } /* ANSI callbacks */ term_callbacks_t term_callbacks = { term_write, term_set_colors, term_set_csr, term_get_csr_x, term_get_csr_y, term_set_cell, term_clear, term_scroll, term_redraw_cursor, input_buffer_stuff, set_title, term_set_cell_contents, term_get_cell_width, term_get_cell_height, term_set_csr_show, term_switch_buffer, insert_delete_lines, full_reset, }; static void handle_input(char c) { write_input_buffer(&c, 1); if (scrollback_offset != 0) { scrollback_offset = 0; term_redraw_all(); } } static void handle_input_s(char * c) { size_t len = strlen(c); write_input_buffer(c, len); if (scrollback_offset != 0) { scrollback_offset = 0; term_redraw_all(); } } /* Scroll the view up (scrollback) */ static void scroll_up(int amount) { int i = 0; while (i < amount && scrollback_list && scrollback_offset < (int)scrollback_list->length) { scrollback_offset ++; i++; } redraw_scrollback(); } /* Scroll the view down (scrollback) */ void scroll_down(int amount) { int i = 0; while (i < amount && scrollback_list && scrollback_offset != 0) { scrollback_offset -= 1; i++; } redraw_scrollback(); } /* Handle a key press from Yutani */ static void key_event(int ret, key_event_t * event) { if (ret) { /* Ctrl-Shift-C - Copy selection */ if ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) && (event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) && (event->keycode == 'c')) { if (selection) { /* Copy selection */ copy_selection(); } return; } /* Ctrl-Shift-V - Paste selection */ if ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) && (event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) && (event->keycode == 'v')) { /* Paste selection */ yutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD); return; } if ((event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) && (event->keycode == '0')) { scale_fonts = 0; font_scaling = 1.0; update_scale_menu(); reinit(); return; } if ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) && (event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) && (event->keycode == '=')) { scale_fonts = 1; font_scaling = font_scaling * 1.2; update_scale_menu(); reinit(); return; } if ((event->modifiers & KEY_MOD_LEFT_CTRL || event->modifiers & KEY_MOD_RIGHT_CTRL) && (event->keycode == '-')) { scale_fonts = 1; font_scaling = font_scaling * 0.8333333; update_scale_menu(); reinit(); return; } /* Left alt */ if (event->modifiers & KEY_MOD_LEFT_ALT || event->modifiers & KEY_MOD_RIGHT_ALT) { handle_input('\033'); } /* Shift-Tab */ if ((event->modifiers & KEY_MOD_LEFT_SHIFT || event->modifiers & KEY_MOD_RIGHT_SHIFT) && event->key == '\t') { handle_input_s("\033[Z"); return; } /* ENTER = reads as linefeed, should be carriage return */ if (event->keycode == 10) { handle_input('\r'); return; } /* BACKSPACE = reads as ^H, should be ^? */ if (event->keycode == 8) { handle_input(0x7F); return; } /* Pass key value to PTY */ handle_input(event->key); } else { /* Special keys without ->key values */ /* Only trigger on key down */ if (event->action == KEY_ACTION_UP) return; switch (event->keycode) { case KEY_F1: handle_input_s("\033OP"); break; case KEY_F2: handle_input_s("\033OQ"); break; case KEY_F3: handle_input_s("\033OR"); break; case KEY_F4: handle_input_s("\033OS"); break; case KEY_F5: handle_input_s("\033[15~"); break; case KEY_F6: handle_input_s("\033[17~"); break; case KEY_F7: handle_input_s("\033[18~"); break; case KEY_F8: handle_input_s("\033[19~"); break; case KEY_F9: handle_input_s("\033[20~"); break; case KEY_F10: handle_input_s("\033[21~"); break; case KEY_F11: handle_input_s("\033[23~"); break; case KEY_F12: /* Toggle decorations */ if (!_fullscreen) { _no_frame = !_no_frame; update_bounds(); window_width = window->width - decor_width; window_height = window->height - (decor_height + menu_bar_height); reinit(); } break; case KEY_ARROW_UP: if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[6A"); } else if (event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[5A"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[4A"); } else if (event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[3A"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT) { handle_input_s("\033[2A"); } else { handle_input_s("\033[A"); } break; case KEY_ARROW_DOWN: if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[6B"); } else if (event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[5B"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[4B"); } else if (event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[3B"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT) { handle_input_s("\033[2B"); } else { handle_input_s("\033[B"); } break; case KEY_ARROW_RIGHT: if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[6C"); } else if (event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[5C"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[4C"); } else if (event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[3C"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT) { handle_input_s("\033[2C"); } else { handle_input_s("\033[C"); } break; case KEY_ARROW_LEFT: if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[6D"); } else if (event->modifiers & KEY_MOD_LEFT_CTRL) { handle_input_s("\033[5D"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT && event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[4D"); } else if (event->modifiers & KEY_MOD_LEFT_ALT) { handle_input_s("\033[3D"); } else if (event->modifiers & KEY_MOD_LEFT_SHIFT) { handle_input_s("\033[2D"); } else { handle_input_s("\033[D"); } break; case KEY_PAGE_UP: if (event->modifiers & KEY_MOD_LEFT_SHIFT) { scroll_up(term_height/2); } else { handle_input_s("\033[5~"); } break; case KEY_PAGE_DOWN: if (event->modifiers & KEY_MOD_LEFT_SHIFT) { scroll_down(term_height/2); } else { handle_input_s("\033[6~"); } break; case KEY_HOME: if (event->modifiers & KEY_MOD_LEFT_SHIFT) { if (scrollback_list) { scrollback_offset = scrollback_list->length; redraw_scrollback(); } } else { handle_input_s("\033[H"); } break; case KEY_END: if (event->modifiers & KEY_MOD_LEFT_SHIFT) { scrollback_offset = 0; redraw_scrollback(); } else { handle_input_s("\033[F"); } break; case KEY_DEL: handle_input_s("\033[3~"); break; case KEY_INSERT: handle_input_s("\033[2~"); break; } } } /* Check if the Terminal should close. */ static void check_for_exit(void) { /* If something has set exit_application, we should exit. */ if (exit_application) return; pid_t pid = waitpid(-1, NULL, WNOHANG); /* If the child has exited, we should exit. */ if (pid != child_pid) return; /* Clean up */ exit_application = 1; /* Write [Process terminated] */ char exit_message[] = "[Process terminated]\n"; write(fd_slave, exit_message, sizeof(exit_message)); close(input_buffer_semaphore[1]); } static term_cell_t * copy_terminal(int old_width, int old_height, term_cell_t * term_buffer) { term_cell_t * new_term_buffer = malloc(sizeof(term_cell_t) * term_width * term_height); memset(new_term_buffer, 0x0, sizeof(term_cell_t) * term_width * term_height); int offset = 0; if (term_height < old_height) { while (csr_y >= term_height) { offset++; old_height--; csr_y--; } } for (int row = 0; row < min(old_height, term_height); ++row) { for (int col = 0; col < min(old_width, term_width); ++col) { term_cell_t * old_cell = &term_buffer[(row+offset) * old_width + col]; term_cell_t * new_cell = &new_term_buffer[row * term_width + col]; *new_cell = *old_cell; } } if (csr_x >= term_width) { csr_x = term_width-1; } return new_term_buffer; } /* Reinitialize the terminal after a resize. */ static void reinit(void) { /* Figure out character sizes if fonts have changed. */ if (_use_aa) { char_width = 8; char_height = 17; font_size = 13; char_offset = 13; if (scale_fonts) { font_size *= font_scaling; char_height *= font_scaling; char_width *= font_scaling; char_offset *= font_scaling; } } else { char_width = LARGE_FONT_CELL_WIDTH; char_height = LARGE_FONT_CELL_HEIGHT; } int old_width = term_width; int old_height = term_height; /* Resize the terminal buffer */ term_width = window_width / char_width; term_height = window_height / char_height; if (term_width == old_width && term_height == old_height) { memset(term_display, 0xFF, sizeof(term_cell_t) * term_width * term_height); draw_fill(ctx, rgba(0,0,0, TERM_DEFAULT_OPAC)); render_decors(); maybe_flip_display(1); return; } if (term_buffer) { term_cell_t * new_a = copy_terminal(old_width, old_height, term_buffer_a); term_cell_t * new_b = copy_terminal(old_width, old_height, term_buffer_b); free(term_buffer_a); term_buffer_a = new_a; free(term_buffer_b); term_buffer_b = new_b; if (active_buffer == 0) { term_buffer = new_a; } else { term_buffer = new_b; } } else { term_buffer_a = malloc(sizeof(term_cell_t) * term_width * term_height); memset(term_buffer_a, 0x0, sizeof(term_cell_t) * term_width * term_height); term_buffer_b = malloc(sizeof(term_cell_t) * term_width * term_height); memset(term_buffer_b, 0x0, sizeof(term_cell_t) * term_width * term_height); term_buffer = term_buffer_a; } term_mirror = realloc(term_mirror, sizeof(term_cell_t) * term_width * term_height); memcpy(term_mirror, term_buffer, sizeof(term_cell_t) * term_width * term_height); term_display = realloc(term_display, sizeof(term_cell_t) * term_width * term_height); memset(term_display, 0xFF, sizeof(term_cell_t) * term_width * term_height); /* Reset the ANSI library, ensuring we keep certain values */ int old_mouse_state = 0; if (ansi_state) old_mouse_state = ansi_state->mouse_on; ansi_state = ansi_init(ansi_state, term_width, term_height, &term_callbacks); ansi_state->mouse_on = old_mouse_state; /* Send window size change ioctl */ struct winsize w; w.ws_row = term_height; w.ws_col = term_width; w.ws_xpixel = term_width * char_width; w.ws_ypixel = term_height * char_height; ioctl(fd_master, TIOCSWINSZ, &w); /* Redraw the window */ draw_fill(ctx, rgba(0,0,0, TERM_DEFAULT_OPAC)); render_decors(); term_redraw_all(); } static void update_bounds(void) { if (!_no_frame) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); decor_left_width = bounds.left_width; decor_top_height = bounds.top_height; decor_right_width = bounds.right_width; decor_bottom_height = bounds.bottom_height; decor_width = bounds.width; decor_height = bounds.height; menu_bar_height = 24; } else { decor_left_width = 0; decor_top_height = 0; decor_right_width = 0; decor_bottom_height = 0; decor_width = 0; decor_height = 0; menu_bar_height = 0; } } /* Handle window resize event. */ static void resize_finish(int width, int height) { static int resize_attempts = 0; /* Calculate window size */ update_bounds(); int extra_x = decor_width; int extra_y = decor_height + menu_bar_height; int t_window_width = width - extra_x; int t_window_height = height - extra_y; /* Prevent the terminal from becoming too small. */ if (t_window_width < char_width * 20 || t_window_height < char_height * 10) { resize_attempts++; int n_width = extra_x + max(char_width * 20, t_window_width); int n_height = extra_y + max(char_height * 10, t_window_height); yutani_window_resize_offer(yctx, window, n_width, n_height); return; } /* If requested, ensure the terminal resizes to a fixed size based on the cell size. */ if (!_free_size && ((t_window_width % char_width != 0 || t_window_height % char_height != 0) && resize_attempts < 3)) { resize_attempts++; int n_width = extra_x + t_window_width - (t_window_width % char_width); int n_height = extra_y + t_window_height - (t_window_height % char_height); yutani_window_resize_offer(yctx, window, n_width, n_height); return; } resize_attempts = 0; /* Accept new window size */ yutani_window_resize_accept(yctx, window, width, height); window_width = window->width - extra_x; window_height = window->height - extra_y; /* Reinitialize the graphics library */ reinit_graphics_yutani(ctx, window); /* Reinitialize the terminal buffer and ANSI library */ reinit(); maybe_flip_display(1); /* We are done resizing. */ yutani_window_resize_done(yctx, window); yutani_flip(yctx, window); } /* Insert a mouse event sequence into the PTY */ static void mouse_event(int button, int x, int y) { if (ansi_state->mouse_on & TERMEMU_MOUSE_SGR) { char buf[100]; sprintf(buf,"\033[<%d;%d;%d%c", button == 3 ? 0 : button, x+1, y+1, button == 3 ? 'm' : 'M'); handle_input_s(buf); } else { char buf[7]; sprintf(buf, "\033[M%c%c%c", button + 32, x + 33, y + 33); handle_input_s(buf); } } /* Handle Yutani messages */ static void * handle_incoming(void) { static uint64_t last_click = 0; yutani_msg_t * m = yutani_poll(yctx); while (m) { if (menu_process_event(yctx, m)) { render_decors(); } switch (m->type) { case YUTANI_MSG_KEY_EVENT: { struct yutani_msg_key_event * ke = (void*)m->data; int ret = (ke->event.action == KEY_ACTION_DOWN) && (ke->event.key); if (ke->wid == window->wid) key_event(ret, &ke->event); } 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*)(uintptr_t)wf->wid); if (win == window) { win->focused = wf->focused; render_decors(); draw_cursor(); maybe_flip_display(1); } } break; case YUTANI_MSG_WINDOW_CLOSE: { struct yutani_msg_window_close * wc = (void*)m->data; if (wc->wid == window->wid) { kill(child_pid, SIGKILL); exit_application = 1; } } break; case YUTANI_MSG_SESSION_END: { kill(child_pid, SIGKILL); exit_application = 1; } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; case YUTANI_MSG_CLIPBOARD: { struct yutani_msg_clipboard * cb = (void *)m->data; if (selection_text) { free(selection_text); } if (*cb->content == '\002') { int size = atoi(&cb->content[2]); FILE * clipboard = yutani_open_clipboard(yctx); selection_text = malloc(size + 1); fread(selection_text, 1, size, clipboard); selection_text[size] = '\0'; fclose(clipboard); } else { selection_text = malloc(cb->size+1); memcpy(selection_text, cb->content, cb->size); selection_text[cb->size] = '\0'; } if (ansi_state->paste_mode) { handle_input_s("\033[200~"); handle_input_s(selection_text); handle_input_s("\033[201~"); } else { handle_input_s(selection_text); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; if (me->wid != window->wid) break; if (!_no_frame) { int decor_response = decor_handle_event(yctx, m); switch (decor_response) { case DECOR_CLOSE: kill(child_pid, SIGKILL); exit_application = 1; 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: break; } menu_bar_mouse_event(yctx, window, &terminal_menu_bar, me, me->new_x, me->new_y); } if (me->new_x < 0 || me->new_y < 0 || (!_no_frame && (me->new_x >= (int)window_width + (int)decor_width || me->new_y < (int)decor_top_height+menu_bar_height || me->new_y >= (int)(window_height + decor_top_height+menu_bar_height) || me->new_x < (int)decor_left_width || me->new_x >= (int)(window_width + decor_left_width))) || (_no_frame && (me->new_x >= (int)window_width || me->new_y >= (int)window_height))) { if (window->mouse_state == YUTANI_CURSOR_TYPE_IBEAM) { yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET); } break; } if (!(ansi_state->mouse_on & TERMEMU_MOUSE_ENABLE)) { if (window->mouse_state == YUTANI_CURSOR_TYPE_RESET) { yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_IBEAM); } } else { if (window->mouse_state == YUTANI_CURSOR_TYPE_IBEAM) { yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET); } } int new_x = me->new_x; int new_y = me->new_y; if (!_no_frame) { new_x -= decor_left_width; new_y -= decor_top_height+menu_bar_height; } /* Convert from coordinate to cell positon */ new_x /= char_width; new_y /= char_height; if (new_x < 0 || new_y < 0) break; if (new_x >= term_width || new_y >= term_height) break; /* Map Cursor Action */ if ((ansi_state->mouse_on & TERMEMU_MOUSE_ENABLE) && !(me->modifiers & YUTANI_KEY_MODIFIER_SHIFT)) { if (me->buttons & YUTANI_MOUSE_SCROLL_UP) { mouse_event(32+32, new_x, new_y); } else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) { mouse_event(32+32+1, new_x, new_y); } if (me->buttons != button_state) { /* Figure out what changed */ if (me->buttons & YUTANI_MOUSE_BUTTON_LEFT && !(button_state & YUTANI_MOUSE_BUTTON_LEFT)) mouse_event(0, new_x, new_y); if (me->buttons & YUTANI_MOUSE_BUTTON_MIDDLE && !(button_state & YUTANI_MOUSE_BUTTON_MIDDLE)) mouse_event(1, new_x, new_y); if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT && !(button_state & YUTANI_MOUSE_BUTTON_RIGHT)) mouse_event(2, new_x, new_y); if (!(me->buttons & YUTANI_MOUSE_BUTTON_LEFT) && button_state & YUTANI_MOUSE_BUTTON_LEFT) mouse_event(3, new_x, new_y); if (!(me->buttons & YUTANI_MOUSE_BUTTON_MIDDLE) && button_state & YUTANI_MOUSE_BUTTON_MIDDLE) mouse_event(3, new_x, new_y); if (!(me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) && button_state & YUTANI_MOUSE_BUTTON_RIGHT) mouse_event(3, new_x, new_y); last_mouse_x = new_x; last_mouse_y = new_y; button_state = me->buttons; } else if (ansi_state->mouse_on & TERMEMU_MOUSE_DRAG) { /* Report motion for pressed buttons */ if (last_mouse_x == new_x && last_mouse_y == new_y) break; if (button_state & YUTANI_MOUSE_BUTTON_LEFT) mouse_event(32, new_x, new_y); if (button_state & YUTANI_MOUSE_BUTTON_MIDDLE) mouse_event(33, new_x, new_y); if (button_state & YUTANI_MOUSE_BUTTON_RIGHT) mouse_event(34, new_x, new_y); last_mouse_x = new_x; last_mouse_y = new_y; } } else { if (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { redraw_scrollback(); uint64_t now = get_ticks(); if (now - last_click < 500000UL && (new_x == selection_start_x && new_y == selection_start_y)) { /* Double click */ while (selection_start_x > 0) { term_cell_t * c = cell_at(selection_start_x-1, selection_start_y); if (!c || c->c == ' ' || !c->c) break; selection_start_x--; } while (selection_end_x < term_width - 1) { term_cell_t * c = cell_at(selection_end_x+1, selection_end_y); if (!c || c->c == ' ' || !c->c) break; selection_end_x++; } selection = 1; if (_menu_copy) menu_update_enabled(_menu_copy, selection); } else { last_click = get_ticks(); selection_start_x = new_x; selection_start_y = new_y; selection_end_x = new_x; selection_end_y = new_y; selection = 0; if (_menu_copy) menu_update_enabled(_menu_copy, selection); } redraw_selection(); } if (me->command == YUTANI_MOUSE_EVENT_DRAG && me->buttons & YUTANI_MOUSE_BUTTON_LEFT ){ mark_selection(); selection_end_x = new_x; selection_end_y = new_y; selection = 1; if (_menu_copy) menu_update_enabled(_menu_copy, selection); flip_selection(); } if (me->command == YUTANI_MOUSE_EVENT_RAISE) { if (me->new_x == me->old_x && me->new_y == me->old_y) { selection = 0; if (_menu_copy) menu_update_enabled(_menu_copy, selection); term_redraw_all(); redraw_scrollback(); } /* else selection */ } if (me->buttons & YUTANI_MOUSE_SCROLL_UP) { scroll_up(5); } else if (me->buttons & YUTANI_MOUSE_SCROLL_DOWN) { scroll_down(5); } else if (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT) { if (!menu_right_click->window) { menu_show_at(menu_right_click, window, me->new_x, me->new_y); } } } } break; default: break; } free(m); m = yutani_poll_async(yctx); } return NULL; } /* * Menu Actions */ /* File > Exit */ static void _menu_action_exit(struct MenuEntry * self) { kill(child_pid, SIGKILL); exit_application = 1; } static void _menu_action_hide_borders(struct MenuEntry * self) { _no_frame = !(_no_frame); update_bounds(); window_width = window->width - decor_width; window_height = window->height - (decor_height + menu_bar_height); menu_update_toggle_state(_menu_toggle_borders_context, !_no_frame); menu_update_toggle_state(_menu_toggle_borders_bar, !_no_frame); reinit(); } static struct MenuEntry * _menu_toggle_bitmap_context = NULL; static struct MenuEntry * _menu_toggle_bitmap_bar = NULL; static void _menu_action_toggle_tt(struct MenuEntry * self) { _use_aa = !(_use_aa); menu_update_toggle_state(_menu_toggle_bitmap_context, !_use_aa); menu_update_toggle_state(_menu_toggle_bitmap_bar, !_use_aa); menu_update_enabled(_menu_set_zoom, _use_aa); reinit(); } static void _menu_action_toggle_free_size(struct MenuEntry * self) { _free_size = !(_free_size); menu_update_toggle_state(self, !_free_size); } static void _menu_action_show_about(struct MenuEntry * self) { char about_cmd[1024] = "\0"; strcat(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\" "); char coords[100]; sprintf(coords, "%d %d &", (int)window->x + (int)window->width / 2, (int)window->y + (int)window->height / 2); strcat(about_cmd, coords); system(about_cmd); render_decors(); } static void _menu_action_show_help(struct MenuEntry * self) { system("help-browser terminal.trt &"); render_decors(); } static void _menu_action_copy(struct MenuEntry * self) { copy_selection(); } static void _menu_action_paste(struct MenuEntry * self) { yutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD); } static void update_scale_menu(void) { menu_update_toggle_state(_menu_scale_075, font_scaling == 0.75); menu_update_toggle_state(_menu_scale_100, font_scaling == 1.00); menu_update_toggle_state(_menu_scale_150, font_scaling == 1.50); menu_update_toggle_state(_menu_scale_200, font_scaling == 2.00); } static void _menu_action_set_scale(struct MenuEntry * self) { struct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self; if (!_self->action) { scale_fonts = 0; font_scaling = 1.0; update_scale_menu(); } else { scale_fonts = 1; font_scaling = atof(_self->action); update_scale_menu(); } reinit(); } static void render_decors_callback(struct menu_bar * self) { (void)self; render_decors(); } /** * Geometry argument follows this format: * [@]WxH[+X,Y] * * If @ is present, W and H are in characters. * If + is present, X and Y are the left and top offset. */ static void parse_geometry(char ** argv, char * str) { int in_chars = 0; if (*str == '@') { in_chars = 1; str++; } /* Split on 'x', which is required. */ char * c = strstr(str, "x"); if (!c) return; /* Ignore invalid arg */ *c = '\0'; c++; /* Find optional + that starts position */ char * plus = strstr(c, "+"); if (plus) { *plus = '\0'; plus++; } /* Parse size */ window_width = atoi(str) * (in_chars ? char_width : 1); window_height = atoi(c) * (in_chars ? char_height : 1); if (plus) { /* If there was a plus, let's look for a comma */ char * comma = strstr(plus, ","); if (!comma) return; /* Skip invalid position */ *comma = '\0'; comma++; window_position_set = 1; window_left = atoi(plus); window_top = atoi(comma); } } int main(int argc, char ** argv) { int _flags = 0; window_width = char_width * 80; window_height = char_height * 24; static struct option long_opts[] = { {"fullscreen", no_argument, 0, 'F'}, {"bitmap", no_argument, 0, 'b'}, {"scale", required_argument, 0, 's'}, {"help", no_argument, 0, 'h'}, {"grid", no_argument, 0, 'x'}, {"no-frame", no_argument, 0, 'n'}, {"geometry", required_argument, 0, 'g'}, {"blurred", no_argument, 0, 'B'}, {"scrollback", required_argument, 0, 'S'}, {0,0,0,0} }; /* Read some arguments */ int index, c; while ((c = getopt_long(argc, argv, "bhxnFls:g:BS:", long_opts, &index)) != -1) { if (!c) { if (long_opts[index].flag == 0) { c = long_opts[index].val; } } switch (c) { case 'x': _free_size = 0; break; case 'n': _no_frame = 1; break; case 'F': _fullscreen = 1; _no_frame = 1; break; case 'b': _use_aa = 0; break; case 'h': usage(argv); return 0; break; case 's': scale_fonts = 1; font_scaling = atof(optarg); break; case 'g': parse_geometry(argv,optarg); break; case 'B': _flags = YUTANI_WINDOW_FLAG_BLUR_BEHIND; break; case 'S': max_scrollback = strtoull(optarg,NULL,10); break; case '?': break; default: break; } } /* Initialize the windowing library */ yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]); return 1; } _tt_font_normal = tt_font_from_shm("monospace"); _tt_font_bold = tt_font_from_shm("monospace.bold"); _tt_font_oblique = tt_font_from_shm("monospace.italic"); _tt_font_bold_oblique = tt_font_from_shm("monospace.bolditalic"); _tt_font_fallback = _tt_font_normal; _tt_font_japanese = tt_font_from_file("/usr/share/fonts/truetype/vlgothic/VL-Gothic-Regular.ttf"); /* Might not be present */ /* Full screen mode forces window size to be that the display server */ if (_fullscreen) { window_width = yctx->display_width; window_height = yctx->display_height; } if (_no_frame) { window = yutani_window_create_flags(yctx, window_width, window_height, YUTANI_WINDOW_FLAG_NO_ANIMATION); } else { init_decorations(); struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); window = yutani_window_create_flags(yctx, window_width + bounds.width, window_height + bounds.height + menu_bar_height, _flags); yutani_window_update_shape(yctx, window, 20); } if (_fullscreen) { /* If fullscreen, assume we're always focused and put us on the bottom. */ yutani_set_stack(yctx, window, YUTANI_ZORDER_BOTTOM); window->focused = 1; } else { window->focused = 0; } update_bounds(); /* Set up menus */ terminal_menu_bar.entries = terminal_menu_entries; terminal_menu_bar.redraw_callback = render_decors_callback; _menu_exit = menu_create_normal("exit","exit","Exit", _menu_action_exit); _menu_copy = menu_create_normal(NULL, NULL, "Copy", _menu_action_copy); _menu_paste = menu_create_normal(NULL, NULL, "Paste", _menu_action_paste); menu_update_enabled(_menu_copy, selection); menu_right_click = menu_create(); menu_insert(menu_right_click, _menu_copy); menu_insert(menu_right_click, _menu_paste); menu_insert(menu_right_click, menu_create_separator()); if (!_fullscreen) { _menu_toggle_borders_context = menu_create_toggle(NULL, "Show borders", !_no_frame, _menu_action_hide_borders); menu_insert(menu_right_click, _menu_toggle_borders_context); } _menu_toggle_bitmap_context = menu_create_toggle(NULL, "Bitmap font", !_use_aa, _menu_action_toggle_tt); menu_insert(menu_right_click, _menu_toggle_bitmap_context); menu_insert(menu_right_click, menu_create_separator()); menu_insert(menu_right_click, _menu_exit); /* Menu Bar menus */ terminal_menu_bar.set = menu_set_create(); struct MenuList * m; m = menu_create(); /* File */ menu_insert(m, _menu_exit); menu_set_insert(terminal_menu_bar.set, "file", m); m = menu_create(); menu_insert(m, _menu_copy); menu_insert(m, _menu_paste); menu_set_insert(terminal_menu_bar.set, "edit", m); m = menu_create(); menu_insert(m, (_menu_scale_075 = menu_create_toggle("0.75", "75%", 0, _menu_action_set_scale))); menu_insert(m, (_menu_scale_100 = menu_create_toggle(NULL, "100%", 0, _menu_action_set_scale))); menu_insert(m, (_menu_scale_150 = menu_create_toggle("1.5", "150%", 0, _menu_action_set_scale))); menu_insert(m, (_menu_scale_200 = menu_create_toggle("2.0", "200%", 0, _menu_action_set_scale))); update_scale_menu(); menu_set_insert(terminal_menu_bar.set, "zoom", m); m = menu_create(); menu_insert(m, menu_create_normal(NULL, NULL, "View stats", _menu_action_cache_stats)); menu_insert(m, menu_create_normal(NULL, NULL, "Clear cache", _menu_action_clear_cache)); menu_set_insert(terminal_menu_bar.set, "cache", m); m = menu_create(); _menu_toggle_borders_bar = menu_create_toggle(NULL, "Show borders", !_no_frame, _menu_action_hide_borders); menu_insert(m, _menu_toggle_borders_bar); menu_insert(m, (_menu_set_zoom = menu_create_submenu(NULL,"zoom","Set zoom..."))); menu_update_enabled(_menu_set_zoom, _use_aa); _menu_toggle_bitmap_bar = menu_create_toggle(NULL, "Bitmap font", !_use_aa, _menu_action_toggle_tt); menu_insert(m, _menu_toggle_bitmap_bar); menu_insert(m, menu_create_toggle(NULL, "Snap to Cell Size", !_free_size, _menu_action_toggle_free_size)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal(NULL, NULL, "Redraw", _menu_action_redraw)); menu_insert(m, menu_create_submenu(NULL,"cache","Glyph cache...")); menu_set_insert(terminal_menu_bar.set, "view", m); m = menu_create(); menu_insert(m, menu_create_normal("help","help","Contents", _menu_action_show_help)); menu_insert(m, menu_create_separator()); menu_insert(m, menu_create_normal("star","star","About Terminal", _menu_action_show_about)); menu_set_insert(terminal_menu_bar.set, "help", m); scrollback_list = list_create(); images_list = list_create(); /* Initialize the graphics context */ ctx = init_graphics_yutani_double_buffer(window); /* Clear to black */ draw_fill(ctx, rgba(0,0,0,0)); if (window_position_set) { /* Move to requested position */ yutani_window_move(yctx, window, window_left, window_top); } else { /* Move window to screen center */ yutani_window_move(yctx, window, yctx->display_width / 2 - window->width / 2, yctx->display_height / 2 - window->height / 2); } /* Open a PTY */ openpty(&fd_master, &fd_slave, NULL, NULL, NULL); terminal = fdopen(fd_slave, "w"); /* Initialize the terminal buffer and ANSI library for the first time. */ reinit(); /* Run thread to handle asynchronous writes to the tty */ pthread_t input_buffer_thread; pipe(input_buffer_semaphore); input_buffer_queue = list_create(); pthread_create(&input_buffer_thread, NULL, handle_input_writing, NULL); /* Make sure we're not passing anything to stdin on the child */ fflush(stdin); /* Fork off child */ child_pid = fork(); if (!child_pid) { setsid(); /* Prepare stdin/out/err */ dup2(fd_slave, 0); dup2(fd_slave, 1); dup2(fd_slave, 2); ioctl(STDIN_FILENO, TIOCSCTTY, &(int){1}); tcsetpgrp(STDIN_FILENO, getpid()); /* Set the TERM environment variable. */ putenv("TERM=toaru"); /* Execute requested initial process */ if (argv[optind] != NULL) { /* Run something specified by the terminal startup */ execvp(argv[optind], &argv[optind]); fprintf(stderr, "Failed to launch requested startup application.\n"); } else { /* Run the user's shell */ char * shell = getenv("SHELL"); if (!shell) shell = "/bin/sh"; /* fallback */ char * tokens[] = {shell,NULL}; execvp(tokens[0], tokens); exit(1); } /* Failed to start */ exit_application = 1; return 1; } else { /* Set up fswait to check Yutani and the PTY master */ int fds[2] = {fileno(yctx->sock), fd_master}; /* PTY read buffer */ unsigned char buf[4096]; int next_wait = 200; while (!exit_application) { /* Wait for something to happen. */ int res[] = {0,0}; fswait3(2,fds,next_wait,res); /* Check if the child application has closed. */ check_for_exit(); maybe_flip_cursor(); int force_flip = (!res[1] && (next_wait == 10)); if (res[1]) { /* Read from PTY */ ssize_t r = read(fd_master, buf, 4096); for (ssize_t i = 0; i < r; ++i) { ansi_put(ansi_state, buf[i]); } next_wait = 10; } else { next_wait = 200; } if (res[0]) { /* Handle Yutani events. */ handle_incoming(); } maybe_flip_display(force_flip); } } close(input_buffer_semaphore[1]); /* Windows will close automatically on exit. */ return 0; } ================================================ FILE: apps/test-badwrite.c ================================================ /** * @brief Test tool for examining a bug that was crashing the audio subsystem. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include int main(int argc, char * argv[]) { int fd = open("/dev/dsp",O_WRONLY); if (fd < 0) { fprintf(stderr, "failed to open dsp\n"); return 1; } return write(fd, &fd, -1); } ================================================ FILE: apps/test-conf.c ================================================ /** * @brief Test tool for the INI confreader library. * * @copyright * 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 */ #include #include #include #include int main(int argc, char * argv[]) { confreader_t * conf = confreader_load("/etc/demo.conf"); fprintf(stderr, "test 1\n"); assert(confreader_get(conf, "", "test") != NULL); assert(!strcmp(confreader_get(conf, "", "test"),"hello")); fprintf(stderr, "test 2\n"); assert(!strcmp(confreader_get(conf,"sec","tion"),"test")); return 0; } ================================================ FILE: apps/test-fpclassify.c ================================================ #include #include #include #include #include int main(int argc, char * argv[]) { if (argc < 2) return 1; union { double asFloat; uint64_t asInt; } bits; if (!strcmp(argv[1], "inf")) { bits.asFloat = INFINITY; } else if (!strcmp(argv[1], "nan")) { bits.asFloat = NAN; } else { bits.asFloat = strtod(argv[1], NULL); } printf("0x%016zx %d\n", bits.asInt, fpclassify(bits.asFloat)); return 0; } ================================================ FILE: apps/test-ftruncate.c ================================================ /** * @brief Quick spot check of ftruncate * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2026 K. Lange */ #include #include #include #include #include #include int main(int argc, char * argv[]) { int fd = open("test.file", O_RDWR | O_CREAT); ftruncate(fd, 7000); lseek(fd, 0, SEEK_SET); unsigned char buf[10000]; ssize_t resp = read(fd, buf, 8000); if (resp != 7000) { fprintf(stderr, "expected resp to be 7000, was %zd\n", resp); return 1; } /* Assert that 7000 bytes were empty */ for (size_t i = 0; i < 7000; ++i) { if (buf[i] != 0) { fprintf(stderr, "Byte %zu was not 0 (%#x)\n", i, (unsigned int)buf[i]); return 1; } } /* Write into the whole thing */ memset(buf, 0xAA, 7000); pwrite(fd, buf, 7000, 0); /* Assert we can read back out */ resp = pread(fd, buf, 8000, 0); if (resp != 7000) { fprintf(stderr, "pread: expected resp to be 7000, was %zd\n", resp); return 1; } for (size_t i = 0; i < 7000; ++i) { if (buf[i] != 0xaa) { fprintf(stderr, "Byte %zu was not 0xaa (%#x)\n", i, (unsigned int)buf[i]); return 1; } } ftruncate(fd, 2000); resp = pread(fd, buf, 8000, 0); if (resp != 2000) { fprintf(stderr, "pread: expected resp to be 2000, was %zd\n", resp); return 1; } for (size_t i = 0; i < 2000; ++i) { if (buf[i] != 0xaa) { fprintf(stderr, "Byte %zu was not 0xaa (%#x)\n", i, (unsigned int)buf[i]); return 1; } } ftruncate(fd, 6000); resp = pread(fd, buf, 8000, 0); if (resp != 6000) { fprintf(stderr, "pread: expected resp to be 6000, was %zd\n", resp); return 1; } for (size_t i = 0; i < 2000; ++i) { if (buf[i] != 0xaa) { fprintf(stderr, "Byte %zu was not 0xaa (%#x)\n", i, (unsigned int)buf[i]); return 1; } } for (size_t i = 2000; i < 6000; ++i) { if (buf[i] != 0) { fprintf(stderr, "Byte %zu was not 0 (%#x)\n", i, (unsigned int)buf[i]); return 1; } } return 0; } ================================================ FILE: apps/test-localtime.c ================================================ /** * @brief Test tool for the libc localtime() function. * * @copyright * 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-2021 K. Lange */ #include #include int main(int argc, char * argv[]) { time_t i = 1576000000; while (i < 2000000000) { struct tm * t = localtime(&i); if (t->tm_sec < 0 || t->tm_sec >= 60) fprintf(stderr, "Erroneous value at %ld: sec = %d\n", i, t->tm_sec); if (t->tm_min < 0 || t->tm_min >= 60) fprintf(stderr, "Erroneous value at %ld: min = %d\n", i, t->tm_min); if (t->tm_hour < 0 || t->tm_hour >= 24) fprintf(stderr, "Erroneous value at %ld (%s) hour = %d\n", i, asctime(t), t->tm_hour); i++; } return 0; } ================================================ FILE: apps/test-lock.c ================================================ /** * @brief Test tool for filesystem locks (O_EXCL) * * @copyright * 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 */ #include #include #include #include #include int main(int argc, char * argv[]) { if (argc < 2 ){ fprintf(stderr, "usage: test-lock LOCKPATH\n"); return 1; } int fd = open(argv[1],O_RDWR|O_CREAT|O_EXCL); if (fd < 0) { if (errno == EEXIST) { fprintf(stderr, "Lock is already held.\n"); return 0; } else { fprintf(stderr, "Some other error? %d = %s\n", errno, strerror(errno)); return 1; } } else { fprintf(stderr, "I have the lock, the fd is %d.\n", fd); fprintf(stderr, "Press Enter to release lock.\n"); while (!feof(stdin) && fgetc(stdin) != '\n') { /* nothing */ } close(fd); unlink(argv[1]); return 0; } } ================================================ FILE: apps/test-loop.c ================================================ /** * @brief Spins in a loop. Useful for testing preemption, cpu usage. * * @copyright * 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 */ int main(int argc, char * argv[]) { while (1); return 0; } ================================================ FILE: apps/test-printf.c ================================================ /** * @brief Test tool for libc printf formatters. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2020-2021 K. Lange */ #include int main(int argc, char * argv[]) { printf("%.3d\n", 42); printf("%.10d\n", 12345); printf("%.1d\n", 0); printf("%.0d\n", 0); printf("%.0d\n", 1); printf("%.0d\n", 123); return 0; } ================================================ FILE: apps/test-ptrace-syscall.c ================================================ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int main(int argc, char * argv[]) { pid_t p = fork(); if (!p) { if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) < 0) { fprintf(stderr, "%s: ptrace: %s\n", argv[0], strerror(errno)); return 1; } execvp(argv[optind], &argv[optind]); return 1; } while (1) { int status = 0; pid_t res = waitpid(p, &status, WSTOPPED); if (res < 0) { fprintf(stderr, "%s: waitpid: %s\n", argv[0], strerror(errno)); } else { if (WIFSTOPPED(status)) { if (WSTOPSIG(status) == SIGTRAP) { struct URegs regs; ptrace(PTRACE_GETREGS, p, NULL, ®s); /* Event type */ int event = (status >> 16) & 0xFF; switch (event) { case PTRACE_EVENT_SYSCALL_ENTER: if (uregs_syscall_num(®s) == SYS_SLEEP) { fprintf(stderr, "%s: sleep called, rewriting to yield\n", argv[0]); uregs_syscall_num(®s) = SYS_YIELD; ptrace(PTRACE_SETREGS, p, NULL, ®s); } break; default: break; } ptrace(PTRACE_CONT, p, NULL, NULL); } else { ptrace(PTRACE_CONT, p, NULL, (void*)(uintptr_t)WSTOPSIG(status)); } } else if (WIFSIGNALED(status)) { return 0; } else if (WIFEXITED(status)) { return 0; } } } return 0; } ================================================ FILE: apps/test-sigsegv.c ================================================ /** * @brief Test tool for producing segmentation faults. * * Useful for testing the debugger. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include int main(int argc, char * argv[]) { if (argc > 1) { fprintf(stderr, "%s\n", (char*)(uintptr_t)strtoul(argv[1],NULL,0)); } else { *(volatile int*)0x12345 = 42; } return 0; } ================================================ FILE: apps/test-sigsuspend.c ================================================ #include #include #include #include #include void handler(int sig) { fprintf(stderr, "received %d\n", sig); } int main(int argc, char * argv[]) { signal(SIGINT, handler); signal(SIGWINCH, handler); sigset_t all, prev; sigfillset(&all); sigprocmask(SIG_SETMASK,&all,&prev); fprintf(stderr, "Ignoring signals and pausing for three seconds.\n"); sleep(3); fprintf(stderr, "Sleep is over, calling sigsuspend.\n"); int result = sigsuspend(&prev); fprintf(stderr, "result = %d, errno = %s\n", result, strerror(errno)); sigprocmask(SIG_SETMASK,&prev,NULL); fprintf(stderr, "Restoring mask\n"); return 0; } ================================================ FILE: apps/test-sigwait.c ================================================ #include #include #include #include void handler(int sig) { fprintf(stderr, "received %d\n", sig); } int main(int argc, char * argv[]) { signal(SIGINT, handler); signal(SIGWINCH, handler); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigprocmask(SIG_BLOCK, &mask, NULL); while (1) { int sig = 0; int result = sigwait(&mask, &sig); fprintf(stderr, "result = %d, sig = %d, errno = %s\n", result, sig, strerror(errno)); } return 0; } ================================================ FILE: apps/test-syscall-sysret.c ================================================ #include #include #include int main(int argc, char * argv[]){ long ret = 0; #ifdef __x86_64__ __asm__ __volatile__("syscall" : "=a"(ret) : "a"(SYS_WRITE), "D"(STDOUT_FILENO), "S"("Hello, world.\n"), "d"((long)14) : "rcx", "r11", "memory"); __asm__ __volatile__("syscall" : "=a"(ret) : "a"(SYS_WRITE), "D"(STDOUT_FILENO), "S"("Hello, world.\n"), "d"((long)14) : "rcx", "r11", "memory"); __asm__ __volatile__("syscall" : "=a"(ret) : "a"(SYS_WRITE), "D"(STDOUT_FILENO), "S"("Hello, world.\n"), "d"((long)14) : "rcx", "r11", "memory"); #endif return ret; } ================================================ FILE: apps/test-tls.c ================================================ /** * @brief Test tool for thread local storage. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2020-2021 K. Lange */ #include #include #include __thread volatile int myvalue; void * getaddressinthread(void * _unused) { fprintf(stderr, "in thread before:\n"); fprintf(stderr, "&myvalue = %p\n", (void*)&myvalue); fprintf(stderr, "myvalue = %d\n", myvalue); myvalue = 1234; fprintf(stderr, "in thread after:\n"); fprintf(stderr, "&myvalue = %p\n", (void*)&myvalue); fprintf(stderr, "myvalue = %d\n", myvalue); return NULL; } int main(int argc, char * argv[]) { myvalue = 42; fprintf(stderr, "main thread before:\n"); fprintf(stderr, "&myvalue = %p\n", (void*)&myvalue); fprintf(stderr, "myvalue = %d\n", myvalue); pthread_t mythread; pthread_create(&mythread, NULL, getaddressinthread, NULL); void * retval; pthread_join(mythread, &retval); fprintf(stderr, "main thread after:\n"); fprintf(stderr, "&myvalue = %p\n", (void*)&myvalue); fprintf(stderr, "myvalue = %d\n", myvalue); return 0; } ================================================ FILE: apps/test-tty-read.c ================================================ #include #include #include #include #include struct termios old; struct termios new; void get_initial_termios(void) { tcgetattr(STDIN_FILENO, &old); } void on_sigusr1(int sig) { fprintf(stderr, "received SIGUSR1\n"); } int action = TCSADRAIN; void set_unbuffered(void) { memcpy(&new,&old,sizeof(struct termios)); new.c_iflag &= (~ICRNL) & (~IXON); new.c_lflag &= (~ICANON) & (~ECHO) & (~ISIG); #ifdef VLNEXT new.c_cc[VLNEXT] = 0; #endif new.c_cc[VMIN] = 2; tcsetattr(STDIN_FILENO, action, &new); } void set_buffered(void) { tcsetattr(STDIN_FILENO, action, &old); } int main(int argc, char * argv[]) { if (argc > 1 && !strcmp(argv[1], "flush")) { action = TCSAFLUSH; } get_initial_termios(); fprintf(stderr, "was VMIN=%d, VTIME=%d\n", old.c_cc[VMIN], old.c_cc[VTIME]); set_unbuffered(); fprintf(stderr, "now VMIN=%d, VTIME=%d\n", new.c_cc[VMIN], new.c_cc[VTIME]); signal(SIGUSR1, on_sigusr1); char buf[4096]; ssize_t r = read(STDIN_FILENO, buf, 4096); fprintf(stderr, "read=%zd\n", r); set_buffered(); } ================================================ FILE: apps/test-udp-recv.krk ================================================ #!/bin/kuroko import socket let s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) print("Binding to :1234") s.bind(('0.0.0.0',1234)) print("Waiting.") while True: print(s.recv(1024)) ================================================ FILE: apps/toaru_logo.h ================================================ /** * @file apps/toaru_logo.sh * @brief Generated by GIMP, ToaruOS logo * * Used by sysinfo. Can be used by other things as well. * TODO: sysinfo should probably just load a bitmap? * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ static const struct { unsigned int width; unsigned int height; unsigned int bytes_per_pixel; /* 2:RGB16, 3:RGB, 4:RGBA */ unsigned char pixel_data[23 * 23 * 4 + 1]; } gimp_image = { 23, 23, 4, "\0\260\377\377\0\260\377\377\0\0\0\0\0\260\377\377\0\260\377\377\0\0\0\0" "\0\260\377\377\0\260\377\377\0\0\0\0\0\260\377\377\0\260\377\377\0\0\0\0" "\0\260\377\377\0\260\377\377\0\0\0\0\0\260\377\377\0\260\377\377\0\0\0\0" "\0\260\377\377\0\260\377\377\0\0\0\0\0\260\377\377\0\260\377\377\0\253\377" "\377\0\253\377\377\0\0\0\0\0\253\377\377\0\253\377\377\0\0\0\0\0\253\377" "\377\0\253\377\377\0\0\0\0\0\253\377\377\0\253\377\377\0\0\0\0\0\253\377" "\377\0\253\377\377\0\0\0\0\0\253\377\377\0\253\377\377\0\0\0\0\0\253\377" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\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" "\0\241\377\377\0\0\0\0\0\241\377\377\0\241\377\377\0\0\0\0\0\241\377\377" "\0\241\377\377\0\0\0\0\0\241\377\377\0\241\377\377\0\0\0\0\0\241\377\377" "\0\241\377\377\0\0\0\0\0\241\377\377\0\241\377\377\0\0\0\0\0\241\377\377" "\0\241\377\377\0\234\377\377\0\234\377\377\0\0\0\0\0\234\377\377\0\234\377" "\377\0\0\0\0\0\234\377\377\0\234\377\377\0\0\0\0\0\234\377\377\0\234\377" "\377\0\0\0\0\0\234\377\377\0\234\377\377\0\0\0\0\0\234\377\377\0\234\377" "\377\0\0\0\0\0\234\377\377\0\234\377\377\0\0\0\0\0\234\377\377\0\234\377" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\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" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\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" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\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" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\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}" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\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" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\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^" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\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" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\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\0E\377\377\0E\377\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\0E\377\377\0E\377\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\0\0\0@\377\377" "\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" "\0\0\0\0\0\0\0\0\0\0\0\0\0@\377\377\0@\377\377", }; ================================================ FILE: apps/toast.krk ================================================ #!/bin/kuroko import kuroko import os def jsonFilter(c): let mapping = { '"': '\\"', '\\': '\\\\', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', } return mapping[c] if c in mapping else c def dumpJson(o): if isinstance(o,list): return '[' + ','.join([dumpJson(i) for i in o]) + ']' else if isinstance(o,int): return repr(o) else if isinstance(o,str): return '"' + ''.join([jsonFilter(c) for c in o]) + '"' else if isinstance(o,dict): return '{' + ','.join([dumpJson(k) + ':' + dumpJson(v) for k, v in o.items()]) + '}' else: return dumpJson(str(o)) let msg = {} let args = kuroko.argv[1:] let sock = os.open("/dev/pex/toast",os.O_WRONLY) if not sock: print("No toast daemon.") return 1 while args: let arg = args.pop(0) if arg == '--icon': msg['icon'] = args.pop(0) else if arg == '--duration': msg['duration'] = int(args.pop(0)) else if arg.startswith('--'): print("Unrecognized option:",arg) else: msg['body'] = arg print("Sending toast:", dumpJson(msg),"to",sock) os.write(sock, dumpJson(msg).encode()) ================================================ FILE: apps/toastd.c ================================================ /** * @brief Toast notification daemon. * @file apps/toastd.c * * Provides an endpoint for applications to post notifications * which are displayed in pop-up "toasts" in the upper-right * corner of the screen without stealing focus. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include typedef struct JSON_Value JSON_Value; static yutani_t * yctx; static FILE * pex_endpoint = NULL; static sprite_t background_sprite; static list_t * windows = NULL; static list_t * garbage = NULL; struct ToastNotification { yutani_window_t * window; struct timespec created; int duration; }; #define PAD_RIGHT 10 #define PAD_TOP 48 static void handle_msg(JSON_Value * msg) { if (msg->type != JSON_TYPE_OBJECT) { fprintf(stderr, "expected an object, but json value was of type %d\n", msg->type); return; } JSON_Value * msg_body = JSON_KEY(msg, "body"); if (!msg_body) { fprintf(stderr, "missing 'body'\n"); return; } if (msg_body->type != JSON_TYPE_STRING) { fprintf(stderr, "'body' should have been a string, but got type %d instead\n", msg_body->type); return; } /* At this point, we're going to show something, at least... */ yutani_window_t * win = yutani_window_create_flags(yctx, background_sprite.width, background_sprite.height, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_ALT_ANIMATION); yutani_set_stack(yctx, win, YUTANI_ZORDER_OVERLAY); /* TODO: We need to figure out how to place this... */ yutani_window_move(yctx, win, yctx->display_width - background_sprite.width - PAD_RIGHT, PAD_TOP + background_sprite.height * windows->length); struct ToastNotification * notification = malloc(sizeof(struct ToastNotification)); notification->window = win; clock_gettime(CLOCK_MONOTONIC, ¬ification->created); list_insert(windows, notification); JSON_Value * msg_duration = JSON_KEY(msg,"duration"); if (msg_duration && msg_duration->type == JSON_TYPE_NUMBER) { notification->duration = msg_duration->number; } else { notification->duration = 5; } /* Establish the rendering context for this window, we'll only need it for a bit. * We won't even both double buffering... */ gfx_context_t * ctx = init_graphics_yutani(win); draw_fill(ctx, rgba(0,0,0,0)); draw_sprite(ctx, &background_sprite, 0, 0); int textOffset = 0; /* Does it have an icon? */ JSON_Value * msg_icon = JSON_KEY(msg, "icon"); if (msg_icon && msg_icon->type == JSON_TYPE_STRING) { /* Just ignore the icon if it's not a string... */ sprite_t myIcon; if (!load_sprite(&myIcon, msg_icon->string)) { /* Is this a reasonable icon to display? */ if (myIcon.width < 100) { textOffset = myIcon.width + 8; /* Sounds like a fine padding... */ draw_sprite(ctx, &myIcon, 10, (background_sprite.height - myIcon.height) / 2); } else { int h = myIcon.height * 100 / myIcon.width; textOffset = 100 + 8; draw_sprite_scaled(ctx, &myIcon, 10, (background_sprite.height - h) / 2, 100, h); } free(myIcon.bitmap); } } int height = markup_string_height(msg_body->string); markup_draw_string(ctx, 10 + textOffset, (ctx->height - height) / 2, msg_body->string, rgb(255,255,255)); yutani_flip(yctx, win); } int main(int argc, char * argv[]) { /* Make sure we were actually expecting to be run... */ if (argc < 2 || strcmp(argv[1],"--really")) { fprintf(stderr, "%s: Toast notification daemon\n" "\n" " Displays popup notifications from other\n" " applications in the corner of the screen.\n" " You probably don't want to run this directly - it is\n" " started automatically by the session manager.\n", argv[0]); return 1; } /* Daemonize... */ if (!fork()) { /* Connect to display server... */ yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: Failed to connect to compositor.\n", argv[0]); return 1; } /* Open pex endpoint to receive notifications... */ pex_endpoint = pex_bind("toast"); if (!pex_endpoint) { fprintf(stderr, "%s: Failed to establish socket.\n", argv[0]); return 1; } /* Set up our text rendering and sprite contexts... */ markup_text_init(); load_sprite(&background_sprite, "/usr/share/ttk/toast/default.png"); windows = list_create(); garbage = list_create(); int should_exit = 0; while (!should_exit) { int fds[2] = {fileno(yctx->sock),fileno(pex_endpoint)}; int index = fswait2(2,fds,windows->length ? 20 : -1); if (index == 0) { yutani_msg_t * m = yutani_poll(yctx); while (m) { switch (m->type) { case YUTANI_MSG_SESSION_END: should_exit = 1; break; default: break; } free(m); m = yutani_poll_async(yctx); } } else if (index == 1) { pex_packet_t * p = calloc(PACKET_SIZE, 1); pex_listen(pex_endpoint, p); JSON_Value * msg = json_parse((char*)p->data); if (msg) { handle_msg(msg); json_free(msg); } free(p); } if (windows->length) { /* Check all the existing toasts for expired ones... */ struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); foreach(node, windows) { struct ToastNotification * notification = node->value; /* How long has it been since this notification was created? */ struct timespec diff; diff.tv_sec = now.tv_sec - notification->created.tv_sec; diff.tv_nsec = now.tv_nsec - notification->created.tv_nsec; if (diff.tv_nsec < 0) { diff.tv_sec--; diff.tv_nsec += 1000000000L; } if (diff.tv_sec >= notification->duration) { if (notification->window) { yutani_close(yctx, notification->window); notification->window = NULL; } if (diff.tv_sec >= notification->duration + 1) { list_insert(garbage, node); } } } /* Expunge garbage */ if (garbage->length) { while (garbage->length) { node_t * n = list_pop(garbage); node_t * node = n->value; free(n); list_delete(windows, node); free(node->value); free(node); } } /* Figure out if we need to move anything */ if (index == 2) { int index = 0; foreach(node, windows) { struct ToastNotification * notification = node->value; if (notification->window && notification->window->y > PAD_TOP + background_sprite.height * index) { yutani_window_move(yctx, notification->window, notification->window->x, notification->window->y - 4); } index++; } sched_yield(); } } } } return 0; } ================================================ FILE: apps/toggle-abs-mouse.c ================================================ /** * @brief toggle-abs-mouse - Toggle mouse modes * * Set the mouse mode under VirtualBox, VMware, or QEMU to either * relative or absolute via ioctl to the relevant absolute mouse * device driver interface. * * @copyright * 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 */ #include #include #include #include #include int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "%s: argument (relative, absolute, get) expected\n", argv[0]); return 1; } int fd = open("/dev/absmouse",O_WRONLY); if (fd < 0) { /* try vmmouse */ fd = open("/dev/vmmouse",O_WRONLY); if (fd < 0) { fprintf(stderr, "%s: no valid mouse interface found.\n", argv[0]); return 1; } } int flag = 0; if (!strcmp(argv[1],"relative")) { flag = 1; } if (!strcmp(argv[1],"absolute")) { flag = 2; } if (!strcmp(argv[1],"get")) { flag = 3; } if (!flag) { fprintf(stderr, "%s: invalid argument\n", argv[0]); return 1; } int result = ioctl(fd, flag, NULL); if (flag == 3) { if (result == 0) { fprintf(stdout, "relative\n"); } else { fprintf(stdout, "absolute\n"); } return 0; } return result; } ================================================ FILE: apps/top.c ================================================ /** * @brief Show processes sorted by resource usage. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LINE_LEN 4096 enum header_columns { COLUMN_NONE, COLUMN_PID, COLUMN_TID, COLUMN_USER, COLUMN_VSZ, COLUMN_SHM, COLUMN_MEM, COLUMN_CPUA, COLUMN_CPU, COLUMN_S }; static hashmap_t * process_ents = NULL; static int cpu_count = 1; static int sort_column = COLUMN_CPU; static int show_help = 0; static const char * help_text[] = { "q: quit", "w: switch sort column", "h: show this help text", }; enum { FORMATTER_DECIMAL, FORMATTER_PERCENT, FORMATTER_STRING }; enum { SORT_ASC, SORT_DEC }; struct process { int uid, pid, tid, mem, vsz, shm, cpu, cpua; char * user; char * process; char * command_line; char * state; }; struct columns { const char * title; intptr_t member; int formatter; int width; int sort_order; } ColumnDescriptions[] = { [COLUMN_NONE] = {"", 0, 0, 0, 0}, [COLUMN_PID] = {"PID", offsetof(struct process, pid), FORMATTER_DECIMAL, 0, SORT_ASC}, [COLUMN_TID] = {"TID", offsetof(struct process, tid), FORMATTER_DECIMAL, 0, SORT_ASC}, [COLUMN_VSZ] = {"VSZ", offsetof(struct process, vsz), FORMATTER_DECIMAL, 0, SORT_DEC}, [COLUMN_SHM] = {"SHM", offsetof(struct process, shm), FORMATTER_DECIMAL, 0, SORT_DEC}, [COLUMN_MEM] = {"%MEM", offsetof(struct process, mem), FORMATTER_PERCENT, 0, SORT_DEC}, [COLUMN_CPU] = {"%CPU", offsetof(struct process, cpu), FORMATTER_PERCENT, 0, SORT_DEC}, [COLUMN_CPUA] = {"CPUA", offsetof(struct process, cpua), FORMATTER_PERCENT, 0, SORT_DEC}, [COLUMN_USER] = {"USER", offsetof(struct process, user), FORMATTER_STRING, 0, SORT_ASC}, [COLUMN_S] = {"S", offsetof(struct process, state),FORMATTER_STRING, 0, SORT_ASC}, }; static int columns[] = { COLUMN_PID, COLUMN_USER, COLUMN_VSZ, COLUMN_SHM, COLUMN_S, COLUMN_CPU, COLUMN_CPUA, COLUMN_MEM, COLUMN_NONE }; /** * @brief Print a single column to stdout with the appropriate formatter. */ static int print_column(struct process * proc, int column_id) { struct columns * column = &ColumnDescriptions[column_id]; switch (column->formatter) { case FORMATTER_DECIMAL: { int value = *(int*)((char *)proc + column->member); return printf("%*d ", column->width, value); } case FORMATTER_PERCENT: { int value = *(int*)((char *)proc + column->member); if (value >= 1000) { return printf("%*d ", column->width, value / 10); } else { return printf("%*d.%01d ", column->width - 2, value / 10, value % 10); } } case FORMATTER_STRING: { char * value = *(char**)((char *)proc + column->member); return printf("%-*s ", column->width, value); } default: return 0; } } /** * @brief Calculate the size of a formatted column. */ static int size_column(struct process * proc, int column_id) { char garbage[100]; struct columns * column = &ColumnDescriptions[column_id]; switch (column->formatter) { case FORMATTER_DECIMAL: { int value = *(int*)((char *)proc + column->member); return snprintf(garbage, 100, "%d", value); } case FORMATTER_PERCENT: { int value = *(int*)((char *)proc + column->member); if (value >= 1000) { return 3; } else { return snprintf(garbage, 100, "%d.%01d", value / 10, value % 10); } } case FORMATTER_STRING: { char * value = *(char**)((char *)proc + column->member); return strlen(value); } default: return 0; } } /** * @brief Print the column headings. */ void print_header(void) { printf("\033[44;30m"); for (int * c = columns; *c; ++c) { if (*c == sort_column) printf("\033[97m"); printf("%*s ", ColumnDescriptions[*c].width, ColumnDescriptions[*c].title); if (*c == sort_column) printf("\033[30m"); } if (sort_column == COLUMN_NONE) { printf("\033[1;97mCMD\033[30m"); } else { printf("CMD"); } printf("\033[K\033[0m\n"); } /** * @brief Reset column widths to the minimum required to fit their headings. */ void reset_column_widths(void) { for (size_t i = 0; i < sizeof(ColumnDescriptions) / sizeof(*ColumnDescriptions); ++i) { ColumnDescriptions[i].width = strlen(ColumnDescriptions[i].title); } } /** * @brief Print one entry to stdout with the appropriate formatter. * * @p out Process entry to print. * @p width Total available screen width. */ void print_entry(struct process * out, int width) { int used = 0; for (int * c = columns; *c; ++c) { if (*c == sort_column) printf("\033[1m"); used += print_column(out, *c); if (*c == sort_column) printf("\033[0m"); } printf("%.*s\033[K\n", width - used, out->command_line ? out->command_line : out->process); } /** * @brief Given a process, expand any columns that need to be bigger to fit it. */ void update_column_widths(struct process * out) { int len; for (int * c = columns; *c; ++c) { if ((len = size_column(out, *c)) > ColumnDescriptions[*c].width) ColumnDescriptions[*c].width = len; } } /** * @brief Free resources used by a process entry. * * Frees any strings allocated for the process, as well * as the process struct itself. */ void free_entry(struct process * out) { if (out->command_line) free(out->command_line); if (out->process) free(out->process); if (out->user) free(out->user); if (out->state) free(out->state); free(out); } /** * @brief Given a UID, get the username. * * Always returns a string that must be freed. If the uid * could not be found in the passwd database, the uid itself * is formatted as a string for display. */ char * format_username(int uid) { static char tmp[100]; struct passwd * p = getpwuid(uid); if (p) { snprintf(tmp, 100, "%-8s", p->pw_name); } else { snprintf(tmp, 100, "%-8d", uid); } endpwent(); return strdup(tmp); } /** * @brief Find a process from its pid. * * Used for looking up the main thread's process entry when * collecting information for non-main threads, so we can * sum up CPU usage, which is reported in procfs per-thread. */ struct process * process_from_pid(pid_t pid) { return hashmap_get(process_ents, (void*)(uintptr_t)pid); } /** * @brief Collect information for a process from its procfs entry. * * @p dent Directory entry from calling readdir on /proc. * @returns Process information that must be freed by the caller. */ struct process * process_entry(struct dirent *dent) { char tmp[300]; FILE * f; char line[LINE_LEN]; int pid = 0, uid = 0, tgid = 0, mem = 0, shm = 0, vsz = 0, cpu = 0, cpua = 0; char name[100]; char state[10]; sprintf(tmp, "/proc/%s/status", dent->d_name); f = fopen(tmp, "r"); if (!f) { return NULL; } line[0] = 0; while (fgets(line, LINE_LEN, f) != NULL) { char * n = strstr(line,"\n"); if (n) { *n = '\0'; } char * tab = strstr(line,"\t"); if (tab) { *tab = '\0'; tab++; } if (strstr(line, "Pid:") == line) { pid = atoi(tab); } else if (strstr(line, "State:") == line) { strcpy(state, tab); } else if (strstr(line, "Uid:") == line) { uid = atoi(tab); } else if (strstr(line, "Tgid:") == line) { tgid = atoi(tab); } else if (strstr(line, "Name:") == line) { strcpy(name, tab); } else if (strstr(line, "VmSize:") == line) { vsz = atoi(tab); } else if (strstr(line, "RssShmem:") == line) { shm = atoi(tab); } else if (strstr(line, "MemPermille:") == line) { mem = atoi(tab); } else if (strstr(line, "CpuPermille:") == line) { cpu = strtoul(tab, &tab, 10); cpua = cpu; cpua += strtoul(tab, &tab, 10); cpua += strtoul(tab, &tab, 10); cpua += strtoul(tab, &tab, 10); cpua /= 4; } } fclose(f); if (tgid != pid) { /* Add this thread's CPU usage to the parent */ struct process * parent = process_from_pid(tgid); if (parent) { parent->cpu += cpu; parent->cpua += cpua; } return NULL; } struct process * out = malloc(sizeof(struct process)); out->uid = uid; out->pid = tgid; out->tid = pid; out->mem = mem; out->shm = shm; out->vsz = vsz; out->cpu = cpu; out->cpua = cpua; out->process = strdup(name); out->state = strdup(state); out->command_line = NULL; out->user = format_username(out->uid); hashmap_set(process_ents, (void*)(uintptr_t)pid, out); sprintf(tmp, "/proc/%s/cmdline", dent->d_name); f = fopen(tmp, "r"); char foo[1024]; int s = fread(foo, 1, 1024, f); if (s > 0) { out->command_line = malloc(s + 1); memset(out->command_line, 0, s + 1); memcpy(out->command_line, foo, s); for (int i = 0; i < s; ++i) { if (out->command_line[i] == 30) { out->command_line[i] = ' '; } } } fclose(f); update_column_widths(out); return out; } /** * @brief Sort an array of process struct pointers using the * currently selected sort column. */ static int sort_processes(const void * a, const void * b) { struct process * left = *(struct process **)a; struct process * right = *(struct process **)b; struct columns * column = &ColumnDescriptions[sort_column]; if (sort_column == COLUMN_NONE) { return strcmp(left->command_line, right->command_line); } switch (column->formatter) { case FORMATTER_DECIMAL: case FORMATTER_PERCENT: { int a = *(int*)((char *)left + column->member); int b = *(int*)((char *)right + column->member); return (column->sort_order == SORT_ASC) ? (a - b) : (b - a); } case FORMATTER_STRING: { char * a = *(char **)((char *)left + column->member); char * b = *(char **)((char *)right + column->member); return (column->sort_order == SORT_ASC) ? strcmp(a,b) : strcmp(b,a); } default: return 0; } } /** * @brief Collect memory usage information from /proc/meminfo * * @p total (out) Total memory available in KiB * @p used (out) In-use memory in KiB */ static void get_mem_info(int * total, int * used) { FILE * f = fopen("/proc/meminfo", "r"); if (!f) return; int free; char buf[1024] = {0}; fgets(buf, 1024, f); char * a, * b; a = strchr(buf, ' '); a++; b = strchr(a, '\n'); *b = '\0'; *total = atoi(a); fgets(buf, 1024, f); a = strchr(buf, ' '); a++; b = strchr(a, '\n'); *b = '\0'; free = atoi(a); *used = *total - free; fclose(f); } /** * @brief Collect CPU usage information from /proc/idle * * @p cpus (out) Array of CPU usage in permilles. */ static void get_cpu_info(int cpus[]) { FILE * f = fopen("/proc/idle","r"); char buf[4096]; fread(buf, 4096, 1, f); char * buffer = buf; for (int i = 0; i < cpu_count; ++i) { char * b = strchr(buffer, ':'); b++; cpus[i] = 1000 - atoi(b); if (cpus[i] < 0) cpus[i] = 0; buffer = strchr(b, '\n'); } fclose(f); } /** * @brief Obtain information on how much system memory is * being used for tmpfs blocks. */ static void get_tmpfs_info(size_t * size) { FILE * f = fopen("/proc/tmpfs", "r"); if (!f) return; char buf[1024] = {0}; fread(buf,1,1024,f); fclose(f); /* Should probably be looking for UsedBlocks: and advancing from there... */ char *b = strstr(buf, ":"); if (!b) return; b += 2; *size = strtoul(b,NULL,10) * 4; /* Expressed in pages, so * 4 for kilobytes */ } static int fill_colors[] = { 1, 3, 4, 5, 6 }; /** * @brief Display a progress-bar-style usage meter. * * @p title Label to apply to the meter, shown on left. * @p label Label to show inside of the meter, show on the right. * @p width Available width to display the meter in, including title and frame. * @p count Number of values to display. * @p filled Values to stack in the meter. * @p maximum Maximum value of the meter. */ static void print_meter(const char * title, const char * label, int width, int count, int filled[], int maximum) { int available = width - strlen(title) - 4; int remaining = available; int fillSlots = 0; /* Count total fill slots */ for (int i = 0; i < count; ++i) { filled[i] = filled[i] * available / maximum; if (filled[i] < 0) filled[i] = 0; if (filled[i] > remaining) filled[i] = remaining; fillSlots += filled[i]; remaining = available - fillSlots; } int emptSlots = available - fillSlots; printf("\033[1m%s [", title); char * fill = malloc(available + 1); size_t j = 0; for (int i = 0; i < fillSlots; ++i, j++) fill[j] = '|'; for (int i = 0; i < emptSlots; ++i, j++) fill[j] = ' '; size_t l = strlen(label); if (available > (int)l) { sprintf(fill + available - l, "%s", label); } j = 0; for (int c = 0; c < count; ++c) { printf("\033[0;9%dm", fill_colors[c % (sizeof(fill_colors) / sizeof(int))]); for (int i = 0; i < filled[c]; ++i, j++) printf("%c",fill[j]); } printf("\033[90m"); for (int i = 0; i < emptSlots; ++i, j++) printf("%c",fill[j]); printf("\033[0;1m]\033[0m "); free(fill); } /** * @brief Switch sorting to the next column. */ static void next_sort_order(void) { size_t column_count = sizeof(columns)/sizeof(*columns); for (size_t i = 0; i < column_count; ++i) { if (columns[i] == sort_column) { sort_column = columns[(i + 1) % column_count]; return; } } } /** * @brief Switch sorting to the previous column. */ static void prev_sort_order(void) { size_t column_count = sizeof(columns)/sizeof(*columns); for (size_t i = 0; i < column_count; ++i) { if (columns[i] == sort_column) { sort_column = columns[(i + column_count - 1) % column_count]; return; } } } /** * @brief Collect information on running processes. */ static struct process ** read_processes(size_t * count) { /* Set minimum column widths to titles */ reset_column_widths(); /* Read the entries in the directory */ list_t * ents_list = list_create(); process_ents = hashmap_create_int(10); /* Scan /proc entries */ DIR * dirp = opendir("/proc"); struct dirent * dent = readdir(dirp); while (dent != NULL) { if (dent->d_name[0] >= '0' && dent->d_name[0] <= '9') { struct process * p = process_entry(dent); if (p) { list_insert(ents_list, (void *)p); } } dent = readdir(dirp); } closedir(dirp); hashmap_free(process_ents); free(process_ents); /* Turn list into an array */ *count = ents_list->length; struct process ** processList = malloc(sizeof(struct process*) * *count); size_t ent = 0; while (ents_list->length) { node_t * node = list_pop(ents_list); processList[ent] = node->value; free(node); ent++; } free(ents_list); /* Sort processes with the current sort column */ qsort(processList, *count, sizeof(struct process*), sort_processes); return processList; } /** * @brief Gather system information and print one sample. */ static int do_once(void) { size_t count; struct process ** processList = read_processes(&count); /* Gather total memory usage /proc/meminfo */ int mem_total = 0, mem_used = 0; get_mem_info(&mem_total, &mem_used); size_t mem_tmpfs = 0; get_tmpfs_info(&mem_tmpfs); /* Gather per-CPU usage from /proc/idle */ int cpus[32]; get_cpu_info(cpus); /* Gather screen size */ struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); /* Figure out how we're going to lay out widgets */ int top_rows = 1 + cpu_count; int meter_width = w.ws_col / 2; int current_row = 0; int left_side = 1; /* Generate info rows */ int info_width = w.ws_col - meter_width; char info_row[5][100] = {0}; int info_rows = 0; if (info_width <= 30) { meter_width = w.ws_col; info_width = 0; } else { #define T_T "\033[94m" #define T_C "\033[0;1m" #define T_E "\033[0m" if (top_rows >= 1) { info_rows = 1; char tmp[256] = {0}; gethostname(tmp, 255); snprintf(info_row[0], 99, T_T "Hostname: " T_C "%.*s" T_E, info_width - 10, tmp); } if (top_rows >= 2) { info_rows = 2; char tmp[256] = {0}; char * format = "%a %b %d %T %Y %Z"; struct tm * timeinfo; struct timeval now; gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); strftime(tmp,255,format,timeinfo); snprintf(info_row[1], 99, T_T "Time: " T_C "%.*s" T_E, info_width - 6, tmp); } if (top_rows >= 3) { info_rows = 3; snprintf(info_row[2], 99, T_T "Tasks: " T_C "%lu" T_E, count); } } /* Reset cursor to upper left */ printf("\033[H"); /* Display CPU usage widgets */ for (int cpu = 0; cpu < cpu_count; ++cpu) { char name[20], usage[30]; sprintf(name, "%3d", cpu + 1); sprintf(usage, "%d.%01d%%", cpus[cpu] / 10, cpus[cpu] % 10); print_meter(name, usage, left_side ? meter_width : info_width, 1, (int[]){cpus[cpu]}, 1000); if (current_row < info_rows) { printf("%s\033[K\n", info_row[current_row]); current_row++; } else if (info_rows) { if (left_side) { left_side = 0; } else { left_side = 1; current_row++; } } else { current_row++; } } /* Display memory usage widget */ char memUsed[30]; sprintf(memUsed, "%dM/%dM", mem_used / 1024, mem_total / 1024); print_meter("Mem", memUsed, left_side ? meter_width : info_width, 2, (int[]){mem_used-mem_tmpfs,mem_tmpfs}, mem_total); if (left_side && current_row < info_rows) { printf("%s", info_row[current_row]); } current_row++; printf("\033[K\n"); /* Show column headers */ print_header(); int i = 0; size_t ent = 0; /* Print entries, or help text lines */ if (show_help) { for (ent = 0; ent < sizeof(help_text) / sizeof(*help_text); ++i, ++ent) { if (i >= w.ws_row - current_row - 2) break; printf("%*s\033[K\n", (int)w.ws_col, help_text[ent]); } } else { for (ent = 0; ent < count; ++i, ++ent) { if (i >= w.ws_row - current_row - 2) break; print_entry(processList[ent], w.ws_col); } } /* Clear remaining screen lines */ for (; i < w.ws_row - current_row - 2; ++i) { printf("\033[K\n"); } /* Clean up process data from this round */ for (ent = 0; ent < count; ++ent) { free_entry(processList[ent]); } free(processList); /* Wait for command or 2 seconds for next refresh... */ struct pollfd fds[1]; fds[0].fd = STDIN_FILENO; fds[0].events = POLLIN; int ret = poll(fds,1,2000); if (ret > 0 && fds[0].revents & POLLIN) { int c = fgetc(stdin); if (c == 'q') return 0; if (c == 'w') next_sort_order(); if (c == 'W') prev_sort_order(); if (c == 'h') show_help = !show_help; } return 1; } /** * @brief Gather and print process information once. * * Prints only process information. */ static int do_log(void) { size_t count; struct process ** processList = read_processes(&count); int mem_total = 0, mem_used = 0; get_mem_info(&mem_total, &mem_used); size_t mem_tmpfs = 0; get_tmpfs_info(&mem_tmpfs); /* Gather per-CPU usage from /proc/idle */ int cpus[32]; get_cpu_info(cpus); /* Hostname */ { char tmp[256] = {0}; gethostname(tmp, 255); printf("Hostname: %s\n", tmp); } /* Current time */ { char tmp[256] = {0}; char * format = "%a %b %d %T %Y %Z"; struct tm * timeinfo; struct timeval now; gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); strftime(tmp,255,format,timeinfo); printf("Time: %s\n", tmp); } /* Task count and memory usage on one line */ printf("Tasks: %-7lu Mem: %dM/%dM (%ldM tmpfs)\n", count, mem_used / 1024, mem_total / 1024, mem_tmpfs/1024); /* CPU usage; all on one line; formatted best for small counts and <100% usage */ for (int cpu = 0; cpu < cpu_count; ++cpu) { printf("CPU%2d:%3d.%01d%%%s", cpu + 1, cpus[cpu] / 10, cpus[cpu] % 10, (cpu + 1 == cpu_count) ? "\n" : " "); } /* Blank line to separator process table */ printf("\n"); for (int * c = columns; *c; ++c) { printf("%*s ", ColumnDescriptions[*c].width, ColumnDescriptions[*c].title); } printf("CMD\n"); for (size_t ent = 0; ent < count; ++ent) { struct process * out = processList[ent]; for (int * c = columns; *c; ++c) { print_column(out, *c); } printf("%s\n", out->command_line ? out->command_line : out->process); free(out); } free(processList); return 0; } struct termios old; void get_initial_termios(void) { tcgetattr(STDOUT_FILENO, &old); } /** * @brief Switch to alt screen, turn on raw input. */ void set_unbuffered(void) { struct termios new = old; new.c_iflag &= (~ICRNL) & (~IXON); new.c_lflag &= (~ICANON) & (~ECHO); tcsetattr(STDOUT_FILENO, TCSAFLUSH, &new); printf("\033[?1049h\033[?25l\033[H\033[2J"); } /** * @brief Switch to main screen, re-enable buffering. */ void set_buffered(void) { printf("\033[H\033[2J\033[?25h\033[?1049l"); tcsetattr(STDOUT_FILENO, TCSAFLUSH, &old); } void SIGWINCH_handler(int sig) { (void)sig; signal(SIGWINCH, SIGWINCH_handler); } int main (int argc, char * argv[]) { /* Assume CPU count doesn't change... */ cpu_count = sysfunc(TOARU_SYS_FUNC_NPROC, NULL); /* * If we are writing to a regular file, use the simple log format and * only output one sample of data before exiting. */ if (!isatty(STDOUT_FILENO)) { return do_log(); } /* Initialize terminal for alt screen */ get_initial_termios(); set_unbuffered(); signal(SIGWINCH, SIGWINCH_handler); /* Loop */ while (do_once()); /* Reset terminal */ set_buffered(); return 0; } ================================================ FILE: apps/touch.c ================================================ /** * @brief touch - Create or update file timestamps * * @copyright * 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 */ #include #include #include int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "%s: argument expected\n", argv[0]); return 1; } int out = 0; for (int i = 1; i < argc; ++i) { FILE * f = fopen(argv[i], "a"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); out = 1; continue; } fclose(f); } return out; } ================================================ FILE: apps/true.c ================================================ /** * @brief true - Return success code * * @copyright * 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 */ int main() { return 0; } ================================================ FILE: apps/tty.c ================================================ /** * @brief tty - print terminal name * * @copyright * 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 */ #include #include int main(int argc, char * argv[]) { if (!isatty(STDIN_FILENO)) { fprintf(stdout, "not a tty\n"); return 1; } fprintf(stdout,"%s\n",ttyname(STDIN_FILENO)); return 0; } ================================================ FILE: apps/ttysize.c ================================================ /** * @brief ttysize - Magically divine terminal size * * This is called by getty to determine the size of foreign * terminals, such as ones attached over serial. * * It works by placing the cursor in the lower right of the * screen and requesting its position. Note that typing things * while this happens can cause problems. Maybe we can flush * stdin before doing this to try to avoid any conflicting data? * * @copyright * 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 */ #include #include #include #include #include #include #include static struct termios old; static void set_unbuffered() { tcgetattr(fileno(stdin), &old); struct termios new = old; new.c_lflag &= (~ICANON & ~ECHO); tcsetattr(fileno(stdin), TCSAFLUSH, &new); } static void set_buffered() { tcsetattr(fileno(stdin), TCSAFLUSH, &old); } static int getc_timeout(int timeout) { int fds[1] = {STDIN_FILENO}; int index = fswait2(1,fds,timeout); if (index == 0) { unsigned char buf[1]; read(STDIN_FILENO, buf, 1); return buf[0]; } else { return -1; } } static void divine_size(int * width, int * height) { set_unbuffered(); *width = 80; *height = 24; fprintf(stderr, "\033[s\033[1000;1000H\033[6n\033[u"); fflush(stderr); char buf[1024] = {0}; size_t i = 0; while (1) { int c = getc_timeout(200); if (c == 'R') break; if (c == -1) goto _done; if (c == '\033') continue; if (c == '[') continue; buf[i++] = c; } char * s = strstr(buf, ";"); if (s) { *(s++) = '\0'; *height = atoi(buf); *width = atoi(s); } _done: fflush(stderr); set_buffered(); } int main(int argc, char * argv[]) { int width, height; int opt; int quiet = 0; while ((opt = getopt(argc, argv, "q")) != -1) { switch (opt) { case 'q': quiet = 1; break; } } if (optind + 2 == argc) { width = atoi(argv[optind]); height = atoi(argv[optind+1]); } else { divine_size(&width, &height); } struct winsize w; w.ws_col = width; w.ws_row = height; w.ws_xpixel = 0; w.ws_ypixel = 0; ioctl(0, TIOCSWINSZ, &w); if (!quiet) { fprintf(stderr, "%dx%d\n", width, height); } return 0; } ================================================ FILE: apps/tutorial.c ================================================ /** * @brief A recreation of the original wizard.py, explaining * the functionality of ToaruOS and how to use the WM. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2019-2021 K. Lange */ #include #include #include #include #include #include #include #include #define BUTTON_HEIGHT 28 #define BUTTON_WIDTH 86 #define BUTTON_PADDING 14 static yutani_t * yctx; static yutani_window_t * window = NULL; static gfx_context_t * ctx = NULL; static yutani_window_t * background = NULL; static gfx_context_t * background_ctx = NULL; static int32_t width = 640; static int32_t height = 480; static char * title_str; static char * body_text[20] = {NULL}; static sprite_t * icon = NULL; static sprite_t terminal; static sprite_t folder; static sprite_t package; static sprite_t logo; static sprite_t mouse_drag; static sprite_t cdicon; static struct TT_Font * _tt_font_thin = NULL; static struct TT_Font * _tt_font_bold = NULL; static int page = 0; static int center(int x, int width) { return (width - x) / 2; } static void draw_string(int y, const char * string, struct TT_Font * font, uint32_t color, int size) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); tt_set_size(font, size); tt_draw_string(ctx, font, bounds.left_width + center(tt_string_width(font, string), width), bounds.top_height + 30 + y + size, string, color); } struct TTKButton _next_button = {0}; struct TTKButton _prev_button = {0}; static int _prev_enabled = 0; static void redraw(void) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); draw_fill(ctx, rgb(204,204,204)); int offset = 0; if (icon) { offset = icon->height; draw_sprite(ctx, icon, bounds.left_width + center(icon->width, width), bounds.top_height + 15); } for (char ** copy_str = body_text; *copy_str; ++copy_str) { if (**copy_str == '-') { offset += 10; } else if (**copy_str == '%') { draw_string(offset, *copy_str+1, _tt_font_thin, rgb(0,0,255), 13); offset += 20; } else if (**copy_str == '#') { draw_string(offset, *copy_str+1, _tt_font_bold, rgb(0,0,0), 20); offset += 20; } else { draw_string(offset, *copy_str, _tt_font_thin, rgb(0,0,0), 13); offset += 20; } } ttk_button_draw(ctx, &_next_button); if (!_prev_enabled) { int _tmp = _prev_button.hilight; _prev_button.hilight = (1 << 8); ttk_button_draw(ctx, &_prev_button); _prev_button.hilight = _tmp; } else { ttk_button_draw(ctx, &_prev_button); } render_decorations(window, ctx, title_str); flip(ctx); yutani_flip(yctx, window); } static void reset_background(void) { draw_fill(background_ctx, rgba(0,0,0,200)); } static void invert_background_alpha(void) { for (unsigned int y = 0; y < background->height; ++y) { for (unsigned int x = 0; x < background->width; ++x) { uint32_t c = GFX(background_ctx, x, y); int r = _RED(c); int g = _GRE(c); int b = _BLU(c); int a = _ALP(c); a = 255 - a; GFX(background_ctx, x, y) = rgba(r,g,b,a); } } } static void circle(int x, int y, int r) { draw_fill(background_ctx, rgba(0,0,0,255-200)); draw_rounded_rectangle(background_ctx, x - r, y - r, r * 2, r * 2, r, rgb(0,0,0)); invert_background_alpha(); } static char * randomly_select_begging(void) { char * options[] = { "You can help support ToaruOS by donating:", "Your donation helps us continue developing ToaruOS:", "You can sponsor ToaruOS development on Github:", "Please give me money:", }; return options[rand() % (sizeof(options) / sizeof(*options))]; } static void load_page(int page) { int i = 0; _prev_enabled = 1; _next_button.title = "Next"; reset_background(); switch (page) { case 0: _prev_enabled = 0; title_str = "Welcome to ToaruOS!"; icon = &logo; body_text[i++] = "#Welcome to ToaruOS!"; body_text[i++] = ""; body_text[i++] = "This tutorial will guide you through the features of the operating"; body_text[i++] = "system, as well as give you a feel for the UI and design principles."; body_text[i++] = ""; body_text[i++] = "When you're ready to continue, press \"Next\"."; body_text[i++] = ""; body_text[i++] = "%https://github.com/klange/toaruos - https://toaruos.org"; body_text[i++] = ""; body_text[i++] = "ToaruOS is free software, released under the terms of the"; body_text[i++] = "NCSA/University of Illinois license."; body_text[i++] = ""; body_text[i++] = randomly_select_begging(); body_text[i++] = "%https://github.com/sponsors/klange"; body_text[i++] = NULL; break; case 1: icon = &logo; body_text[i++] = "ToaruOS is a hobby project. The entire contents of this Live CD"; body_text[i++] = "were written by the ToaruOS development team over the course of"; body_text[i++] = "many years, but that development team is very small. Some features"; body_text[i++] = "may be missing, incomplete, or unstable. Contributions in the form"; body_text[i++] = "of bug reports and new ports are welcome. You can join our community"; body_text[i++] = "through IRC by joining the #toaruos channel on Libera.chat."; body_text[i++] = ""; body_text[i++] = "You can help support ToaruOS by donating:"; body_text[i++] = "%https://github.com/sponsors/klange"; body_text[i++] = NULL; break; case 2: icon = &cdicon; body_text[i++] = "This is a \"live CD\". You can make changes to the file system, including"; body_text[i++] = "installing applications, but those changes will not persist between reboots."; body_text[i++] = ""; body_text[i++] = "If you need to enter a password, such as for the \"sudo\" utility or when"; body_text[i++] = "using the package manager, the default user account is \"local\" with the"; body_text[i++] = "password \"local\". There is also a \"guest\" account available with limited"; body_text[i++] = "privileges (password \"guest\"), and a \"root\" account (password \"toor\")."; body_text[i++] = NULL; break; case 3: icon = &folder; circle(70, 90, 60); body_text[i++] = "You can explore the file system using the File Browser."; body_text[i++] = "Application shortcuts on the desktop, as well as files in the file browser"; body_text[i++] = "are opened with a double click. You can also find more applications in"; body_text[i++] = "the Applications menu in the upper left."; body_text[i++] = NULL; break; case 4: icon = &terminal; circle(70, 170, 60); body_text[i++] = "ToaruOS aims to provide a Unix-like environment. You can find"; body_text[i++] = "familiar command-line tools by opening a terminal. ToaruOS's"; body_text[i++] = "shell provides command history, syntax highlighting, and tab"; body_text[i++] = "completion. There is also a growing suite of Unix utilities"; body_text[i++] = "and a featureful text editor (bim)."; body_text[i++] = NULL; break; case 5: icon = &package; circle(70, 250, 60); body_text[i++] = "Many third-party software packages have been ported to ToaruOS"; body_text[i++] = "and are available from our package repositories. You can use the"; body_text[i++] = "Package Manager to install GCC, Doom, Quake, and more."; body_text[i++] = NULL; break; case 6: icon = &mouse_drag; body_text[i++] = "With ToaruOS's window manager, you can drag most windows by"; body_text[i++] = "holding Alt, or by using the title bar. You can also resize"; body_text[i++] = "windows by dragging from their edges or using Alt + Middle Click."; body_text[i++] = ""; body_text[i++] = "Note that if you are running ToaruOS in a virtual machine, your"; body_text[i++] = "host operating system configuration may conflict with modifier"; body_text[i++] = "keys in ToaruOS."; body_text[i++] = NULL; break; case 7: icon = NULL; _next_button.title = "Exit"; body_text[i++] = "#That's it!"; body_text[i++] = ""; body_text[i++] = "The tutorial is over."; body_text[i++] = ""; body_text[i++] = "Press \"Exit\" to close this window and start exploring ToaruOS."; body_text[i++] = NULL; break; default: exit(0); break; } flip(background_ctx); yutani_flip(yctx, background); } int in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) { if (me->new_y >= button->y && me->new_y < button->y + button->height) { if (me->new_x >= button->x && me->new_x < button->x + button->width) { return 1; } } return 0; } void setup_buttons(void) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); _next_button.title = "Next"; _next_button.width = BUTTON_WIDTH; _next_button.height = BUTTON_HEIGHT; _next_button.x = ctx->width - bounds.right_width - BUTTON_WIDTH - BUTTON_PADDING; _next_button.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING; _prev_button.title = "Back"; _prev_button.width = BUTTON_WIDTH; _prev_button.height = BUTTON_HEIGHT; _prev_button.x = ctx->width - bounds.right_width - BUTTON_WIDTH * 2 - BUTTON_PADDING * 2; _prev_button.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING; } static void update_size(int w, int h) { struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); width = w - bounds.width; height = h - bounds.height; } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, window, w, h); reinit_graphics_yutani(ctx, window); update_size(w, h); setup_buttons(); redraw(); yutani_window_resize_done(yctx, window); } void resize_finish_bg(int w, int h) { yutani_window_resize_accept(yctx, background, w, h); reinit_graphics_yutani(background_ctx, background); load_page(page); yutani_window_resize_done(yctx, background); } void set_hilight(struct TTKButton * button, int hilight) { if (!button && (_next_button.hilight || _prev_button.hilight)) { _next_button.hilight = 0; _prev_button.hilight = 0; redraw(); } else if (button && (button->hilight != hilight)) { _next_button.hilight = 0; _prev_button.hilight = 0; button->hilight = hilight; redraw(); } } int main(int argc, char * argv[]) { srand(time(NULL)); 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(); _tt_font_thin = tt_font_from_shm("sans-serif"); _tt_font_bold = tt_font_from_shm("sans-serif.bold"); background = yutani_window_create_flags(yctx, yctx->display_width, yctx->display_height, YUTANI_WINDOW_FLAG_DISALLOW_RESIZE | YUTANI_WINDOW_FLAG_DISALLOW_DRAG | YUTANI_WINDOW_FLAG_ALT_ANIMATION | YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS); yutani_window_move(yctx, background, 0, 0); yutani_window_update_shape(yctx, background, 2); background_ctx = init_graphics_yutani_double_buffer(background); reset_background(); flip(background_ctx); yutani_flip(yctx, background); update_size(width, height); struct decor_bounds bounds; decor_get_bounds(NULL, &bounds); window = yutani_window_create(yctx, width + bounds.width, height + bounds.height); window->decorator_flags |= DECOR_FLAG_NO_MAXIMIZE; req_center_x = yctx->display_width / 2; req_center_y = yctx->display_height / 2; yutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2); /* Load icons */ load_sprite(&logo, "/usr/share/logo_login.png"); load_sprite(&terminal, "/usr/share/icons/48/utilities-terminal.png"); load_sprite(&folder, "/usr/share/icons/48/folder.png"); load_sprite(&package, "/usr/share/icons/48/package.png"); load_sprite(&mouse_drag, "/usr/share/cursor/drag.png"); load_sprite(&cdicon, "/usr/share/icons/48/cd.png"); load_page(0); yutani_window_advertise_icon(yctx, window, title_str, "star"); ctx = init_graphics_yutani_double_buffer(window); setup_buttons(); redraw(); struct TTKButton * _down_button = NULL; int playing = 1; int status = 0; 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 == '\n') { page++; load_page(page); redraw(); } else if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == KEY_ESCAPE) { playing = 0; status = 2; } } 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*)(uintptr_t)wf->wid); if (wf->wid == background->wid) { yutani_focus_window(yctx, window->wid); } else if (win) { win->focused = wf->focused; redraw(); } } break; case YUTANI_MSG_WELCOME: yutani_window_resize_offer(yctx, background, yctx->display_width, yctx->display_height); req_center_x = yctx->display_width / 2; req_center_y = yctx->display_height / 2; yutani_window_move(yctx, window, req_center_x - window->width / 2, req_center_y - window->height / 2); break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; if (wr->wid == window->wid) { resize_finish(wr->width, wr->height); } else if (wr->wid == background->wid) { resize_finish_bg(wr->width, wr->height); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; if (me->wid == window->wid) { int result = decor_handle_event(yctx, m); switch (result) { case DECOR_CLOSE: playing = 0; status = 2; 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; } struct decor_bounds bounds; decor_get_bounds(window, &bounds); if (me->new_y > bounds.top_height) { if (me->command == YUTANI_MOUSE_EVENT_DOWN) { if (in_button(&_next_button, me)) { set_hilight(&_next_button, 2); _down_button = &_next_button; } else if (in_button(&_prev_button, me)) { set_hilight(&_prev_button, 2); _down_button = &_prev_button; } } else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) { if (_down_button) { if (in_button(_down_button, me)) { if (_down_button == &_prev_button) { if (page > 0) { page--; load_page(page); } } else if (_down_button == &_next_button) { page++; load_page(page); } _down_button->hilight = 0; } } _down_button = NULL; } if (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { if (in_button(&_next_button, me)) { set_hilight(&_next_button, 1); } else if (in_button(&_prev_button, me)) { set_hilight(&_prev_button, 1); } else { set_hilight(NULL,0); } } else if (_down_button) { if (in_button(_down_button, me)) { set_hilight(_down_button, 2); } else { set_hilight(NULL, 0); } } } } } break; case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: playing = 0; status = 2; break; default: break; } free(m); m = yutani_poll_async(yctx); } } yutani_close(yctx, window); return status; } ================================================ FILE: apps/uname.c ================================================ /** * @brief uname - Print kernel version information * * Supports all the usual options (a,s,n,r,v,m,o) * * Note that o is hardcoded, which is also the situation in * the coreutils implementation, so I don't see that being * a problem. If you want to build this uname for Linux or * something... you'll have to change that. * * @copyright * 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 */ #include #include #include #include #define FLAG_SYSNAME 0x01 #define FLAG_NODENAME 0x02 #define FLAG_RELEASE 0x04 #define FLAG_VERSION 0x08 #define FLAG_MACHINE 0x10 #define FLAG_OSNAME 0x20 #define FLAG_ALL (FLAG_SYSNAME|FLAG_NODENAME|FLAG_RELEASE|FLAG_VERSION|FLAG_MACHINE|FLAG_OSNAME) #define _ITALIC "\033[3m" #define _END "\033[0m\n" void show_usage(int argc, char * argv[]) { fprintf(stderr, "uname - Print system version information.\n" "\n" "usage: %s [-asnrvmop]\n" "\n" " -a " _ITALIC "Print the standard uname string we all love" _END " -s " _ITALIC "Print kernel name" _END " -n " _ITALIC "Print system name" _END " -r " _ITALIC "Print kernel version number" _END " -v " _ITALIC "Print the extra kernel version information" _END " -m " _ITALIC "Print the architecture name" _END " -o " _ITALIC "Print operating system name" _END " -p " _ITALIC "Alias to -m" _END "\n", argv[0]); exit(1); } int main(int argc, char * argv[]) { struct utsname u; int flags = 0; int space = 0; for (int i = 1; i < argc; ++i) { if (argv[i][0] == '-') { char *c = &argv[i][1]; while (*c) { switch (*c) { case 'a': flags |= FLAG_ALL; break; case 's': flags |= FLAG_SYSNAME; break; case 'n': flags |= FLAG_NODENAME; break; case 'r': flags |= FLAG_RELEASE; break; case 'v': flags |= FLAG_VERSION; break; case 'm': case 'p': flags |= FLAG_MACHINE; break; case 'o': flags |= FLAG_OSNAME; break; case 'h': default: show_usage(argc, argv); break; } c++; } } } uname(&u); if (!flags) { /* By default, we just print the kernel name */ flags = FLAG_SYSNAME; } if (flags & FLAG_SYSNAME) { if (space++) printf(" "); printf("%s", u.sysname); } if (flags & FLAG_NODENAME) { if (space++) printf(" "); printf("%s", u.nodename); } if (flags & FLAG_RELEASE) { if (space++) printf(" "); printf("%s", u.release); } if (flags & FLAG_VERSION) { if (space++) printf(" "); printf("%s", u.version); } if (flags & FLAG_MACHINE) { if (space++) printf(" "); printf("%s", u.machine); } if (flags & FLAG_OSNAME) { if (space++) printf(" "); printf("%s", "ToaruOS"); } printf("\n"); return 0; } ================================================ FILE: apps/ununicode.h ================================================ /* Commonly used by a few different things. */ static uint32_t ununicode(uint32_t c) { switch (c) { case L'☺': return 1; case L'☻': return 2; case L'♥': return 3; case L'♦': return 4; case L'♣': return 5; case L'♠': return 6; case L'•': return 7; case L'◘': return 8; case L'○': return 9; case L'◙': return 10; case L'♂': return 11; case L'♀': return 12; case L'♪': return 13; case L'♫': return 14; case L'☼': return 15; case L'►': return 16; case L'◄': return 17; case L'↕': return 18; case L'‼': return 19; case L'¶': return 20; case L'§': return 21; case L'▬': return 22; case L'↨': return 23; case L'↑': return 24; case L'↓': return 25; case L'→': return 26; case L'←': return 27; case L'∟': return 28; case L'↔': return 29; case L'▲': return 30; case L'▼': return 31; /* ASCII text */ case L'⌂': return 127; case L'Ç': return 128; case L'ü': return 129; case L'é': return 130; case L'â': return 131; case L'ä': return 132; case L'à': return 133; case L'å': return 134; case L'ç': return 135; case L'ê': return 136; case L'ë': return 137; case L'è': return 138; case L'ï': return 139; case L'î': return 140; case L'ì': return 141; case L'Ä': return 142; case L'Å': return 143; case L'É': return 144; case L'æ': return 145; case L'Æ': return 146; case L'ô': return 147; case L'ö': return 148; case L'ò': return 149; case L'û': return 150; case L'ù': return 151; case L'ÿ': return 152; case L'Ö': return 153; case L'Ü': return 154; case L'¢': return 155; case L'£': return 156; case L'¥': return 157; case L'₧': return 158; case L'ƒ': return 159; case L'á': return 160; case L'í': return 161; case L'ó': return 162; case L'ú': return 163; case L'ñ': return 164; case L'Ñ': return 165; case L'ª': return 166; case L'º': return 167; case L'¿': return 168; case L'⌐': return 169; case L'¬': return 170; case L'½': return 171; case L'¼': return 172; case L'¡': return 173; case L'«': return 174; case L'»': return 175; case L'░': return 176; case L'▒': return 177; case L'▓': return 178; case L'│': return 179; case L'┤': return 180; case L'╡': return 181; case L'╢': return 182; case L'╖': return 183; case L'╕': return 184; case L'╣': return 185; case L'║': return 186; case L'╗': return 187; case L'╝': return 188; case L'╜': return 189; case L'╛': return 190; case L'┐': return 191; case L'└': return 192; case L'┴': return 193; case L'┬': return 194; case L'├': return 195; case L'─': return 196; case L'┼': return 197; case L'╞': return 198; case L'╟': return 199; case L'╚': return 200; case L'╔': return 201; case L'╩': return 202; case L'╦': return 203; case L'╠': return 204; case L'═': return 205; case L'╬': return 206; case L'╧': return 207; case L'╨': return 208; case L'╤': return 209; case L'╥': return 210; case L'╙': return 211; case L'╘': return 212; case L'╒': return 213; case L'╓': return 214; case L'╫': return 215; case L'╪': return 216; case L'┘': return 217; case L'┌': return 218; case L'█': return 219; case L'▄': return 220; case L'▌': return 221; case L'▐': return 222; case L'▀': return 223; case L'α': return 224; case L'ß': return 225; case L'Γ': return 226; case L'π': return 227; case L'Σ': return 228; case L'σ': return 229; case L'µ': return 230; case L'τ': return 231; case L'Φ': return 232; case L'Θ': return 233; case L'Ω': return 234; case L'δ': return 235; case L'∞': return 236; case L'φ': return 237; case L'ε': return 238; case L'∩': return 239; case L'≡': return 240; case L'±': return 241; case L'≥': return 242; case L'≤': return 243; case L'⌠': return 244; case L'⌡': return 245; case L'÷': return 246; case L'≈': return 247; case L'°': return 248; case L'∙': return 249; case L'·': return 250; case L'√': return 251; case L'ⁿ': return 252; case L'²': return 253; case L'■': return 254; } return 4; } ================================================ FILE: apps/upload.krk ================================================ #!/bin/kuroko ''' Test tool to send file contents over a socket. $ upload.krk file address:port ''' import socket import fileio import kuroko def usage(): print(f'''usage: {kuroko.argv[0]} file address:port Sends files over a TCP socket to a remote address. ''', file=fileio.stderr) return 1 if __name__ == '__main__': if len(kuroko.argv) < 3: return usage() let path = kuroko.argv[1] let endpoint = kuroko.argv[2] if ':' not in endpoint: return usage() let x = endpoint.split(':') endpoint = (x[0], int(x[1])) let b with fileio.open(path,'rb') as f: b = f.read() print("Uploading",len(b),"byte(s) to",endpoint) let s = socket.socket() s.connect(endpoint) print("Connected...") s.send(b) print("Done.") ================================================ FILE: apps/uptime.c ================================================ /** * @brief Print system uptime * * @copyright * 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 */ #include #include #include #include #include #include void print_time(void) { struct timeval now; struct tm * timeinfo; char clocktime[10]; gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); strftime(clocktime, 80, "%H:%M:%S", timeinfo); printf(" %s ", clocktime); } #define MINUTE (60) #define HOUR (60 * MINUTE) #define DAY (24 * HOUR) void print_seconds(int seconds) { if (seconds > DAY) { int days = seconds / DAY; seconds -= DAY * days; printf("%d day%s, ", days, days != 1 ? "s" : ""); } if (seconds > HOUR) { int hours = seconds / HOUR; seconds -= HOUR * hours; int minutes = seconds / MINUTE; printf("%2d:%02d", hours, minutes); return; } else if (seconds > MINUTE) { int minutes = seconds / MINUTE; printf("%d minute%s, ", minutes, minutes != 1 ? "s" : ""); seconds -= MINUTE * minutes; } printf("%2d second%s", seconds, seconds != 1 ? "s" : ""); } void print_uptime(void) { FILE * f = fopen("/proc/uptime", "r"); if (!f) return; int seconds; char buf[1024] = {0}; fgets(buf, 1024, f); char * dot = strchr(buf, '.'); *dot = '\0'; dot++; dot[3] = '\0'; seconds = atoi(buf); printf("up "); print_seconds(seconds); } void show_usage(int argc, char * argv[]) { printf( "uptime - display system uptime information\n" "\n" "usage: %s [-p]\n" "\n" " -p \033[3mshow just the uptime info\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); } int main(int argc, char * argv[]) { int just_pretty_uptime = 0; int opt; while ((opt = getopt(argc, argv, "?p")) != -1 ) { switch (opt) { case 'p': just_pretty_uptime = 1; break; case '?': show_usage(argc, argv); return 0; } } if (!just_pretty_uptime) print_time(); print_uptime(); printf("\n"); return 0; } ================================================ FILE: apps/vga-palette.h ================================================ /** * @brief VGA palette conversion * * Converts 256-color index values to closest matching 16-color * value for the VGA terminal. Note that values here are terminal * color codes, not the VGA color codes - the terminal converts * them to VGA color codes later. This was automatically generated * from a script, but I don't know where that script went. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2018 K. Lange */ #define PALETTE_COLORS 256 uint32_t vga_colors[PALETTE_COLORS] = { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, /* #000000 -> #000000 */ 0x4, /* #00005f -> #0000aa */ 0x4, /* #000087 -> #0000aa */ 0x4, /* #0000af -> #0000aa */ 0x4, /* #0000d7 -> #0000aa */ 0xc, /* #0000ff -> #5555ff */ 0x2, /* #005f00 -> #00aa00 */ 0x8, /* #005f5f -> #555555 */ 0x6, /* #005f87 -> #00aaaa */ 0x6, /* #005faf -> #00aaaa */ 0xc, /* #005fd7 -> #5555ff */ 0xc, /* #005fff -> #5555ff */ 0x2, /* #008700 -> #00aa00 */ 0xa, /* #00875f -> #55aa55 */ 0x6, /* #008787 -> #00aaaa */ 0x6, /* #0087af -> #00aaaa */ 0x6, /* #0087d7 -> #00aaaa */ 0xc, /* #0087ff -> #5555ff */ 0x2, /* #00af00 -> #00aa00 */ 0xa, /* #00af5f -> #55aa55 */ 0x6, /* #00af87 -> #00aaaa */ 0x6, /* #00afaf -> #00aaaa */ 0x6, /* #00afd7 -> #00aaaa */ 0xe, /* #00afff -> #55ffff */ 0x2, /* #00d700 -> #00aa00 */ 0xa, /* #00d75f -> #55aa55 */ 0x6, /* #00d787 -> #00aaaa */ 0x6, /* #00d7af -> #00aaaa */ 0x6, /* #00d7d7 -> #00aaaa */ 0xe, /* #00d7ff -> #55ffff */ 0x2, /* #00ff00 -> #00aa00 */ 0xa, /* #00ff5f -> #55aa55 */ 0x6, /* #00ff87 -> #00aaaa */ 0x6, /* #00ffaf -> #00aaaa */ 0xe, /* #00ffd7 -> #55ffff */ 0xe, /* #00ffff -> #55ffff */ 0x1, /* #5f0000 -> #aa0000 */ 0x8, /* #5f005f -> #555555 */ 0x5, /* #5f0087 -> #aa00aa */ 0x5, /* #5f00af -> #aa00aa */ 0x5, /* #5f00d7 -> #aa00aa */ 0xc, /* #5f00ff -> #5555ff */ 0x3, /* #5f5f00 -> #aa5500 */ 0x8, /* #5f5f5f -> #555555 */ 0x8, /* #5f5f87 -> #555555 */ 0x7, /* #5f5faf -> #aaaaaa */ 0xc, /* #5f5fd7 -> #5555ff */ 0xc, /* #5f5fff -> #5555ff */ 0x2, /* #5f8700 -> #00aa00 */ 0xa, /* #5f875f -> #55aa55 */ 0xa, /* #5f8787 -> #55aa55 */ 0x7, /* #5f87af -> #aaaaaa */ 0xc, /* #5f87d7 -> #5555ff */ 0xc, /* #5f87ff -> #5555ff */ 0x2, /* #5faf00 -> #00aa00 */ 0xa, /* #5faf5f -> #55aa55 */ 0xa, /* #5faf87 -> #55aa55 */ 0x7, /* #5fafaf -> #aaaaaa */ 0x7, /* #5fafd7 -> #aaaaaa */ 0xe, /* #5fafff -> #55ffff */ 0x2, /* #5fd700 -> #00aa00 */ 0xa, /* #5fd75f -> #55aa55 */ 0xa, /* #5fd787 -> #55aa55 */ 0x7, /* #5fd7af -> #aaaaaa */ 0xe, /* #5fd7d7 -> #55ffff */ 0xe, /* #5fd7ff -> #55ffff */ 0x2, /* #5fff00 -> #00aa00 */ 0xb, /* #5fff5f -> #ffff55 */ 0xb, /* #5fff87 -> #ffff55 */ 0x7, /* #5fffaf -> #aaaaaa */ 0xe, /* #5fffd7 -> #55ffff */ 0xe, /* #5fffff -> #55ffff */ 0x1, /* #870000 -> #aa0000 */ 0x8, /* #87005f -> #555555 */ 0x5, /* #870087 -> #aa00aa */ 0x5, /* #8700af -> #aa00aa */ 0x5, /* #8700d7 -> #aa00aa */ 0xc, /* #8700ff -> #5555ff */ 0x3, /* #875f00 -> #aa5500 */ 0x8, /* #875f5f -> #555555 */ 0x8, /* #875f87 -> #555555 */ 0x7, /* #875faf -> #aaaaaa */ 0xc, /* #875fd7 -> #5555ff */ 0xc, /* #875fff -> #5555ff */ 0x3, /* #878700 -> #aa5500 */ 0xa, /* #87875f -> #55aa55 */ 0x7, /* #878787 -> #aaaaaa */ 0x7, /* #8787af -> #aaaaaa */ 0x7, /* #8787d7 -> #aaaaaa */ 0xc, /* #8787ff -> #5555ff */ 0x2, /* #87af00 -> #00aa00 */ 0xa, /* #87af5f -> #55aa55 */ 0x7, /* #87af87 -> #aaaaaa */ 0x7, /* #87afaf -> #aaaaaa */ 0x7, /* #87afd7 -> #aaaaaa */ 0xe, /* #87afff -> #55ffff */ 0x2, /* #87d700 -> #00aa00 */ 0xa, /* #87d75f -> #55aa55 */ 0x7, /* #87d787 -> #aaaaaa */ 0x7, /* #87d7af -> #aaaaaa */ 0xe, /* #87d7d7 -> #55ffff */ 0xe, /* #87d7ff -> #55ffff */ 0x2, /* #87ff00 -> #00aa00 */ 0xb, /* #87ff5f -> #ffff55 */ 0xb, /* #87ff87 -> #ffff55 */ 0x7, /* #87ffaf -> #aaaaaa */ 0xe, /* #87ffd7 -> #55ffff */ 0xe, /* #87ffff -> #55ffff */ 0x1, /* #af0000 -> #aa0000 */ 0x5, /* #af005f -> #aa00aa */ 0x5, /* #af0087 -> #aa00aa */ 0x5, /* #af00af -> #aa00aa */ 0x5, /* #af00d7 -> #aa00aa */ 0xd, /* #af00ff -> #ff55ff */ 0x3, /* #af5f00 -> #aa5500 */ 0x9, /* #af5f5f -> #ff5555 */ 0x9, /* #af5f87 -> #ff5555 */ 0x7, /* #af5faf -> #aaaaaa */ 0xd, /* #af5fd7 -> #ff55ff */ 0xd, /* #af5fff -> #ff55ff */ 0x3, /* #af8700 -> #aa5500 */ 0xa, /* #af875f -> #55aa55 */ 0x7, /* #af8787 -> #aaaaaa */ 0x7, /* #af87af -> #aaaaaa */ 0x7, /* #af87d7 -> #aaaaaa */ 0xd, /* #af87ff -> #ff55ff */ 0x2, /* #afaf00 -> #00aa00 */ 0xa, /* #afaf5f -> #55aa55 */ 0x7, /* #afaf87 -> #aaaaaa */ 0x7, /* #afafaf -> #aaaaaa */ 0x7, /* #afafd7 -> #aaaaaa */ 0xf, /* #afafff -> #ffffff */ 0x2, /* #afd700 -> #00aa00 */ 0xb, /* #afd75f -> #ffff55 */ 0x7, /* #afd787 -> #aaaaaa */ 0x7, /* #afd7af -> #aaaaaa */ 0x7, /* #afd7d7 -> #aaaaaa */ 0xf, /* #afd7ff -> #ffffff */ 0x2, /* #afff00 -> #00aa00 */ 0xb, /* #afff5f -> #ffff55 */ 0xb, /* #afff87 -> #ffff55 */ 0x7, /* #afffaf -> #aaaaaa */ 0xf, /* #afffd7 -> #ffffff */ 0xf, /* #afffff -> #ffffff */ 0x1, /* #d70000 -> #aa0000 */ 0x9, /* #d7005f -> #ff5555 */ 0x5, /* #d70087 -> #aa00aa */ 0x5, /* #d700af -> #aa00aa */ 0x5, /* #d700d7 -> #aa00aa */ 0xd, /* #d700ff -> #ff55ff */ 0x3, /* #d75f00 -> #aa5500 */ 0x9, /* #d75f5f -> #ff5555 */ 0x9, /* #d75f87 -> #ff5555 */ 0x7, /* #d75faf -> #aaaaaa */ 0xd, /* #d75fd7 -> #ff55ff */ 0xd, /* #d75fff -> #ff55ff */ 0x3, /* #d78700 -> #aa5500 */ 0x9, /* #d7875f -> #ff5555 */ 0x7, /* #d78787 -> #aaaaaa */ 0x7, /* #d787af -> #aaaaaa */ 0x7, /* #d787d7 -> #aaaaaa */ 0xd, /* #d787ff -> #ff55ff */ 0x2, /* #d7af00 -> #00aa00 */ 0xa, /* #d7af5f -> #55aa55 */ 0x7, /* #d7af87 -> #aaaaaa */ 0x7, /* #d7afaf -> #aaaaaa */ 0x7, /* #d7afd7 -> #aaaaaa */ 0xf, /* #d7afff -> #ffffff */ 0x2, /* #d7d700 -> #00aa00 */ 0xb, /* #d7d75f -> #ffff55 */ 0x7, /* #d7d787 -> #aaaaaa */ 0x7, /* #d7d7af -> #aaaaaa */ 0xf, /* #d7d7d7 -> #ffffff */ 0xf, /* #d7d7ff -> #ffffff */ 0xb, /* #d7ff00 -> #ffff55 */ 0xb, /* #d7ff5f -> #ffff55 */ 0xb, /* #d7ff87 -> #ffff55 */ 0x7, /* #d7ffaf -> #aaaaaa */ 0xf, /* #d7ffd7 -> #ffffff */ 0xf, /* #d7ffff -> #ffffff */ 0x1, /* #ff0000 -> #aa0000 */ 0x9, /* #ff005f -> #ff5555 */ 0x5, /* #ff0087 -> #aa00aa */ 0x5, /* #ff00af -> #aa00aa */ 0x5, /* #ff00d7 -> #aa00aa */ 0xd, /* #ff00ff -> #ff55ff */ 0x3, /* #ff5f00 -> #aa5500 */ 0x9, /* #ff5f5f -> #ff5555 */ 0x9, /* #ff5f87 -> #ff5555 */ 0x7, /* #ff5faf -> #aaaaaa */ 0xd, /* #ff5fd7 -> #ff55ff */ 0xd, /* #ff5fff -> #ff55ff */ 0x3, /* #ff8700 -> #aa5500 */ 0x9, /* #ff875f -> #ff5555 */ 0x9, /* #ff8787 -> #ff5555 */ 0x7, /* #ff87af -> #aaaaaa */ 0xd, /* #ff87d7 -> #ff55ff */ 0xd, /* #ff87ff -> #ff55ff */ 0x2, /* #ffaf00 -> #00aa00 */ 0xb, /* #ffaf5f -> #ffff55 */ 0x7, /* #ffaf87 -> #aaaaaa */ 0x7, /* #ffafaf -> #aaaaaa */ 0x7, /* #ffafd7 -> #aaaaaa */ 0xf, /* #ffafff -> #ffffff */ 0x2, /* #ffd700 -> #00aa00 */ 0xb, /* #ffd75f -> #ffff55 */ 0xb, /* #ffd787 -> #ffff55 */ 0x7, /* #ffd7af -> #aaaaaa */ 0xf, /* #ffd7d7 -> #ffffff */ 0xf, /* #ffd7ff -> #ffffff */ 0xb, /* #ffff00 -> #ffff55 */ 0xb, /* #ffff5f -> #ffff55 */ 0xb, /* #ffff87 -> #ffff55 */ 0xf, /* #ffffaf -> #ffffff */ 0xf, /* #ffffd7 -> #ffffff */ 0xf, /* #ffffff -> #ffffff */ 0x0, /* #080808 -> #000000 */ 0x0, /* #121212 -> #000000 */ 0x0, /* #1c1c1c -> #000000 */ 0x0, /* #262626 -> #000000 */ 0x8, /* #303030 -> #555555 */ 0x8, /* #3a3a3a -> #555555 */ 0x8, /* #444444 -> #555555 */ 0x8, /* #4e4e4e -> #555555 */ 0x8, /* #585858 -> #555555 */ 0x8, /* #626262 -> #555555 */ 0x8, /* #6c6c6c -> #555555 */ 0x8, /* #767676 -> #555555 */ 0x7, /* #808080 -> #aaaaaa */ 0x7, /* #8a8a8a -> #aaaaaa */ 0x7, /* #949494 -> #aaaaaa */ 0x7, /* #9e9e9e -> #aaaaaa */ 0x7, /* #a8a8a8 -> #aaaaaa */ 0x7, /* #b2b2b2 -> #aaaaaa */ 0x7, /* #bcbcbc -> #aaaaaa */ 0x7, /* #c6c6c6 -> #aaaaaa */ 0x7, /* #d0d0d0 -> #aaaaaa */ 0xf, /* #dadada -> #ffffff */ 0xf, /* #e4e4e4 -> #ffffff */ 0xf, /* #eeeeee -> #ffffff */ }; ================================================ FILE: apps/wallpaper-picker.c ================================================ /** * @brief Graphical wallpaper picker. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2019 K. Lange */ #include #include #include #include #include #include #include #include #include #include #define BUTTON_HEIGHT 28 #define BUTTON_WIDTH 86 #define BUTTON_PADDING 14 static yutani_t * yctx; static yutani_window_t * window = NULL; static gfx_context_t * ctx = NULL; static sprite_t wallpaper = { 0 }; static struct TT_Font * tt_font = NULL; static int32_t width = 640; static int32_t height = 300; static char * title_str = "Wallpaper Picker"; #define DEFAULT_PATH "/usr/share/wallpaper.jpg" #define WALLPAPERS_PATH "/usr/share/wallpapers" static char * wallpaper_path; static struct TTKButton _set = {0}; static struct TTKButton _close = {0}; static struct TTKButton _left = {0}; static struct TTKButton _right = {0}; static list_t * wallpapers = NULL; static node_t * current_wallpaper = NULL; static void redraw(void) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); /* Clear to black */ draw_fill(ctx, rgb(0,0,0)); /* Calculate fit */ int max_width = window->width - bounds.width; int max_height = window->height - bounds.height; /* Calculate the appropriate scaled size to fit the screen. */ float x = (float)max_width / (float)wallpaper.width; float y = (float)max_height / (float)wallpaper.height; int nh = (int)(x * (float)wallpaper.height); int nw = (int)(y * (float)wallpaper.width); /* Scale the wallpaper into the buffer. */ if (nw <= width) { /* Scaled wallpaper is wider, height should match. */ draw_sprite_scaled(ctx, &wallpaper, bounds.left_width + ((int)max_width - nw) / 2, bounds.top_height, nw+2, max_height); } else { /* Scaled wallpaper is taller, width should match. */ draw_sprite_scaled(ctx, &wallpaper, bounds.left_width, bounds.top_height + ((int)max_height - nh) / 2, max_width+2, nh); } /* Draws the path for the selected wallpaper in white, centered, with a drop shadow */ tt_set_size(tt_font, 13); int str_width = tt_string_width(tt_font, wallpaper_path); int center_x_text = (window->width - bounds.width - str_width) / 2; tt_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); tt_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); /* Draw the buttons */ ttk_button_draw(ctx, &_set); ttk_button_draw(ctx, &_close); ttk_button_draw(ctx, &_left); ttk_button_draw(ctx, &_right); /* Draw window decorations */ render_decorations(window, ctx, title_str); flip(ctx); yutani_flip(yctx, window); } int in_button(struct TTKButton * button, struct yutani_msg_window_mouse_event * me) { if (me->new_y >= button->y && me->new_y < button->y + button->height) { if (me->new_x >= button->x && me->new_x < button->x + button->width) { return 1; } } return 0; } void setup_buttons(void) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); _set.title = "Set"; _set.width = BUTTON_WIDTH; _set.height = BUTTON_HEIGHT; _set.x = ctx->width - bounds.right_width - BUTTON_WIDTH - BUTTON_PADDING * 2 - BUTTON_HEIGHT; _set.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING; _close.title = "Close"; _close.width = BUTTON_WIDTH; _close.height = BUTTON_HEIGHT; _close.x = ctx->width - bounds.right_width - BUTTON_WIDTH * 2 - BUTTON_PADDING * 3 - BUTTON_HEIGHT; _close.y = ctx->height - bounds.bottom_height - BUTTON_HEIGHT - BUTTON_PADDING; _left.title = "<"; _left.width = BUTTON_HEIGHT; _left.height = BUTTON_WIDTH; _left.x = bounds.left_width + BUTTON_PADDING; _left.y = bounds.top_height + (ctx->height - BUTTON_WIDTH) / 2; _right.title = ">"; _right.width = BUTTON_HEIGHT; _right.height = BUTTON_WIDTH; _right.x = ctx->width - bounds.right_width - BUTTON_HEIGHT - BUTTON_PADDING; _right.y = bounds.top_height + (ctx->height - BUTTON_WIDTH) / 2; } void resize_finish(int w, int h) { yutani_window_resize_accept(yctx, window, w, h); reinit_graphics_yutani(ctx, window); width = w; height = h; setup_buttons(); redraw(); yutani_window_resize_done(yctx, window); } void set_hilight(struct TTKButton * button, int hilight) { if (!button && (_set.hilight || _close.hilight || _left.hilight || _right.hilight)) { _set.hilight = 0; _close.hilight = 0; _left.hilight = 0; _right.hilight = 0; redraw(); } else if (button && (button->hilight != hilight)) { _set.hilight = 0; _close.hilight = 0; _left.hilight = 0; _right.hilight = 0; button->hilight = hilight; redraw(); } } void load_wallpaper(void) { if (wallpaper.bitmap) free(wallpaper.bitmap); wallpaper.bitmap = NULL; /* load wallpaper */ load_sprite(&wallpaper, wallpaper_path); /* Ensures we render correctly when scaling */ wallpaper.alpha = ALPHA_EMBEDDED; } void get_default_wallpaper(void) { char * home = getenv("HOME"); if (!home) { /* That should not happen... */ wallpaper_path = strdup(DEFAULT_PATH); return; } char path[512]; sprintf(path, "%s/.wallpaper.conf", home); FILE * conf = fopen(path,"r"); if (!conf) { wallpaper_path = strdup(DEFAULT_PATH); return; } if (conf) { char line[1024]; while (!feof(conf)) { fgets(line, 1024, conf); char * nl = strchr(line, '\n'); if (nl) *nl = '\0'; if (line[0] == ';') { continue; } if (strstr(line, "wallpaper=") == line) { wallpaper_path = strdup(line+strlen("wallpaper=")); break; } } fclose(conf); } } void set_wallpaper(void) { /* get the PID of the destkop file-browser */ FILE * f = fopen("/var/run/.wallpaper.pid","r"); if (!f) { /* TODO show an error dialog */ fprintf(stderr, "Failed to read wallpaper PID\n"); return; } char data[30]; fgets(data, 30, f); fclose(f); int pid = atoi(data); /* write the config file */ char * home = getenv("HOME"); if (!home) { /* That should not happen... */ fprintf(stderr, "Failed to read HOME envvar\n"); return; } char path[512]; sprintf(path, "%s/.wallpaper.conf", home); FILE * conf = fopen(path,"w"); fprintf(conf,"wallpaper=%s\n", wallpaper_path); fprintf(stderr, "Setting wallpaper to %s\n", wallpaper_path); fclose(conf); /* signal the desktop */ kill(pid, SIGUSR1); } void read_wallpapers(void) { wallpapers = list_create(); /* Open wallpapers directory */ DIR * dirp = opendir(WALLPAPERS_PATH); if (!dirp) { return; /* No wallpapers? */ } struct dirent * ent = readdir(dirp); while (ent != NULL) { if (!strcmp(ent->d_name,".") || !strcmp(ent->d_name,"..")) { ent = readdir(dirp); continue; } char tmp[strlen(WALLPAPERS_PATH)+strlen(ent->d_name)+2]; sprintf(tmp, "%s/%s", WALLPAPERS_PATH, ent->d_name); list_insert(wallpapers, strdup(tmp)); ent = readdir(dirp); } closedir(dirp); } void pick_wallpaper(int dir) { if (current_wallpaper) { if (dir == 1) { current_wallpaper = current_wallpaper->next; } else { current_wallpaper = current_wallpaper->prev; } } if (!current_wallpaper) { if (dir == 1) { current_wallpaper = wallpapers->head; } else { current_wallpaper = wallpapers->tail; } if (!current_wallpaper) return; /* No wallpapers */ } free(wallpaper_path); wallpaper_path = strdup(current_wallpaper->value); load_wallpaper(); } 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; tt_font = tt_font_from_shm("sans-serif"); get_default_wallpaper(); read_wallpapers(); 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, "wallpaper-picker"); ctx = init_graphics_yutani_double_buffer(window); setup_buttons(); load_wallpaper(); redraw(); struct TTKButton * _down_button = NULL; 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 == '\n') { playing = 0; } else if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == KEY_ESCAPE) { 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*)(uintptr_t)wf->wid); if (win) { win->focused = wf->focused; redraw(); } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; resize_finish(wr->width, wr->height); } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; if (me->wid == window->wid) { 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; } struct decor_bounds bounds; decor_get_bounds(window, &bounds); if (me->new_y > bounds.top_height) { if (me->command == YUTANI_MOUSE_EVENT_DOWN) { if (in_button(&_set, me)) { set_hilight(&_set, 2); _down_button = &_set; } else if (in_button(&_close, me)) { set_hilight(&_close, 2); _down_button = &_close; } else if (in_button(&_left, me)) { set_hilight(&_left, 2); _down_button = &_left; } else if (in_button(&_right, me)) { set_hilight(&_right, 2); _down_button = &_right; } } else if (me->command == YUTANI_MOUSE_EVENT_RAISE || me->command == YUTANI_MOUSE_EVENT_CLICK) { if (_down_button) { if (in_button(_down_button, me)) { if (_down_button == &_close) { playing = 0; break; } else if (_down_button == &_set) { /* Set wallpaper */ set_wallpaper(); } else if (_down_button == &_left) { /* Previous wallpaper */ pick_wallpaper(-1); redraw(); } else if (_down_button == &_right) { /* Next wallpaper */ pick_wallpaper(1); redraw(); } _down_button->hilight = 0; } } _down_button = NULL; } if (!me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { if (in_button(&_set, me)) { set_hilight(&_set, 1); } else if (in_button(&_close, me)) { set_hilight(&_close, 1); } else if (in_button(&_left, me)) { set_hilight(&_left, 1); } else if (in_button(&_right, me)) { set_hilight(&_right, 1); } else { set_hilight(NULL,0); } } else if (_down_button) { if (in_button(_down_button, me)) { set_hilight(_down_button, 2); } else { set_hilight(NULL, 0); } } } } } 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; } ================================================ FILE: apps/wc.c ================================================ /** * @brief wc - count bytes, characters, words, lines... * * @copyright * 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 */ #include #include #include #include #include #include int main(int argc, char * argv[]) { int show_lines = 0; int show_words = 0; int show_chars = 0; int show_bytes = 0; int opt; while ((opt = getopt(argc,argv,"cmlw")) != -1) { switch (opt) { case 'c': show_bytes = 1; break; case 'm': show_chars = 1; break; case 'l': show_lines = 1; break; case 'w': show_words = 1; break; } } int retval = 0; int total_lines = 0; int total_chars = 0; int total_words = 0; int just_stdin = 0; if (optind == argc) { argv[optind] = ""; argc++; just_stdin = 1; } for (int i = optind; i < argc; ++i) { if (!*argv[i] && !just_stdin) { fprintf(stderr, "%s: invalid zero-length file name\n", argv[0]); retval = 1; continue; } FILE * f = (!strcmp(argv[i], "-") || just_stdin) ? stdin : fopen(argv[i], "r"); if (!f) { fprintf(stderr, "%s: %s: %s\n", argv[0], argv[i], strerror(errno)); retval = 1; continue; } int lines = 0; int chars = 0; int words = 0; int ch; uint32_t state, c; int last_was_whitespace = 0; while (!feof(f)) { ch = getc(f); if (ch < 0) break; if (show_chars) { if (!decode(&state, &c, ch)) { } else if (state == UTF8_REJECT) { state = 0; } } else { c = ch; } chars++; if (c == '\n') { last_was_whitespace = 1; lines++; words++; } else if (c == ' ') { if (last_was_whitespace) continue; last_was_whitespace = 1; words++; } else { last_was_whitespace = 0; } } if (!last_was_whitespace && chars > 0) words++; if (!show_words && !show_chars && !show_bytes && !show_lines) { fprintf(stdout, "%d %d %d %s\n", lines, words, chars, argv[i]); } else { if (show_lines) fprintf(stdout, "%d ", lines); if (show_words) fprintf(stdout, "%d ", words); if (show_bytes|show_chars) fprintf(stdout, "%d ", chars); fprintf(stdout, "%s\n", argv[i]); } total_lines += lines; total_words += words; total_chars += chars; if (f != stdin) fclose(f); if (just_stdin) return 0; } if (optind + 1 < argc) { if (!show_words && !show_chars && !show_bytes && !show_lines) { fprintf(stdout, "%d %d %d %s\n", total_lines, total_words, total_chars, "total"); } else { if (show_lines) fprintf(stdout, "%d ", total_lines); if (show_words) fprintf(stdout, "%d ", total_words); if (show_bytes|show_chars) fprintf(stdout, "%d ", total_chars); fprintf(stdout, "%s\n", "total"); } } return retval; } ================================================ FILE: apps/weather-configurator.c ================================================ /** * @brief Configure the weather information 'daemon' * * Messes with /etc/weather.json so it needs root for now... * * @copyright * 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-2021 K. Lange */ #include #include #include #include typedef struct JSON_Value Value; int main(int argc, char * argv[]) { Value * config = json_parse_file("/etc/weather.json"); if (config) { char * city = JSON_KEY(config, "city")->string; char * key = JSON_KEY(config, "key")->string; char * __comment = JSON_KEY(config, "--comment")->string; char * units = JSON_KEY(config, "units")->string; fprintf(stdout, "City? [%s] ", city); fflush(stdout); char ncity[100]; fgets(ncity, 100, stdin); if (ncity[0] != '\n') { char * n = strstr(ncity, "\n"); if (n) *n = '\0'; city = ncity; } fprintf(stdout, "Units? [%s] ", units); fflush(stdout); char nunits[100]; fgets(nunits, 100, stdin); if (nunits[0] != '\n') { char * n = strstr(nunits, "\n"); if (n) *n = '\0'; units = nunits; } FILE * f = fopen("/etc/weather.json", "w"); fprintf(f, "{\n"); fprintf(f, " \"city\": \"%s\",\n", city); fprintf(f, " \"units\": \"%s\",\n", units); fprintf(f, "\n"); fprintf(f, " \"--comment\": \"%s\",\n", __comment); fprintf(f, " \"key\": \"%s\"\n", key); fprintf(f, "}\n"); fclose(f); } else { fprintf(stderr, "Configuration is not set. A key is required. Please create the file manually.\n"); fprintf(stderr, "(Press ENTER to exit.)\n"); getchar(); return 0; } } ================================================ FILE: apps/weather-tool.c ================================================ /** * @brief Ask OpenWeather for forecast data. * * Fetches weather forecast data from OpenWeather and converts the JSON * output to a simpler parsed format we read in the panel. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include typedef struct JSON_Value Value; #define WEATHER_CONF_PATH "/etc/weather.json" #define WEATHER_DATA_PATH "/tmp/weather-data.json" #define WEATHER_OUT_PATH "/tmp/weather-parsed.conf" #define LOCATION_DATA_PATH "/tmp/location-data.json" int main(int argc, char * argv[]) { Value * config = json_parse_file(WEATHER_CONF_PATH); if (!config) { fprintf(stderr, "No weather config data\n"); return 1; } char * city = JSON_KEY(config, "city")->string; char * key = JSON_KEY(config, "key")->string; char * units = JSON_KEY(config, "units")->string; char cmdline[1024]; /* If the city is 'guess', we'll make a single query to a separate service to * get a location from the user's external IP... */ if (!strcmp(city, "guess")) { /* See if the location data already exists... */ Value * locationData = json_parse_file(LOCATION_DATA_PATH); if (!locationData) { sprintf(cmdline, "fetch -o \"" LOCATION_DATA_PATH "\" \"http://ip-api.com/json/?fields=lat,lon,city,offset\""); system(cmdline); locationData = json_parse_file(LOCATION_DATA_PATH); } /* If we still failed to load it, then bail. */ if (!locationData) { fprintf(stderr, "%s: city field was set to 'guess' but failed to acquire data from IP geolocation service\n", argv[0]); return 1; } city = JSON_KEY(locationData, "city")->string; double lat = JSON_KEY(locationData, "lat")->number; double lon = JSON_KEY(locationData, "lon")->number; sprintf(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); system(cmdline); } else { sprintf(cmdline, "fetch -o \"" WEATHER_DATA_PATH "\" \"http://api.openweathermap.org/data/2.5/weather?q=%s&appid=%s&units=%s\"", city, key, units); system(cmdline); } Value * result = json_parse_file(WEATHER_DATA_PATH); assert(result && result->type == JSON_TYPE_OBJECT); Value * _main = JSON_KEY(result,"main"); Value * conditions = (JSON_KEY(result,"weather") && JSON_KEY(result,"weather")->array->length > 0) ? JSON_IND(JSON_KEY(result,"weather"),0) : NULL; FILE * out = fopen(WEATHER_OUT_PATH, "w"); /** * The format for a parsed weather payload is a series of line-separated entries: * - Formatted temperature, with decimal. * - Integral temperature, eg. for the panel widget. * - Main weather conditions string, eg. "Clouds" * - Icon identifier, eg. 02d is "cloudy, daytime". * - Humidity (integer, percentage) * - Cloud coverage (integer, percentage) * - City name (we're using the guessed location, not the one from the weather provider...) * - Date string of last update */ fprintf(out, "%.2lf\n", JSON_KEY(_main,"temp")->number); fprintf(out, "%d\n", (int)JSON_KEY(_main,"temp")->number); fprintf(out, "%s\n", conditions ? JSON_KEY(conditions,"main")->string : ""); fprintf(out, "%s\n", conditions ? JSON_KEY(conditions,"icon")->string : ""); fprintf(out, "%d\n", (int)JSON_KEY(_main,"humidity")->number); fprintf(out, "%d\n", JSON_KEY(JSON_KEY(result,"clouds"),"all") ? (int)JSON_KEY(JSON_KEY(result,"clouds"),"all")->number : 0); fprintf(out, "%s\n", city); char * format = "%a, %d %b %Y %H:%M:%S\n"; struct tm * timeinfo; struct timeval now; char buf[BUFSIZ] = {0}; gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); strftime(buf,BUFSIZ,format,timeinfo); fprintf(out, buf); fprintf(out, "%d\n", (int)JSON_KEY(_main,"pressure")->number); fclose(out); return 0; } ================================================ FILE: apps/which.c ================================================ /** * @brief which - Figure out which binary will be used * * Searches through $PATH to find a matching binary, just like * how execp* family does it. (Except does our execp actually * bother checking permissions? Look into this...) * * @copyright * 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-2014 K. Lange */ #include #include #include #include #define DEFAULT_PATH "/bin:/usr/bin" int main(int argc, char * argv[]) { int ret_val = 0; int i = 1; int print_all = 0; if (i < argc && !strcmp(argv[i],"-a")) { print_all = 1; i++; } if (i == argc) { return 1; } for (; i < argc; ++i) { if (strstr(argv[i], "/")) { struct stat t; if (!stat(argv[i], &t)) { if ((t.st_mode & 0111)) { printf("%s\n", argv[1]); } } } else { char * file = argv[i]; char * path = getenv("PATH"); if (!path) { path = DEFAULT_PATH; } char * xpath = strdup(path); char * p, * last; int found = 0; for ((p = strtok_r(xpath, ":", &last)); p; p = strtok_r(NULL, ":", &last)) { int r; struct stat stat_buf; char * exe = malloc(strlen(p) + strlen(file) + 2); strcpy(exe, p); strcat(exe, "/"); strcat(exe, file); r = stat(exe, &stat_buf); if (r != 0) { continue; } if (!(stat_buf.st_mode & 0111)) { continue; /* XXX not technically correct; need to test perms */ } found = 1; printf("%s\n", exe); if (print_all) continue; break; } free(xpath); if (!found) ret_val = 1; } } return ret_val; } ================================================ FILE: apps/whoami.c ================================================ /** * @brief uses getpwuid and geteuid to retrieve the current user's name. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include int main(int argc, char ** argv) { struct passwd * p = getpwuid(geteuid()); if (!p) return 0; fprintf(stdout, "%s\n", p->pw_name); endpwent(); return 0; } ================================================ FILE: apps/yes.c ================================================ /** * @brief yes - Continually print stuff * * Continually prints its first argument, followed by a newline. * * @copyright * 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 */ #include int main(int argc, char * argv[]) { char * yes_string = "y"; if (argc > 1) { yes_string = argv[1]; } while (1) { printf("%s\n", yes_string); } return 0; } ================================================ FILE: apps/yutani-clipboard.c ================================================ /** * @brief yutani-clipboard - Manipulate the Yutani clipboard * * Gets and sets clipboard values. * * @copyright * 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 */ #include #include #include #include void show_usage(int argc, char * argv[]) { printf( "yutani-clipboard - set and obtain clipboard contents\n" "\n" "usage: %s -g\n" " %s -s TEXT...\n" " %s -f FILE\n" "\n" " -s \033[3mset the clipboard text to argument\033[0m\n" " -f \033[3mset the clibboard text to file\033[0m\n" " -g \033[3mprint clipboard contents to stdout\033[0m\n" " -n \033[3mensure a linefeed is printed\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0], argv[0], argv[0]); } yutani_t * yctx; int force_linefeed = 0; int set_clipboard_from_file(char * file) { FILE * f; f = fopen(file, "r"); if (!f) return 1; fseek(f, 0, SEEK_END); size_t size = ftell(f); fseek(f, 0, SEEK_SET); char * tmp = malloc(size+1); fread(tmp, 1, size, f); tmp[size] = '\0'; yutani_set_clipboard(yctx, tmp); free(tmp); return 0; } void get_clipboard(void) { yutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_CLIPBOARD); yutani_msg_t * clipboard = yutani_wait_for(yctx, YUTANI_MSG_CLIPBOARD); struct yutani_msg_clipboard * cb = (void *)clipboard->data; if (*cb->content == '\002') { int size = atoi(&cb->content[2]); FILE * clipboard = yutani_open_clipboard(yctx); char * selection_text = malloc(size + 1); fread(selection_text, 1, size, clipboard); selection_text[size] = '\0'; fclose(clipboard); fwrite(selection_text, 1, size, stdout); if (force_linefeed && size && selection_text[size-1] != '\n') { printf("\n"); } } else { char * selection_text = malloc(cb->size+1); memcpy(selection_text, cb->content, cb->size); selection_text[cb->size] = '\0'; fwrite(selection_text, 1, cb->size, stdout); if (force_linefeed && cb->size && selection_text[cb->size-1] != '\n') { printf("\n"); } } } int main(int argc, char * argv[]) { yctx = yutani_init(); if (!yctx) { fprintf(stderr, "%s: failed to connect to compositor\n", argv[0]); return 1; } int opt; while ((opt = getopt(argc, argv, "?s:f:gn")) != -1) { switch (opt) { case 's': yutani_set_clipboard(yctx, optarg); return 0; case 'f': return set_clipboard_from_file(optarg); case 'n': force_linefeed = 1; break; case 'g': get_clipboard(); return 0; case '?': show_usage(argc,argv); return 1; } } show_usage(argc, argv); return 1; } ================================================ FILE: apps/yutani-kbd.c ================================================ /** * @brief Debug tool for keyboard input. * * @copyright * 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 */ #include #include #include #include #include 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; char * modifiers(unsigned int m) { static char out[] = "........"; if (m & YUTANI_KEY_MODIFIER_LEFT_CTRL) out[0] = 'c'; else out[0] = '.'; if (m & YUTANI_KEY_MODIFIER_LEFT_SHIFT) out[1] = 's'; else out[1] = '.'; if (m & YUTANI_KEY_MODIFIER_LEFT_ALT) out[2] = 'a'; else out[2] = '.'; if (m & YUTANI_KEY_MODIFIER_LEFT_SUPER) out[3] = 'x'; else out[3] = '.'; if (m & YUTANI_KEY_MODIFIER_RIGHT_CTRL) out[4] = 'c'; else out[4] = '.'; if (m & YUTANI_KEY_MODIFIER_RIGHT_SHIFT) out[5] = 's'; else out[5] = '.'; if (m & YUTANI_KEY_MODIFIER_RIGHT_ALT) out[6] = 'a'; else out[6] = '.'; if (m & YUTANI_KEY_MODIFIER_RIGHT_SUPER) out[7] = 'x'; else out[7] = '.'; return out; } void redraw(void) { draw_fill(ctx, rgb(0,0,0)); int w = width - 1, h = height - 1; draw_line(ctx, 0, w, 0, 0, rgb(255,255,255)); draw_line(ctx, 0, w, h, h, rgb(255,255,255)); draw_line(ctx, 0, 0, 0, h, rgb(255,255,255)); draw_line(ctx, w, w, 0, h, rgb(255,255,255)); } int main (int argc, char ** argv) { left = 100; top = 100; width = 500; height = 500; yctx = yutani_init(); wina = yutani_window_create(yctx, width, height); yutani_window_move(yctx, wina, left, top); ctx = init_graphics_yutani(wina); redraw(); char keys[256] = {0}; printf("\033[H\033[2J"); 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.keycode >= 'a' && ke->event.keycode < 'z') { keys[ke->event.keycode] = (ke->event.action == KEY_ACTION_DOWN); } printf("\033[1;1H"); for (int i = 'a'; i < 'z'; ++i) { printf("\033[%dm%c ", keys[i] ? 0 : 31, i); } fflush(stdout); } 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; } ================================================ FILE: apps/yutani-query.c ================================================ /** * @brief yutani-query - Query display server information * * At the moment, this only supports querying the display * resolution. An older version of this application had * support for getting the default font names, but the * font server is no longer part of the compositor, so * that functionality doesn't make sense here. * * @copyright * 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 */ #include #include #include yutani_t * yctx; int quiet = 0; void show_usage(int argc, char * argv[]) { printf( "yutani-query - show misc. information about the display system\n" "\n" "usage: %s [-r?]\n" "\n" " -r \033[3mprint display resoluton\033[0m\n" " -e \033[3mask compositor to reload extensions\033[0m\n" " -? \033[3mshow this help text\033[0m\n" "\n", argv[0]); } int show_resolution(void) { if (!yctx) { if (!quiet) printf("(not connected)\n"); return 1; } printf("%dx%d\n", (int)yctx->display_width, (int)yctx->display_height); return 0; } int reload(void) { if (!yctx) { if (!quiet) printf("(not connected)\n"); return 1; } yutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_RELOAD); return 0; } int main(int argc, char * argv[]) { yctx = yutani_init(); int opt; while ((opt = getopt(argc, argv, "?qre")) != -1) { switch (opt) { case 'q': quiet = 1; break; /* Legacy options */ case 'r': return show_resolution(); case 'e': return reload(); case '?': show_usage(argc,argv); return 0; } } if (optind < argc) { if (!strcmp(argv[optind], "resolution")) { return show_resolution(); } else if (!strcmp(argv[optind], "reload")) { return reload(); } else { fprintf(stderr, "%s: unsupported command: %s\n", argv[0], argv[optind]); return 1; } } return 0; } ================================================ FILE: apps/yutani-test.c ================================================ /** * @brief yutani-test - Yutani Test Tool * * Kinda like xev: Pops up a window and displays events in a * human-readable format. * * @copyright * 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 */ #include #include #include #include #include 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; const char * action_name(unsigned int action) { switch (action) { case KEY_ACTION_UP: return "up"; case KEY_ACTION_DOWN: return "down"; default: return "?"; } } char * modifiers(unsigned int m) { static char out[] = "........"; if (m & YUTANI_KEY_MODIFIER_LEFT_CTRL) out[0] = 'c'; else out[0] = '.'; if (m & YUTANI_KEY_MODIFIER_LEFT_SHIFT) out[1] = 's'; else out[1] = '.'; if (m & YUTANI_KEY_MODIFIER_LEFT_ALT) out[2] = 'a'; else out[2] = '.'; if (m & YUTANI_KEY_MODIFIER_LEFT_SUPER) out[3] = 'x'; else out[3] = '.'; if (m & YUTANI_KEY_MODIFIER_RIGHT_CTRL) out[4] = 'c'; else out[4] = '.'; if (m & YUTANI_KEY_MODIFIER_RIGHT_SHIFT) out[5] = 's'; else out[5] = '.'; if (m & YUTANI_KEY_MODIFIER_RIGHT_ALT) out[6] = 'a'; else out[6] = '.'; if (m & YUTANI_KEY_MODIFIER_RIGHT_SUPER) out[7] = 'x'; else out[7] = '.'; return out; } char * mouse_buttons(unsigned char button) { static char out[] = "...."; if (button & YUTANI_MOUSE_BUTTON_LEFT) out[0] = 'l'; else out[0] = '.'; if (button & YUTANI_MOUSE_BUTTON_MIDDLE) out[1] = 'm'; else out[1] = '.'; if (button & YUTANI_MOUSE_BUTTON_RIGHT) out[2] = 'r'; else out[2] = '.'; if (button & YUTANI_MOUSE_SCROLL_UP) out[3] = 'u'; else \ if (button & YUTANI_MOUSE_SCROLL_DOWN) out[3] = 'd'; else out[3] = '.'; return out; } const char * mouse_command(unsigned char type) { switch (type) { case (YUTANI_MOUSE_EVENT_CLICK): return "click"; case (YUTANI_MOUSE_EVENT_DRAG ): return "drag"; case (YUTANI_MOUSE_EVENT_RAISE): return "raise"; case (YUTANI_MOUSE_EVENT_DOWN ): return "down"; case (YUTANI_MOUSE_EVENT_MOVE ): return "move"; case (YUTANI_MOUSE_EVENT_LEAVE): return "leave"; case (YUTANI_MOUSE_EVENT_ENTER): return "enter"; default: return "unknown"; } } void redraw(void) { draw_fill(ctx, rgb(0,0,0)); int w = width - 1, h = height - 1; draw_line(ctx, 0, w, 0, 0, rgb(255,255,255)); draw_line(ctx, 0, w, h, h, rgb(255,255,255)); draw_line(ctx, 0, 0, 0, h, rgb(255,255,255)); draw_line(ctx, w, w, 0, h, rgb(255,255,255)); yutani_flip(yctx, wina); } int main (int argc, char ** argv) { int show_cursor = 1; left = 100; top = 100; width = 500; height = 500; yctx = yutani_init(); wina = yutani_window_create(yctx, width, height); yutani_window_move(yctx, wina, left, top); ctx = init_graphics_yutani(wina); redraw(); 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; fprintf(stderr, "Key Press (wid=%d) %s\n" "\tevent.action = %d\n" "\tevent.keycode = %d\n" "\tevent.modifiers = %s\n" "\tevent.key = %d (%c)\n", ke->wid, action_name(ke->event.action), ke->event.action, ke->event.keycode, modifiers(ke->event.modifiers), ke->event.key, ke->event.key == 0 ? '?' : ke->event.key); if (ke->event.key == 'm' && ke->event.action == KEY_ACTION_DOWN) { show_cursor = !show_cursor; yutani_window_show_mouse(yctx, wina, show_cursor); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; fprintf(stderr, "Mouse Event (wid=%d) %s\n" "\tnew = %d, %d\n" "\told = %d, %d\n" "\tbuttons = %s\n" "\tmodifiers = %s\n" "\tcommand = %d\n", (int)me->wid, mouse_command(me->command), (int)me->new_x, (int)me->new_y, (int)me->old_x, (int)me->old_y, mouse_buttons(me->buttons), modifiers(me->modifiers), me->command); } break; case YUTANI_MSG_WINDOW_FOCUS_CHANGE: { struct yutani_msg_window_focus_change * fc = (void*)m->data; fprintf(stderr, "Focus Change (wid=%d) %s\n", fc->wid, fc->focused ? "on" : "off"); } break; case YUTANI_MSG_WINDOW_MOVE: { struct yutani_msg_window_move * wm = (void*)m->data; fprintf(stderr, "Window Moved (wid=%d) %d, %d\n", (int)wm->wid, (int)wm->x, (int)wm->y); } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void*)m->data; fprintf(stderr, "Resize Offer (wid=%d) %d x %d\n" "\tbufid = %d\n", (int)wr->wid, (int)wr->width, (int)wr->height, (int)wr->bufid); } 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; } ================================================ FILE: apps/yutani-tty-pipe.c ================================================ #include #include #include #include #include #include #include yutani_t * yctx; int quiet = 0; int show_resolution(void) { if (!yctx) { if (!quiet) printf("(not connected)\n"); return 1; } printf("%dx%d\n", (int)yctx->display_width, (int)yctx->display_height); return 0; } int reload(void) { if (!yctx) { if (!quiet) printf("(not connected)\n"); return 1; } yutani_special_request(yctx, NULL, YUTANI_SPECIAL_REQUEST_RELOAD); return 0; } struct termios old; void set_unbuffered() { tcgetattr(fileno(stdin), &old); struct termios new = old; new.c_lflag &= (~ICANON & ~ECHO); tcsetattr(fileno(stdin), TCSAFLUSH, &new); } int main(int argc, char * argv[]) { yctx = yutani_init(); if (!yctx) { fprintf(stderr, "not connected; did you set $DISPLAY?\n"); return 1; } set_unbuffered(); int c; while ((c = fgetc(stdin))) { key_event_t event = {0}; key_event_state_t state = {0}; event.keycode = c; event.key = c; switch (c) { case 27: event.keycode = KEY_ESCAPE; event.key = KEY_ESCAPE; break; /* Either of the backspace keys */ case 8: case 0x7f: event.keycode = 8; event.key = 8; break; /* Any of \r or \n */ case '\r': case '\n': event.keycode = '\n'; event.key = '\n'; break; } event.action = KEY_ACTION_DOWN; yutani_msg_buildx_key_event_alloc(m_); yutani_msg_buildx_key_event(m_, 0, &event, &state); yutani_msg_send(yctx, m_); event.action = KEY_ACTION_UP; yutani_msg_buildx_key_event(m_, 0, &event, &state); yutani_msg_send(yctx, m_); } return 0; } ================================================ FILE: apps/zcat.c ================================================ #include #include int main(int argc, char * argv[]) { char ** args = malloc(sizeof(char*) * (argc + 2)); /* For -c and terminating NULL */ args[0] = "gunzip"; args[1] = "-c"; for (int i = 1; i < argc; ++i) { args[i+1] = argv[i]; } args[argc+1] = NULL; return execvp("gunzip", args); } ================================================ FILE: base/etc/demo.conf ================================================ ; this is a comment test=hello [sec] tion=test ; this is also a comment ================================================ FILE: base/etc/group ================================================ root:x:0: adm:x:1:local dialout:x:2:local ================================================ FILE: base/etc/hostname ================================================ livecd ================================================ FILE: base/etc/master.passwd ================================================ root:toor:0:0:Administrator:/home/root:/bin/esh:fancy local:local:1000:1000:Local User:/home/local:/bin/esh:fancy guest:guest:1001:1001:Guest User:/home/guest:/bin/esh:fancy ================================================ FILE: base/etc/motd ================================================ Welcome to ToaruOS! ToaruOS is free software, released under the terms of the NCSA / University of Illinois License. https://toaruos.org https://github.com/klange/toaruos ================================================ FILE: base/etc/msk.conf ================================================ remote_order=cdrom,cdn,fallback [remotes] cdrom=/cdrom/extra local=http://192.168.11.2:8080 fallback=http://toaruos.org/msk/2.0.x cdn=http://toaruos.sfo3.cdn.digitaloceanspaces.com/msk/2.0.x ================================================ FILE: base/etc/panel.menu ================================================ :_ &accessories,folder,Accessories &demos,folder,Demos &games,folder,Games &settings,folder,Settings - exec help-browser,help,Help Browser exec about,star,About ToaruOS log-out,exit,Log Out :accessories exec calculator,calculator,Calculator exec file-browser,folder,File Browser exec terminal,utilities-terminal,Terminal :demos &decorated,folder,Decorated &undecorated,folder,Undecorated :undecorated exec drawlines,drawlines,Draw Lines :decorated exec julia,julia,Julia Fractals exec mandelbrot,mandelbrot,Mandelbrot Explorer exec plasma,plasma,Plasma :games exec mines.krk,mines,Mines :settings exec gsudo package-manager,package,Package Manager exec wallpaper-picker,wallpaper-picker,Select Wallpaper exec cpuwidget,system-monitor,System Monitor ================================================ FILE: base/etc/passwd ================================================ root:x:0:0:Administrator:/home/root:/bin/esh:fancy adm:x:1:1:Administrators:/tmp:/bin/false:nope dialout:x:2:2:Serial Users:/tmp:/bin/false:nope local:x:1000:1000:Local User:/home/local:/bin/esh:fancy guest:x:1001:1001:Guest User:/home/guest:/bin/esh:fancy ================================================ FILE: base/etc/startup.d/00_startuplog.sh ================================================ #!/bin/esh # This daemonizes exec splash-log ================================================ FILE: base/etc/startup.d/01_migrate.sh ================================================ #!/bin/esh if not kcmdline -q migrate then exit 0 echo -n "Migrating filesystem..." >> /dev/pex/splash /bin/migrate ================================================ FILE: base/etc/startup.d/02_hostname.sh ================================================ #!/bin/esh export-cmd HOSTNAME cat /etc/hostname echo -n "Setting hostname..." > /dev/pex/splash if empty? "$HOSTNAME" then exec hostname "localhost" else exec hostname "$HOSTNAME" ================================================ FILE: base/etc/startup.d/03_tmpfs.sh ================================================ #!/bin/esh echo -n "Mounting tmpfs..." > /dev/pex/splash mount tmpfs tmp,777 /tmp mount tmpfs var,755 /var mkdir /var/run ================================================ FILE: base/etc/startup.d/04_modprobe.sh ================================================ #!/bin/esh echo -n "Installing device driver modules..." > /dev/pex/splash # Only load this in virtualbox for now, as we're not # even sure we're doing the remapping correctly... if lspci -q 80EE:CAFE,8086:7000 then insmod /mod/piix4.ko # Add module descriptions here... if lspci -q 8086:2415 then insmod /mod/ac97.ko if lspci -q 1234:1111,15ad:07a0 then insmod /mod/vmware.ko if lspci -q 80EE:CAFE then insmod /mod/vbox.ko if lspci -q 8086:0046 then insmod /mod/i965.ko if lspci -q 1274:1371 then insmod /mod/es1371.ko if lspci -q 8086:100e,8086:1004,8086:100f,8086:10ea,8086:10d3 then insmod /mod/e1000.ko # Device drivers if lspci -q 8086:7111,8086:7010 then insmod /mod/ata.ko ================================================ FILE: base/etc/startup.d/05_mountcd.sh ================================================ #!/bin/esh if not stat -Lq /dev/cdrom0 then exit 0 echo -n "Mounting CD..." > /dev/pex/splash insmod /mod/iso9660.ko mount iso /dev/cdrom0 /cdrom # Does it look like it might be ours? if not stat -Lq /cdrom/bootcat then exit 0 echo -e "icon=cd\nrun=cd /cdrom ; exec file-browser\ntitle=CD-ROM" > /home/local/Desktop/5_cdrom.launcher ================================================ FILE: base/etc/startup.d/40_dhcp.sh ================================================ #!/bin/esh if kcmdline -q no-startup-dhcp then exit 0 echo -n "Setting up network..." >> /dev/pex/splash /bin/dhclient ================================================ FILE: base/etc/startup.d/50_msk.sh ================================================ #!/bin/esh if kcmdline -q no-startup-msk then exit 0 echo -n "Checking for package updates..." >> /dev/pex/splash msk update & ================================================ FILE: base/etc/startup.d/99_runstart.sh ================================================ #!/bin/esh export-cmd START kcmdline -g start # We haven't actually hit a login yet, so make sure these are set here... export USER=root export HOME=/home/root export-cmd TZ_OFFSET find-timezone export-cmd GETTY_ARGS qemu-fwcfg opt/org.toaruos.gettyargs echo -n "Launching startup application..." > /dev/pex/splash echo -n "!quit" > /dev/pex/splash if equals? "$START" "--vga" then exec /bin/terminal-vga -l if equals? "$START" "--headless" then exec /bin/getty ${GETTY_ARGS} if empty? "$START" then exec /bin/compositor else exec /bin/compositor $START ================================================ FILE: base/etc/sudoers ================================================ local ================================================ FILE: base/etc/weather.json ================================================ { "city": "guess", "units": "metric", "--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.", "key": "78c832cfada2b0337f516891afb4f13b" } ================================================ FILE: base/home/guest/hello ================================================ ================================================ FILE: base/home/local/.bim3rc ================================================ ''' Sample config file for bim3 ''' # Quirks work the same as they did in bim2, except for the obvious syntax change quirk('TERM','screen','no24bit','noitalic') quirk('TERM','xterm-256color','caninsert','canpaste','cansgrmouse') quirk('TERM','toaru-vga','no24bit','no256color') quirk('TERMINAL_EMULATOR','JetBrains','nobce') # checkprop() returns 0 if we _can_ do something, so # call it with 'not' to check capabilities... if not checkprop('can_unicode'): tabindicator('»') spaceindicator('·') # Themes are actually Kuroko functions now, but we load # them into a table like always for the :theme command theme('sunsmoke') # Non-string values are coerced into strings, so these commands # can take integer values. global.git(1) global.statusbar(1) global.autohidetabs(1) smartcomplete(1) let x = wq ================================================ FILE: base/home/local/.eshrc ================================================ export PS1_TITLE="\[\e]1;\u@\h:\w\\007\e]2;\u@\h:\w\\007\]" export PS1_RIGHT="\[\\e[1m\e[38;5;59m\][\[\e[38;5;173m\]\d \[\e[38;5;167m\]\t\[\e[38;5;59m\]] \[\e[0m\]" export 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\] " if equals? "$TERM" "toaru-vga" then export RLINE_THEME="default" else export RLINE_THEME="sunsmoke" if stat -Lq /usr/local/bin then export PATH="/usr/local/bin:$PATH" ================================================ FILE: base/home/local/.wallpaper.conf ================================================ ; this doesn't get loaded with the regular confreader wallpaper=/usr/share/wallpaper.jpg ================================================ FILE: base/home/local/.yutanirc ================================================ #!/bin/esh # Start wallpaper cd ~/Desktop file-browser --wallpaper & cd ~ # Start toast daemon toastd --really # Daemonizes # If we haven't shown the tutorial yet, show it. if not stat -q ~/.tutorial-shown then show-tutorial.sh # Login toast notifications show-toasts.krk & # Replace us with the panel exec panel --really ================================================ FILE: base/home/local/Desktop/0_file_browser.launcher ================================================ icon=folder run=cd ~ ; exec file-browser title=File Browser ================================================ FILE: base/home/local/Desktop/1_terminal.launcher ================================================ run=cd ~ ; exec terminal title=Terminal icon=utilities-terminal ================================================ FILE: base/home/local/Desktop/2_packages.launcher ================================================ run=exec gsudo package-manager title=Packages icon=package ================================================ FILE: base/home/local/Desktop/3_read_me.launcher ================================================ icon=file run=exec terminal bim ~/README.md title=Read Me ================================================ FILE: base/home/local/README.md ================================================ # Welcome to ToaruOS! ToaruOS provides a familiar Unix-like environment, but please be aware that the shell is incomplete and does not implement all Unix shell features. For help with the shell's syntax and built-in functions, run `help`. For a list of available commands, press Tab twice. Tab completion is available for both commands and file names. To edit files, try using `bim` - a vi-like editor with syntax highlighting, line and character selection, history stack, tabs, and more. To install packages, use the `msk` tool. You can install a GCC/binutils toolchain with: sudo msk install build-essential Or you can install some games with: sudo msk install doom quake The password for the default user (`local`) is `local`. ToaruOS's compositing window server includes many common keybindings: - Hold Alt to drag windows. - Super (Win) combined with the arrow keys will "grid" windows to the sides or top and bottom of the screen. Combine with Ctrl and Shift for quarter-sized gridding. - Alt-F10 maximized and unmaximizes windows. - Alt-F4 closes windows. (If this file is too long to view in one screenful in your terminal, you can open it with `bim README`) ================================================ FILE: base/home/local/text_layout.krk ================================================ #!/bin/kuroko from _yutani2 import (YutaniCtx, Font, rgb, rgb, MenuBar, decor_get_bounds, decor_render, MenuList, MenuEntry, MenuEntrySeparator, Message, decor_handle_event, decor_show_default_menu, MenuEntryCustom, Sprite, Subregion, TTContour, TransformMatrix, MenuEntrySubmenu, MenuEntryToggle) from yutani_mainloop import Window, yctx as y, AsyncMainloop, Task, sleep import math import random let mainloop = AsyncMainloop() def premul(r,g,b,a): return rgb(int(r*a),int(g*a),int(b*a),a) let show_ascent = True def toggle_ascent(self): show_ascent = !show_ascent self.state = show_ascent let font_size = 16 class Word: def __init__(self, x, y, word, w, sw, font): self.x = x self.y = y self.word = word self.w = w self.sw = sw self.font = font self.asc, self.desc, self.gap = font.measure() def draw(self, ctx): if show_ascent: let bas = TTContour(self.x+0.5, self.y+0.5) bas.line_to(self.x+0.5+self.w,self.y+0.5) let bas_s = bas.stroke(0.5) bas_s.paint(ctx, premul(0,255,0,0.5)) bas_s.free() bas.free() let asc = TTContour(self.x+0.5, self.y+0.5) asc.line_to(self.x+0.5,self.y+0.5-self.asc) asc.line_to(self.x+0.5+self.w,self.y+0.5-self.asc) asc.line_to(self.x+0.5+self.w,self.y+0.5) let asc_s = asc.stroke(0.5) asc_s.paint(ctx, premul(255,0,0,0.5)) asc_s.free() asc.free() let des = TTContour(self.x+0.5,self.y+0.5) des.line_to(self.x+0.5,self.y+0.5-self.desc) des.line_to(self.x+0.5+self.w,self.y+0.5-self.desc) des.line_to(self.x+0.5+self.w,self.y+0.5) let des_s = des.stroke(0.5) des_s.paint(ctx,premul(0,0,255,0.5)) des_s.free() des.free() self.font.draw_string(ctx, self.word, self.x, self.y) class Line: def __init__(self, x, y, cw, justify='center'): self.words = [] self.x = x self.y = y self.cw = cw self.justify = justify self.islast = True def add(self, word, w, sw, font): self.words.append(Word(self.x, self.y, word, w, sw, font)) def finalize(self): if self.justify == "right": let _x = self.x + self.cw for word in self.words[::-1]: word.x = _x - word.w _x -= word.w + word.sw else if self.justify == "justify" and not self.islast and not len(self.words) < 2: let mw = sum(word.w for word in self.words) let av = float(self.cw - mw) / (len(self.words) - 1) let _x = float(self.x) for word in self.words: word.x = int(_x) _x += float(word.w) + av else if self.justify == "center": let mw = sum(word.w for word in self.words) + sum(word.sw for word in self.words[:-1]) let _x = self.x + (self.cw - mw) // 2 for word in self.words: word.x = _x _x += word.w + word.sw else: # self.justify == "left": let _x = self.x for word in self.words: word.x = _x _x += word.w + word.sw def draw(self, ctx): for word in self.words: word.draw(ctx) def layout_and_draw(ctx, text, justify='center'): let lines = [] let left_padding = 10 let right_padding = 10 let base_x = left_padding let x = base_x let cw = ctx.width - left_padding - right_padding let font = Font("sans-serif", font_size) let asc, desc, gap = font.measure() let lh = int(font_size * 1.2) let lb = int((lh - int(asc - desc)) / 2) let y = lb + int(asc) let line = Line(x, y, cw, justify) for word in text.split(' '): let w = font.width(word) if w + x >= cw: line.islast = False line.finalize() lines.append(line) x = base_x y += lh line = Line(x, y, cw, justify) let sw = font.width(' ') line.add(word, w, sw, font) x += w + sw if line.words: line.finalize() lines.append(line) for line in lines: line.draw(ctx) #let sub = Subregion(ctx, 200, 200, 200, 200) #sub.fill(rgb(255,255,0)) def set_menu(menu, action): for sibling in menu.group: sibling.state = False menu.state = True action() def check_list(entries, default=0): let ml = MenuList() let group = [] for i, e in enumerate(entries): let name, action = e let me = MenuEntryToggle(name, lambda menu: set_menu(menu, action), i == default) me.group = group group.append(me) ml.insert(me) return ml class MyWindow(Window): def __init__(self): super().__init__(640, 480, title="Hello", doublebuffer=True) self.bgc = rgb(255,255,255) self.dejavu = Font("sans-serif", 13) self.mb = MenuBar((("File",'file'),("View",'view'),("Help",'help'))) let _menu_File = MenuList() _menu_File.insert(MenuEntry("Test", lambda menu: print("hello, world"))) _menu_File.insert(MenuEntrySeparator()) _menu_File.insert(MenuEntry("Quit", lambda menu: self.close())) self.mb.insert('file', _menu_File) let _menu_View = MenuList() let _menu_View_Justify = check_list(( ('Left', lambda: self.set_justification('left')), ('Right', lambda: self.set_justification('right')), ('Center', lambda: self.set_justification('center')), ('Justify', lambda: self.set_justification('justify')), ), default=2) self.mb.insert('view-justify', _menu_View_Justify) _menu_View.insert(MenuEntrySubmenu('Justify','view-justify')) let _menu_View_Size = check_list(( (lambda x=y: (str(x), lambda: font_size = x))() for y in [8,12,16,32,64] ), default=2) self.mb.insert('view-size', _menu_View_Size) _menu_View.insert(MenuEntrySubmenu('Font size', 'view-size')) _menu_View.insert(MenuEntryToggle('Show ascent/descent',toggle_ascent,True)) self.mb.insert('view', _menu_View) let _menu_Help = MenuList() let _menu_Help_help = MenuEntry("Help",lambda menu: print("oh no!")) _menu_Help.insert(_menu_Help_help) self.mb.insert('help', _menu_Help) self.mb.callback = lambda x: self.pending_updates = True self.redrawer = Task(self.redraw_loop()) self.pending_updates = True self.justification = 'center' self.cursor = 1,1 def set_justification(self, justify): self.justification = justify self.pending_updates = True async def redraw_loop(self): while not self.closed: if self.pending_updates: self.draw() self.pending_updates = False await sleep(0.016) def draw(self): self.fill(self.bgc) decor_render(self) let bounds = decor_get_bounds(self) self.mb.place(bounds['left_width'],bounds['top_height'],self.width-bounds['width'],self) self.mb.render(self) self.mb.height = 24 # Compatibility #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) let sprite = Sprite(width=self.width-bounds['width'],height=self.height-bounds['height']-self.mb.height) sprite.fill(rgb(255,255,255)) 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) #layout.draw(sprite) self.draw_sprite(sprite,bounds['left_width'],bounds['top_height']+self.mb.height) sprite.free() self.flip() def mouse_event(self, msg): let decResponse = decor_handle_event(msg) if decResponse == 2: self.close() return True else if decResponse == 5: decor_show_default_menu(self, self.x + msg.new_x, self.y + msg.new_y) self.mb.mouse_event(self, msg) let nc = msg.new_x, msg.new_y if nc != self.cursor: self.cursor = nc self.pending_updates = True def keyboard_event(self, msg): if msg.keycode == 113 and msg.action == 1: self.close() def close(self): super().close() mainloop.exit() def window_moved(self, msg): self.pending_updates = True def menu_close(self): self.pending_updates = True def finish_resize(self, msg): if msg.width < 100 or msg.height < 100: self.resize_offer(100 if msg.width < 100 else msg.width, 100 if msg.height < 100 else msg.height) return super().finish_resize(msg) mainloop.activate() let w = MyWindow() w.move(200,200) mainloop.menu_closed_callback = w.menu_close mainloop.run() ================================================ FILE: base/home/root/.bimrc ================================================ rundir /usr/share/bim/themes colorscheme ansi ================================================ FILE: base/home/root/hello ================================================ Hello, root. ================================================ FILE: base/lib/.dummy ================================================ ================================================ FILE: base/usr/include/_cheader.h ================================================ #pragma once #ifdef __cplusplus # define _Begin_C_Header extern "C" { # define _End_C_Header } #else # define _Begin_C_Header # define _End_C_Header #endif ================================================ FILE: base/usr/include/alloca.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header #ifdef __GNUC__ #define alloca(size) __builtin_alloca(size) #else #error alloca requested but this isn't gcc #endif _End_C_Header ================================================ FILE: base/usr/include/arpa/inet.h ================================================ #pragma once #include <_cheader.h> #include #include #include _Begin_C_Header #define INADDR_ANY (unsigned long int)0x0 #define INADDR_BROADCAST (unsigned long int)0xFFffFFffUL #ifndef _KERNEL_ extern uint32_t htonl(uint32_t hostlong); extern uint16_t htons(uint16_t hostshort); extern uint32_t ntohl(uint32_t netlong); extern uint16_t ntohs(uint16_t netshort); in_addr_t inet_addr(const char *cp); char *inet_ntoa(struct in_addr in); #endif _End_C_Header ================================================ FILE: base/usr/include/assert.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header #ifndef NDEBUG extern void __assert_func(const char * file, int line, const char * func, const char * failedexpr); #define assert(statement) ((statement) ? (void)0 : __assert_func(__FILE__, __LINE__, __FUNCTION__, #statement)) #else #define assert(statement) ((void)0) #endif _End_C_Header ================================================ FILE: base/usr/include/bits/dirent.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header typedef struct dirent { ino_t d_ino; char d_name[256]; } dirent; #ifndef _KERNEL_ typedef struct DIR { int fd; int cur_entry; } DIR; DIR * opendir (const char * dirname); int closedir (DIR * dir); struct dirent * readdir (DIR * dirp) __asm__("readdir64"); long telldir (DIR * dirp); void rewinddir (DIR * dirp); void seekdir (DIR * dirp, long loc); #endif _End_C_Header ================================================ FILE: base/usr/include/bits/timespec.h ================================================ #pragma once struct timespec { time_t tv_sec; long tv_nsec; }; ================================================ FILE: base/usr/include/ctype.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header extern int isalnum(int c); extern int isalpha(int c); extern int isdigit(int c); extern int islower(int c); extern int isprint(int c); extern int isgraph(int c); extern int iscntrl(int c); extern int isgraph(int c); extern int ispunct(int c); extern int isspace(int c); extern int isupper(int c); extern int isxdigit(int c); extern int isascii(int c); extern int tolower(int c); extern int toupper(int c); /* Derived from newlib */ #define _U 01 #define _L 02 #define _N 04 #define _S 010 #define _P 020 #define _C 040 #define _X 0100 #define _B 0200 extern unsigned char _ctype_[256]; _End_C_Header ================================================ FILE: base/usr/include/dirent.h ================================================ #pragma once #include ================================================ FILE: base/usr/include/dlfcn.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header /* Currently unused... */ #define RTLD_LAZY (1 << 0) #define RTLD_NOW (1 << 1) #define RTLD_GLOBAL (1 << 2) #define RTLD_DEFAULT ((void*)0) /* Provided by ld.so, but also defined by libc.so for linking */ extern void * dlopen(const char *, int); extern int dlclose(void *); extern void * dlsym(void *, const char *); extern char * dlerror(void); _End_C_Header ================================================ FILE: base/usr/include/errno.h ================================================ #pragma once #include <_cheader.h> /* * The bulk of these match Linux. * Descriptions come from newlib. */ _Begin_C_Header #define EPERM 1 /* Not super-user */ #define ENOENT 2 /* No such file or directory */ #define ESRCH 3 /* No such process */ #define EINTR 4 /* Interrupted system call */ #define EIO 5 /* I/O error */ #define ENXIO 6 /* No such device or address */ #define E2BIG 7 /* Arg list too long */ #define ENOEXEC 8 /* Exec format error */ #define EBADF 9 /* Bad file number */ #define ECHILD 10 /* No children */ #define EAGAIN 11 /* No more processes */ #define ENOMEM 12 /* Not enough core */ #define EACCES 13 /* Permission denied */ #define EFAULT 14 /* Bad address */ #define ENOTBLK 15 /* Block device required */ #define EBUSY 16 /* Mount device busy */ #define EEXIST 17 /* File exists */ #define EXDEV 18 /* Cross-device link */ #define ENODEV 19 /* No such device */ #define ENOTDIR 20 /* Not a directory */ #define EISDIR 21 /* Is a directory */ #define EINVAL 22 /* Invalid argument */ #define ENFILE 23 /* Too many open files in system */ #define EMFILE 24 /* Too many open files */ #define ENOTTY 25 /* Not a typewriter */ #define ETXTBSY 26 /* Text file busy */ #define EFBIG 27 /* File too large */ #define ENOSPC 28 /* No space left on device */ #define ESPIPE 29 /* Illegal seek */ #define EROFS 30 /* Read only file system */ #define EMLINK 31 /* Too many links */ #define EPIPE 32 /* Broken pipe */ #define EDOM 33 /* Math arg out of domain of func */ #define ERANGE 34 /* Math result not representable */ #define ENOMSG 35 /* No message of desired type */ #define EIDRM 36 /* Identifier removed */ #define ECHRNG 37 /* Channel number out of range */ #define EL2NSYNC 38 /* Level 2 not synchronized */ #define EL3HLT 39 /* Level 3 halted */ #define EL3RST 40 /* Level 3 reset */ #define ELNRNG 41 /* Link number out of range */ #define EUNATCH 42 /* Protocol driver not attached */ #define ENOCSI 43 /* No CSI structure available */ #define EL2HLT 44 /* Level 2 halted */ #define EDEADLK 45 /* Deadlock condition */ #define ENOLCK 46 /* No record locks available */ #define EBADE 50 /* Invalid exchange */ #define EBADR 51 /* Invalid request descriptor */ #define EXFULL 52 /* Exchange full */ #define ENOANO 53 /* No anode */ #define EBADRQC 54 /* Invalid request code */ #define EBADSLT 55 /* Invalid slot */ #define EDEADLOCK 56 /* File locking deadlock error */ #define EBFONT 57 /* Bad font file fmt */ #define ENOSTR 60 /* Device not a stream */ #define ENODATA 61 /* No data (for no delay io) */ #define ETIME 62 /* Timer expired */ #define ENOSR 63 /* Out of streams resources */ #define ENONET 64 /* Machine is not on the network */ #define ENOPKG 65 /* Package not installed */ #define EREMOTE 66 /* The object is remote */ #define ENOLINK 67 /* The link has been severed */ #define EADV 68 /* Advertise error */ #define ESRMNT 69 /* Srmount error */ #define ECOMM 70 /* Communication error on send */ #define EPROTO 71 /* Protocol error */ #define EMULTIHOP 74 /* Multihop attempted */ #define ELBIN 75 /* Inode is remote (not really error) */ #define EDOTDOT 76 /* Cross mount point (not really error) */ #define EBADMSG 77 /* Trying to read unreadable message */ #define EFTYPE 79 /* Inappropriate file type or format */ #define ENOTUNIQ 80 /* Given log. name not unique */ #define EBADFD 81 /* f.d. invalid for this operation */ #define EREMCHG 82 /* Remote address changed */ #define ELIBACC 83 /* Can't access a needed shared lib */ #define ELIBBAD 84 /* Accessing a corrupted shared lib */ #define ELIBSCN 85 /* .lib section in a.out corrupted */ #define ELIBMAX 86 /* Attempting to link in too many libs */ #define ELIBEXEC 87 /* Attempting to exec a shared library */ #define ENOSYS 88 /* Function not implemented */ #define ENOTEMPTY 90 /* Directory not empty */ #define ENAMETOOLONG 91 /* File or path name too long */ #define ELOOP 92 /* Too many symbolic links */ #define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */ #define EPFNOSUPPORT 96 /* Protocol family not supported */ #define ECONNRESET 104 /* Connection reset by peer */ #define ENOBUFS 105 /* No buffer space available */ #define EAFNOSUPPORT 106 /* Address family not supported by protocol family */ #define EPROTOTYPE 107 /* Protocol wrong type for socket */ #define ENOTSOCK 108 /* Socket operation on non-socket */ #define ENOPROTOOPT 109 /* Protocol not available */ #define ESHUTDOWN 110 /* Can't send after socket shutdown */ #define ECONNREFUSED 111 /* Connection refused */ #define EADDRINUSE 112 /* Address already in use */ #define ECONNABORTED 113 /* Connection aborted */ #define ENETUNREACH 114 /* Network is unreachable */ #define ENETDOWN 115 /* Network interface is not configured */ #define ETIMEDOUT 116 /* Connection timed out */ #define EHOSTDOWN 117 /* Host is down */ #define EHOSTUNREACH 118 /* Host is unreachable */ #define EINPROGRESS 119 /* Connection already in progress */ #define EALREADY 120 /* Socket already connected */ #define EDESTADDRREQ 121 /* Destination address required */ #define EMSGSIZE 122 /* Message too long */ #define EPROTONOSUPPORT 123 /* Unknown protocol */ #define ESOCKTNOSUPPORT 124 /* Socket type not supported */ #define EADDRNOTAVAIL 125 /* Address not available */ #define ENETRESET 126 #define EISCONN 127 /* Socket is already connected */ #define ENOTCONN 128 /* Socket is not connected */ #define ETOOMANYREFS 129 #define EPROCLIM 130 #define EUSERS 131 #define EDQUOT 132 #define ESTALE 133 #define ENOTSUP 134 /* Not supported */ #define EILSEQ 138 #define EOVERFLOW 139 /* Value too large for defined data type */ #define ECANCELED 140 /* Operation canceled */ #define ENOTRECOVERABLE 141 /* State not recoverable */ #define EOWNERDEAD 142 /* Previous owner died */ #define ESTRPIPE 143 /* Streams pipe error */ #define EWOULDBLOCK EAGAIN /* Operation would block */ #define ERESTARTSYS 512 #define ERESTARTSIGSUSPEND 511 #ifndef _KERNEL_ extern int errno; #define __sets_errno(...) long ret = __VA_ARGS__; if (ret < 0) { errno = -ret; ret = -1; } return ret #endif _End_C_Header ================================================ FILE: base/usr/include/fcntl.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header #define O_RDONLY 0x0000 #define O_WRONLY 0x0001 #define O_RDWR 0x0002 #define O_APPEND 0x0008 #define O_CREAT 0x0200 #define O_TRUNC 0x0400 #define O_EXCL 0x0800 #define O_NOFOLLOW 0x1000 #define O_PATH 0x2000 #define O_NONBLOCK 0x4000 #define O_DIRECTORY 0x8000 #define F_GETFD 1 #define F_SETFD 2 #define F_GETFL 3 #define F_SETFL 4 #define F_DUPFD 10 /* Advisory locks are not currently supported; * these definitions are stubs. */ #define F_GETLK 5 #define F_SETLK 6 #define F_SETLKW 7 #define F_RDLCK 0 #define F_WRLCK 1 #define F_UNLCK 2 struct flock { short l_type; short l_whence; off_t l_start; off_t l_len; pid_t l_pid; }; #define FD_CLOEXEC (1 << 0) #ifndef __kernel__ extern int open (const char *, int, ...); extern int chmod(const char *path, mode_t mode); extern int fchmod(int fd, mode_t mode); extern int fcntl(int fd, int cmd, ...); #endif _End_C_Header ================================================ FILE: base/usr/include/getopt.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header struct option { const char *name; int has_arg; int *flag; int val; }; extern char * optarg; extern int optind, opterr, optopt; extern int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); #define no_argument 0 #define required_argument 1 #define optional_argument 2 /* Unsupported */ _End_C_Header ================================================ FILE: base/usr/include/iconv.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header typedef void * iconv_t; extern iconv_t iconv_open(const char *tocode, const char *fromcode); extern int iconv_close(iconv_t cd); extern size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft); _End_C_Header; ================================================ FILE: base/usr/include/inttypes.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header #define PRIi8 "i" #define PRIi16 "i" #define PRIi32 "i" #define PRIi64 "li" #define PRIx8 "x" #define PRIx16 "x" #define PRIx32 "x" #define PRIx64 "lx" #define PRIu8 "u" #define PRIu16 "u" #define PRIu32 "u" #define PRIu64 "lu" #define PRId8 "d" #define PRId16 "d" #define PRId32 "d" #define PRId64 "ld" _End_C_Header ================================================ FILE: base/usr/include/kernel/arch/aarch64/dtb.h ================================================ #pragma once #include struct fdt_header { uint32_t magic; uint32_t totalsize; uint32_t off_dt_struct; uint32_t off_dt_strings; uint32_t off_mem_rsvmap; uint32_t version; uint32_t last_comp_version; uint32_t boot_cpuid_phys; uint32_t size_dt_strings; uint32_t size_dt_struct; }; static inline uint32_t swizzle(uint32_t from) { uint8_t a = from >> 24; uint8_t b = from >> 16; uint8_t c = from >> 8; uint8_t d = from; return (d << 24) | (c << 16) | (b << 8) | (a); } static inline uint64_t swizzle64(uint64_t from) { uint8_t a = from >> 56; uint8_t b = from >> 48; uint8_t c = from >> 40; uint8_t d = from >> 32; uint8_t e = from >> 24; uint8_t f = from >> 16; uint8_t g = from >> 8; uint8_t h = from; return ((uint64_t)h << 56) | ((uint64_t)g << 48) | ((uint64_t)f << 40) | ((uint64_t)e << 32) | (d << 24) | (c << 16) | (b << 8) | (a); } static inline uint16_t swizzle16(uint16_t from) { uint8_t a = from >> 8; uint8_t b = from; return (b << 8) | (a); } uint32_t * dtb_find_node(const char * name); uint32_t * dtb_find_node_prefix(const char * name); uint32_t * dtb_node_find_property(uint32_t * node, const char * property); void dtb_memory_size(size_t * memsize, size_t * physsize); void dtb_callback_direct_children(uint32_t * node, void (*callback)(uint32_t * child)); void dtb_locate_cmdline(char ** args_out); void dtb_pcie_base(void); ================================================ FILE: base/usr/include/kernel/arch/aarch64/gic.h ================================================ #pragma once #include struct irq_callback { int (*callback)(process_t * this, int irq, void *data); process_t * owner; void * data; struct irq_callback * next; }; extern volatile uint32_t * gic_regs; extern volatile uint32_t * gicc_regs; extern struct irq_callback * irq_callbacks[]; void gic_assign_interrupt(int irq, int (*callback)(process_t*,int,void*), void * data); void gic_map_pci_interrupt(const char * name, uint32_t device, int * int_out, int (*callback)(process_t*,int,void*), void * isr_addr); void gic_map_regs(uintptr_t rpi_tag); void gic_send_sgi(uint8_t intid, int target); ================================================ FILE: base/usr/include/kernel/arch/aarch64/pml.h ================================================ #pragma once #include union PML { struct { uint64_t present : 1; uint64_t table_page : 1; uint64_t attrindx:3; uint64_t ns:1; uint64_t ap:2; uint64_t sh:2; uint64_t af:1; uint64_t ng:1; uint64_t page:36; uint64_t reserved:4; uint64_t contiguous:1; uint64_t pxn:1; uint64_t uxn:1; uint64_t avail:4; uint64_t ignored:5; } bits; struct { uint64_t valid : 1; uint64_t table : 1; uint64_t next:46; uint64_t reserved:4; uint64_t ignored:7; uint64_t pxntable:1; uint64_t xntable:1; uint64_t aptable:2; uint64_t nstable:1; } table_bits; uint64_t raw; }; #define mmu_page_is_user_readable(p) (p->bits.ap & 1) #define mmu_page_is_user_writable(p) ((p->bits.ap & 1) && !(p->bits.ap & 2)) ================================================ FILE: base/usr/include/kernel/arch/aarch64/regs.h ================================================ #pragma once #include struct regs { uint64_t x30, user_sp; uint64_t x28, x29; uint64_t x26, x27; uint64_t x24, x25; uint64_t x22, x23; uint64_t x20, x21; uint64_t x18, x19; uint64_t x16, x17; uint64_t x14, x15; uint64_t x12, x13; uint64_t x10, x11; uint64_t x8, x9; uint64_t x6, x7; uint64_t x4, x5; uint64_t x2, x3; uint64_t x0, x1; }; ================================================ FILE: base/usr/include/kernel/arch/aarch64/rpi.h ================================================ #include struct rpitag { uint32_t phys_addr; uint32_t x; uint32_t y; uint32_t s; uint32_t b; uint32_t size; uint32_t ramdisk_start; uint32_t ramdisk_end; }; void rpi_load_ramdisk(struct rpitag * tag, uintptr_t * ramdisk_phys_base, size_t * ramdisk_size); void rpi_set_cmdline(char ** args_out); ================================================ FILE: base/usr/include/kernel/arch/x86_64/acpi.h ================================================ #pragma once #include struct rsdp_descriptor { char signature[8]; uint8_t checksum; char oemid[6]; uint8_t revision; uint32_t rsdt_address; } __attribute__((packed)); struct rsdp_descriptor_20 { struct rsdp_descriptor base; uint32_t length; uint64_t xsdt_address; uint8_t ext_checksum; uint8_t _reserved[3]; } __attribute((packed)); struct acpi_sdt_header { char signature[4]; uint32_t length; uint8_t revision; uint8_t checksum; char oemid[6]; char oem_tableid[8]; uint32_t oem_revision; uint32_t creator_id; uint32_t creator_revision; } __attribute__((packed)); struct rsdt { struct acpi_sdt_header header; uint32_t pointers[]; }; struct madt { struct acpi_sdt_header header; uint32_t lapic_addr; uint32_t flags; uint8_t entries[]; }; static inline int acpi_checksum(struct acpi_sdt_header * header) { uint8_t check = 0; for (size_t i = 0; i < header->length; ++i) { check += ((uint8_t *)header)[i]; } return check == 0; } ================================================ FILE: base/usr/include/kernel/arch/x86_64/cmos.h ================================================ #pragma once #include uint32_t read_cmos(void); ================================================ FILE: base/usr/include/kernel/arch/x86_64/irq.h ================================================ #pragma once #include extern struct regs * _isr0(struct regs*); extern struct regs * _isr1(struct regs*); extern struct regs * _isr2(struct regs*); extern struct regs * _isr3(struct regs*); extern struct regs * _isr4(struct regs*); extern struct regs * _isr5(struct regs*); extern struct regs * _isr6(struct regs*); extern struct regs * _isr7(struct regs*); extern struct regs * _isr8(struct regs*); extern struct regs * _isr9(struct regs*); extern struct regs * _isr10(struct regs*); extern struct regs * _isr11(struct regs*); extern struct regs * _isr12(struct regs*); extern struct regs * _isr13(struct regs*); extern struct regs * _isr14(struct regs*); extern struct regs * _isr15(struct regs*); extern struct regs * _isr16(struct regs*); extern struct regs * _isr17(struct regs*); extern struct regs * _isr18(struct regs*); extern struct regs * _isr19(struct regs*); extern struct regs * _isr20(struct regs*); extern struct regs * _isr21(struct regs*); extern struct regs * _isr22(struct regs*); extern struct regs * _isr23(struct regs*); extern struct regs * _isr24(struct regs*); extern struct regs * _isr25(struct regs*); extern struct regs * _isr26(struct regs*); extern struct regs * _isr27(struct regs*); extern struct regs * _isr28(struct regs*); extern struct regs * _isr29(struct regs*); extern struct regs * _isr30(struct regs*); extern struct regs * _isr31(struct regs*); extern struct regs * _irq0(struct regs*); extern struct regs * _irq1(struct regs*); extern struct regs * _irq2(struct regs*); extern struct regs * _irq3(struct regs*); extern struct regs * _irq4(struct regs*); extern struct regs * _irq5(struct regs*); extern struct regs * _irq6(struct regs*); extern struct regs * _irq7(struct regs*); extern struct regs * _irq8(struct regs*); extern struct regs * _irq9(struct regs*); extern struct regs * _irq10(struct regs*); extern struct regs * _irq11(struct regs*); extern struct regs * _irq12(struct regs*); extern struct regs * _irq13(struct regs*); extern struct regs * _irq14(struct regs*); extern struct regs * _irq15(struct regs*); extern struct regs * _isr123(struct regs*); extern struct regs * _isr124(struct regs*); /* Does not actually take regs */ extern struct regs * _isr125(struct regs*); /* Does not actually take regs */ extern struct regs * _isr126(struct regs*); /* Does not actually take regs */ extern struct regs * _isr127(struct regs*); /* Syscall entry point */ typedef struct regs * (*interrupt_handler_t)(struct regs *); /** * Interrupt descriptor table */ typedef struct { uint16_t base_low; uint16_t selector; uint8_t zero; uint8_t flags; uint16_t base_mid; uint32_t base_high; uint32_t pad; } __attribute__((packed)) idt_entry_t; struct idt_pointer { uint16_t limit; uintptr_t base; } __attribute__((packed)); extern void irq_ack(size_t irq_no); typedef int (*irq_handler_chain_t) (struct regs *); extern void irq_install_handler(size_t irq, irq_handler_chain_t handler, const char * desc); extern const char * get_irq_handler(int irq, int chain); extern void idt_load(void *); ================================================ FILE: base/usr/include/kernel/arch/x86_64/pml.h ================================================ #pragma once #include union PML { struct { uint64_t present:1; uint64_t writable:1; uint64_t user:1; uint64_t writethrough:1; uint64_t nocache:1; uint64_t accessed:1; uint64_t _available1:1; uint64_t size:1; uint64_t global:1; uint64_t cow_pending:1; uint64_t _available2:2; uint64_t page:28; uint64_t reserved:12; uint64_t _available3:11; uint64_t nx:1; } bits; uint64_t raw; }; #define mmu_page_is_user_readable(p) (p->bits.user) #define mmu_page_is_user_writable(p) (p->bits.user && p->bits.writable) ================================================ FILE: base/usr/include/kernel/arch/x86_64/ports.h ================================================ #pragma once #include extern unsigned short inports(unsigned short _port); extern void outports(unsigned short _port, unsigned short _data); extern unsigned int inportl(unsigned short _port); extern void outportl(unsigned short _port, unsigned int _data); extern unsigned char inportb(unsigned short _port); extern void outportb(unsigned short _port, unsigned char _data); extern void outportsm(unsigned short port, unsigned char * data, unsigned long size); extern void inportsm(unsigned short port, unsigned char * data, unsigned long size); ================================================ FILE: base/usr/include/kernel/arch/x86_64/regs.h ================================================ #pragma once #include /** * Register layout for interrupt context. */ struct regs { /* Pushed by common stub */ uintptr_t r15, r14, r13, r12; uintptr_t r11, r10, r9, r8; uintptr_t rbp, rdi, rsi, rdx, rcx, rbx, rax; /* Pushed by wrapper */ uintptr_t int_no, err_code; /* Pushed by interrupt */ uintptr_t rip, cs, rflags, rsp, ss; }; ================================================ FILE: base/usr/include/kernel/args.h ================================================ #pragma once int args_present(const char * karg); char * args_value(const char * karg); void args_parse(const char * arg); ================================================ FILE: base/usr/include/kernel/assert.h ================================================ #pragma once extern void __assert_failed(const char * file, int line, const char * func, const char * cond); #define assert(condition) do { if (!(condition)) __assert_failed(__FILE__,__LINE__,__func__,#condition); } while (0) ================================================ FILE: base/usr/include/kernel/elf.h ================================================ /** * @file elf.h * @author K. Lange * * @copyright Copyright in this work is disclaimed under the assertion * that its contents are purely factual and no rights may be reserved * for its use. * * @brief Structures for Elf binary files. * * Based primarily on the Elf and SysV ABI specification documents. * * @see https://uclibc.org/docs/elf-64-gen.pdf * @see https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.99.pdf */ #pragma once #include /** * @typedef Elf64_Addr * @brief Unsigned program address. (uintptr_t) * @typedef Elf64_Off * @brief Unsigned file offset. (size_t) * @typedef Elf64_Half * @brief Unsigned medium integer. (unsigned short) * @typedef Elf64_Word * @brief Unsigned integer. (unsigned int) * @typedef Elf64_Sword * @brief Signed integer. (int) * @typedef Elf64_Xword * @brief Unsigned long integer. (unsigned long long) * @typedef Elf64_Sxword * @brief Signed long integer. (long long) */ typedef uint64_t Elf64_Addr; typedef uint64_t Elf64_Off; typedef uint16_t Elf64_Half; typedef uint32_t Elf64_Word; typedef int32_t Elf64_Sword; typedef uint64_t Elf64_Xword; typedef int64_t Elf64_Sxword; /** * Values for e_ident[EI_MAGn] */ #define ELFMAG0 0x7f #define ELFMAG1 'E' #define ELFMAG2 'L' #define ELFMAG3 'F' /** * Values for e_ident[EI_CLASS] */ #define ELFCLASS32 1 #define ELFCLASS64 2 /** * Values for e_ident[EI_DATA] */ #define ELFDATA2LSB 1 #define ELFDATA2MSB 2 /** * Values for e_type */ #define ET_NONE 0 #define ET_REL 1 #define ET_EXEC 2 #define ET_DYN 3 #define ET_CORE 4 /** * e_ident fields */ #define EI_MAG0 0 #define EI_MAG1 1 #define EI_MAG2 2 #define EI_MAG3 3 #define EI_CLASS 4 #define EI_DATA 5 #define EI_VERSION 6 #define EI_OSABI 7 #define EI_ABIVERSION 8 #define EI_PAD 9 #define EI_NIDENT 16 #define EM_X86_64 62 /** * @brief Elf object file header. */ typedef struct Elf64_Header { uint8_t e_ident[EI_NIDENT]; /**< @brief Identifies the layout of the rest of the file. */ Elf64_Half e_type; /**< @brief What kind of file this is, eg. object or executable... */ Elf64_Half e_machine; /**< @brief The architecture this file is for. */ Elf64_Word e_version; /**< @brief The version of the standard this file confirms to. */ Elf64_Addr e_entry; /**< @brief The entry point of an executable. */ Elf64_Off e_phoff; /**< @brief The offset of the program headers. */ Elf64_Off e_shoff; /**< @brief The offset of the section headers. */ Elf64_Word e_flags; /**< @brief Various flags. */ Elf64_Half e_ehsize; /**< @brief Size of this header. */ Elf64_Half e_phentsize; /**< @brief Size of one program header table entry. */ Elf64_Half e_phnum; /**< @brief The number of entries in the program header table. */ Elf64_Half e_shentsize; /**< @brief Size of one section header table entry. */ Elf64_Half e_shnum; /**< @brief The number of entries in the section header table. */ Elf64_Half e_shstrndx; } Elf64_Header; /** * Special section indices */ #define SHN_UNDEF 0 #define SHN_LOPROC 0xFF00 #define SHN_HIPROC 0xFF1F #define SHN_LOOS 0xFF20 #define SHN_HIOS 0xFF3F #define SHN_ABS 0xFFF1 #define SHN_COMMON 0xFFF2 /** * Values for sh_type, sh_link, sh_info */ #define SHT_NULL 0 #define SHT_PROGBITS 1 #define SHT_SYMTAB 2 #define SHT_STRTAB 3 #define SHT_RELA 4 #define SHT_HASH 5 #define SHT_DYNAMIC 6 #define SHT_NOTE 7 #define SHT_NOBITS 8 #define SHT_REL 9 #define SHT_SHLIB 10 #define SHT_DYNSYM 11 #define SHT_LOOS 0x60000000 #define SHT_HIOS 0x6FFFFFFF #define SHT_LOPROC 0x70000000 #define SHT_HIPROC 0x7FFFFFFF /** * Values for sh_flags */ #define SHF_WRITE 0x00000001 #define SHF_ALLOC 0x00000002 #define SHF_EXECINSTR 0x00000004 #define SHF_MASKOS 0x0F000000 #define SHF_MASKPROC 0xF0000000 /* From the SysV x86-64 ABI */ #define SHF_X86_64_LARGE 0x10000000 #define SHF_X86_64_UNWIND 0x70000001 typedef struct Elf64_Shdr { Elf64_Word sh_name; Elf64_Word sh_type; Elf64_Xword sh_flags; Elf64_Addr sh_addr; Elf64_Off sh_offset; Elf64_Xword sh_size; Elf64_Word sh_link; Elf64_Word sh_info; Elf64_Xword sh_addralign; Elf64_Xword sh_entsize; } Elf64_Shdr; /** * Binding types. * Contained in the high four bits of @p st_info */ #define STB_LOCAL 0 /**< @brief Not visible outside the object file. */ #define STB_GLOBAL 1 /**< @brief Global symbol, visible to all object files. */ #define STB_WEAK 2 /**< @brief Global scope, but with lower precedence than global symbols. */ #define STB_LOOS 10 #define STB_HIOS 12 #define STB_LOPROC 13 #define STB_HIPROC 15 /** * Symbol types. * Contained in the low four bits of @p st_info */ #define STT_NOTYPE 0 /**< @brief No type specified (e.g., an absolute symbol) */ #define STT_OBJECT 1 /**< @brief Data object */ #define STT_FUNC 2 /**< @brief Function entry point */ #define STT_SECTION 3 /**< @brief Symbol is associated with a section */ #define STT_FILE 4 /**< @brief Source file associated with the object */ #define STT_LOOS 10 #define STT_HIOS 12 #define STT_LOPROC 13 #define STT_HIPROC 15 typedef struct Elf64_Sym { Elf64_Word st_name; /**< @brief Symbol name */ unsigned char st_info; /**< @brief Type and binding attributes */ unsigned char st_other; /**< @brief Reserved */ Elf64_Half st_shndx; /**< @brief Section table index */ Elf64_Addr st_value; /**< @brief Symbol value */ Elf64_Xword st_size; /**< @brief Size of object (e.g., common) */ } Elf64_Sym; /** * Relocations */ #define ELF64_R_SYM(i) ((i) >> 32) #define ELF64_R_TYPE(i) ((i) & 0xFFFFFFFFL) #define ELF64_R_INFO(s,t) (((s) << 32) + ((t) & 0xFFFFFFFFL)) typedef struct Elf64_Rel { Elf64_Addr r_offset; /**< @brief Address of reference */ Elf64_Xword r_info; /**< @brief Symbol index and type of relocation */ } Elf64_Rel; typedef struct Elf64_Rela { Elf64_Addr r_offset; /**< @brief Address of reference */ Elf64_Xword r_info; /**< @brief Symbol index and type of relocation */ Elf64_Sxword r_addend; /**< @brief Constant part of expression */ } Elf64_Rela; /** * x86-64 SysV Relocation types */ #define R_X86_64_NONE 0 /**< @brief @p none none */ #define R_X86_64_64 1 /**< @brief @p word64 S + A */ #define R_X86_64_PC32 2 /**< @brief @p word32 S + A - P */ #define R_X86_64_GOT32 3 /**< @brief @p word32 G + A */ #define R_X86_64_PLT32 4 /**< @brief @p word32 L + A - P */ #define R_X86_64_COPY 5 /**< @brief @p none none */ #define R_X86_64_GLOB_DAT 6 /**< @brief @p word64 S */ #define R_X86_64_JUMP_SLOT 7 /**< @brief @p word64 S */ #define R_X86_64_RELATIVE 8 /**< @brief @p word64 B + A */ #define R_X86_64_GOTPCREL 9 /**< @brief @p word32 G + GOT + A - P */ #define R_X86_64_32 10 /**< @brief @p word32 S + A */ #define R_X86_64_32S 11 /**< @brief @p word32 S + A */ /* vvv These should not appear in a valid file */ #define R_X86_64_16 12 /**< @brief @p word16 S + A */ #define R_X86_64_PC16 13 /**< @brief @p word16 S + A - P */ #define R_X86_64_8 14 /**< @brief @p word8 S + A */ #define R_X86_64_PC8 15 /**< @brief @p word8 S + A - P */ /* ^^^ These should not appear in a valid file */ #define R_X86_64_DTPMOD64 16 /**< @brief @p word64 */ #define R_X86_64_DTPOFF64 17 /**< @brief @p word64 */ #define R_X86_64_TPOFF64 18 /**< @brief @p word64 */ #define R_X86_64_TLSGD 19 /**< @brief @p word32 */ #define R_X86_64_TLSLD 20 /**< @brief @p word32 */ #define R_X86_64_DTPOFF32 21 /**< @brief @p word32 */ #define R_X86_64_GOTTPOFF 22 /**< @brief @p word32 */ #define R_X86_64_TPOFF32 23 /**< @brief @p word32 */ #define R_X86_64_PC64 24 /**< @brief @p word64 S + A - P */ #define R_X86_64_GOTOFF64 25 /**< @brief @p word64 S + A - GOT */ #define R_X86_64_GOTPC32 26 /**< @brief @p word32 GOT + A - P */ /* Large model */ #define R_X86_64_GOT64 27 /**< @brief @p word64 G + A */ #define R_X86_64_GOTPCREL64 28 /**< @brief @p word64 G + GOT - P + A */ #define R_X86_64_GOTPC64 29 /**< @brief @p word64 GOT - P + A */ #define R_X86_64_GOTPLT64 30 /**< @brief @p word64 G + A */ #define R_X86_64_PLTOFF64 31 /**< @brief @p word64 L - GOT + A */ /* ... */ #define R_X86_64_SIZE32 32 /**< @brief @p word32 Z + A */ #define R_X86_64_SIZE64 33 /**< @brief @p word64 Z + A */ #define R_X86_64_GOTPC32_TLSDESC 34 /**< @brief @p word32 */ #define R_X86_64_TLSDESC_CALL 35 /**< @brief @p none */ #define R_X86_64_TLSDESC 36 /**< @brief @p word64*2 */ #define R_X86_64_IRELATIVE 37 /**< @brief @p word64 indirect (B + A) */ #define R_AARCH64_COPY 1024 #define R_AARCH64_GLOB_DAT 1025 /** * Program header types */ #define PT_NULL 0 #define PT_LOAD 1 #define PT_DYNAMIC 2 #define PT_INTERP 3 #define PT_NOTE 4 #define PT_SHLIB 5 #define PT_PHDR 6 #define PT_TLS 7 #define PT_LOOS 0x60000000 #define PT_HIOS 0x6FFFFFFF #define PT_LOPROC 0x70000000 #define PT_HIPROC 0x7FFFFFFF /* From the SysV x86-64 ABI */ #define PT_GNU_EH_FRAME 0x6474e550 #define PT_SUNW_EH_FRAME 0x6474e550 #define PT_SUNW_UNWIND 0x6464e550 /** * Program header flags */ #define PF_X 0x01 #define PF_W 0x02 #define PF_R 0x04 #define PF_MASKOS 0x00FF0000 #define PF_MAKSPROC 0xFF000000 typedef struct Elf64_Phdr { Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf64_Xword p_filesz; Elf64_Xword p_memsz; Elf64_Xword p_align; } Elf64_Phdr; /** * Dynamic table */ #define DT_NULL 0 #define DT_NEEDED 1 #define DT_PLTRELSZ 2 #define DT_PLTGOT 3 #define DT_HASH 4 #define DT_STRTAB 5 #define DT_SYMTAB 6 #define DT_RELA 7 #define DT_RELASZ 8 #define DT_RELAENT 9 #define DT_STRSZ 10 #define DT_SYMENT 11 #define DT_INIT 12 #define DT_FINI 13 #define DT_SONAME 14 #define DT_RPATH 15 #define DT_SYMBOLIC 16 #define DT_REL 17 #define DT_RELSZ 18 #define DT_RELENT 19 #define DT_PLTREL 20 #define DT_DEBUG 21 #define DT_TEXTREL 22 #define DT_JMPREL 23 #define DT_BIND_NOW 24 #define DT_INIT_ARRAY 25 #define DT_FINI_ARRAY 26 #define DT_INIT_ARRAYSZ 27 #define DT_FINI_ARRAYSZ 28 #define DT_LOOS 0x60000000 #define DT_HIOS 0x6FFFFFFF #define DT_LOPROC 0x70000000 #define DT_HIPROC 0x7FFFFFFF typedef struct Elf64_Dyn { Elf64_Sxword d_tag; union { Elf64_Xword d_val; Elf64_Addr d_ptr; } d_un; } Elf64_Dyn; ================================================ FILE: base/usr/include/kernel/generic.h ================================================ #pragma once /** * @brief Initialize early subsystems. * * Should be called after the architecture-specific startup routine has * enabled all hardware required for tasking switching and memory management. * * Initializes the scheduler, shared memory subsystem, and virtual file system; * mounts generic device drivers, sets up the virtual /dev directory, parses * the architecture-provided kernel arguments, and enables scheduling by * launching the idle task and converting the current context to 'init'. */ void generic_startup(void); /** * @brief Starts init. * * Should be called after all architecture-specific initialization is completed. * Parses the boot arguments and executes /bin/init. */ int generic_main(void); ================================================ FILE: base/usr/include/kernel/gzip.h ================================================ /** * @brief Kernel gzip decompressor * * This is a slimmed down version of libtoaru_inflate; it's an implementation * of the tinf algorithm for decompressing gzip/DEFLATE payloads, with a * very straightforward API: Point @c gzip_inputPtr at your gzip data, * point @c gzip_outputPtr where you want the output to go, and then * run @c gzip_decompress(). */ #pragma once #include extern int gzip_decompress(void); extern uint8_t * gzip_inputPtr; extern uint8_t * gzip_outputPtr; ================================================ FILE: base/usr/include/kernel/hashmap.h ================================================ #pragma once #include #include #include typedef unsigned int (*hashmap_hash_t) (const void * key); typedef int (*hashmap_comp_t) (const void * a, const void * b); typedef void (*hashmap_free_t) (void *); typedef void * (*hashmap_dupe_t) (const void *); typedef struct hashmap_entry { char * key; void * value; struct hashmap_entry * next; } hashmap_entry_t; typedef struct hashmap { hashmap_hash_t hash_func; hashmap_comp_t hash_comp; hashmap_dupe_t hash_key_dup; hashmap_free_t hash_key_free; hashmap_free_t hash_val_free; size_t size; hashmap_entry_t ** entries; } hashmap_t; extern hashmap_t * hashmap_create(int size); extern hashmap_t * hashmap_create_int(int size); extern void * hashmap_set(hashmap_t * map, const void * key, void * value); extern void * hashmap_get(hashmap_t * map, const void * key); extern void * hashmap_remove(hashmap_t * map, const void * key); extern int hashmap_has(hashmap_t * map, const void * key); extern list_t * hashmap_keys(hashmap_t * map); extern list_t * hashmap_values(hashmap_t * map); extern void hashmap_free(hashmap_t * map); extern unsigned int hashmap_string_hash(const void * key); extern int hashmap_string_comp(const void * a, const void * b); extern void * hashmap_string_dupe(const void * key); extern int hashmap_is_empty(hashmap_t * map); ================================================ FILE: base/usr/include/kernel/ksym.h ================================================ #pragma once #include extern void ksym_install(void); extern void ksym_bind(const char * symname, void * value); extern void * ksym_lookup(const char * symname); extern list_t * ksym_list(void); extern hashmap_t * ksym_get_map(void); ================================================ FILE: base/usr/include/kernel/list.h ================================================ /** * @brief General-purpose doubly-linked list. */ #pragma once #include #include typedef struct node { struct node * next; struct node * prev; void * value; struct ListHeader * owner; } __attribute__((packed)) node_t; typedef struct ListHeader { node_t * head; node_t * tail; size_t length; const char * name; const void * metadata; } __attribute__((packed)) list_t; extern void list_destroy(list_t * list); extern void list_free(list_t * list); extern void list_append(list_t * list, node_t * item); extern node_t * list_insert(list_t * list, void * item); extern list_t * list_create(const char * name, const void * metadata); extern node_t * list_find(list_t * list, void * value); extern int list_index_of(list_t * list, void * value); extern void list_remove(list_t * list, size_t index); extern void list_delete(list_t * list, node_t * node); extern node_t * list_pop(list_t * list); extern node_t * list_dequeue(list_t * list); extern list_t * list_copy(list_t * original); extern void list_merge(list_t * target, list_t * source); extern void * list_index(list_t * list, int index); extern void list_append_after(list_t * list, node_t * before, node_t * node); extern node_t * list_insert_after(list_t * list, node_t * before, void * item); extern void list_append_before(list_t * list, node_t * after, node_t * node); extern node_t * list_insert_before(list_t * list, node_t * after, void * item); /* Known to conflict with some popular third-party libraries. */ #ifndef TOARU_LIST_NO_FOREACH # define foreach(i, list) for (node_t * i = (list)->head; i != NULL; i = i->next) # define foreachr(i, list) for (node_t * i = (list)->tail; i != NULL; i = i->prev) #endif ================================================ FILE: base/usr/include/kernel/misc.h ================================================ #pragma once #include size_t arch_cpu_mhz(void); const char * arch_get_cmdline(void); const char * arch_get_loader(void); void arch_pause(void); void arch_fatal(void); void arch_set_tls_base(uintptr_t tlsbase); long arch_reboot(void); void arch_fatal_prepare(void); void arch_dump_traceback(void); ================================================ FILE: base/usr/include/kernel/mmu.h ================================================ #pragma once #include #if defined(__x86_64__) #include #elif defined(__aarch64__) #include #endif #define KERNEL_HEAP_START 0xFFFFff0000000000UL #define MMIO_BASE_START 0xffffff1fc0000000UL #define HIGH_MAP_REGION 0xffffff8000000000UL #define MODULE_BASE_START 0xffffffff80000000UL #define USER_SHM_LOW 0x0000400100000000UL #define USER_SHM_HIGH 0x0000500000000000UL #define USER_DEVICE_MAP 0x0000400000000000UL #define MMU_FLAG_KERNEL 0x01 #define MMU_FLAG_WRITABLE 0x02 #define MMU_FLAG_NOCACHE 0x04 #define MMU_FLAG_WRITETHROUGH 0x08 #define MMU_FLAG_SPEC 0x10 #define MMU_FLAG_WC (MMU_FLAG_NOCACHE | MMU_FLAG_WRITETHROUGH | MMU_FLAG_SPEC) #define MMU_FLAG_NOEXECUTE 0x20 #define MMU_GET_MAKE 0x01 #define MMU_PTR_NULL 1 #define MMU_PTR_WRITE 2 void mmu_frame_set(uintptr_t frame_addr); void mmu_frame_clear(uintptr_t frame_addr); void mmu_frame_release(uintptr_t frame_addr); int mmu_frame_test(uintptr_t frame_addr); uintptr_t mmu_first_n_frames(int n); uintptr_t mmu_first_frame(void); void mmu_frame_allocate(union PML * page, unsigned int flags); void mmu_frame_map_address(union PML * page, unsigned int flags, uintptr_t physAddr); void mmu_frame_free(union PML * page); uintptr_t mmu_map_to_physical(union PML * root, uintptr_t virtAddr); union PML * mmu_get_page(uintptr_t virtAddr, int flags); void mmu_set_directory(union PML * new_pml); void mmu_free(union PML * from); union PML * mmu_clone(union PML * from); void mmu_invalidate(uintptr_t addr); uintptr_t mmu_allocate_a_frame(void); uintptr_t mmu_allocate_n_frames(int n); union PML * mmu_get_kernel_directory(void); void * mmu_map_from_physical(uintptr_t frameaddress); void * mmu_map_mmio_region(uintptr_t physical_address, size_t size); void * mmu_map_module(size_t size); void mmu_unmap_module(uintptr_t base_address, size_t size); size_t mmu_count_user(union PML * from); size_t mmu_count_shm(union PML * from); size_t mmu_total_memory(void); size_t mmu_used_memory(void); void * sbrk(size_t); union PML * mmu_get_page_other(union PML * root, uintptr_t virtAddr); int mmu_validate_user_pointer(const void * addr, size_t size, int flags); ================================================ FILE: base/usr/include/kernel/mod/rtl.h ================================================ #ifndef KERNEL_MOD_RTL_H #define KERNEL_MOD_RTL_H #endif ================================================ FILE: base/usr/include/kernel/mod/shell.h ================================================ #ifndef KERNEL_MOD_SHELL_H #define KERNEL_MOD_SHELL_H #include /* * We're going to have a list of shell commands. * We'll search through it linearly because I don't * care to write a hashmap right now. Maybe later. */ struct shell_command { char * name; int (*function) (fs_node_t * tty, int argc, char * argv[]); char * description; }; extern void debug_shell_install(struct shell_command * sh); extern int debug_shell_readline(fs_node_t * dev, char * linebuf, int max); extern void tty_set_buffered(fs_node_t * dev); extern void tty_set_unbuffered(fs_node_t * dev); #define DEFINE_SHELL_FUNCTION(n, desc) \ static int shell_ ## n (fs_node_t * tty, int argc, char * argv[]); \ static struct shell_command shell_ ## n ## _desc = { \ .name = #n , \ .function = &shell_ ## n , \ .description = desc \ }; \ static int shell_ ## n (fs_node_t * tty, int argc, char * argv[]) #define BIND_SHELL_FUNCTION(name) \ debug_shell_install(&shell_ ## name ## _desc); #endif ================================================ FILE: base/usr/include/kernel/mod/snd.h ================================================ #ifndef KERNEL_MOD_SND_H #define KERNEL_MOD_SND_H /* The format isn't really used for anything right now */ #define SND_FORMAT_L16SLE 0 /* Linear 16-bit signed little endian */ #include #include #define SND_KNOB_VENDOR 1024 typedef uint16_t snd_mixer_enum_t; typedef struct snd_knob { char name[SND_KNOB_NAME_SIZE]; uint32_t id; } snd_knob_t; typedef struct snd_device { char name[256]; /* Name of the device. */ void * device; /* Private data for the device. May be NULL. */ uint32_t playback_speed; /* Playback speed in Hz */ uint32_t playback_format; /* Playback format (SND_FORMAT_*) */ snd_knob_t *knobs; uint32_t num_knobs; int (*mixer_read)(uint32_t knob_id, uint32_t *val); int (*mixer_write)(uint32_t knob_id, uint32_t val); uint32_t id; } snd_device_t; /* * Register a device to be used with snd */ int snd_register(snd_device_t * device); /* * Unregister a device */ int snd_unregister(snd_device_t * device); /* * Request a buffer to play from snd. This is to be called from the device in * order to fill a buffer on demand. After the call the buffer is garaunteed * to be filled to the size requested even if that means writing zeroes for * when there are no other samples. */ int snd_request_buf(snd_device_t * device, uint32_t size, uint8_t *buffer); #endif /* KERNEL_MOD_SND_H */ ================================================ FILE: base/usr/include/kernel/mod/sound.h ================================================ #pragma once #include #define SND_MAX_KNOBS 256 #define SND_KNOB_NAME_SIZE 256 #define SND_KNOB_MAX_VALUE UINT32_MAX #define SND_KNOB_MASTER 0 #define SND_DEVICE_MAIN 0 typedef struct snd_knob_list { uint32_t device; /* IN */ uint32_t num; /* OUT */ uint32_t ids[SND_MAX_KNOBS]; /* OUT */ } snd_knob_list_t; typedef struct snd_knob_info { uint32_t device; /* IN */ uint32_t id; /* IN */ char name[SND_KNOB_NAME_SIZE]; /* OUT */ } snd_knob_info_t; typedef struct snd_knob_value { uint32_t device; /* IN */ uint32_t id; /* IN */ uint32_t val; /* OUT for SND_MIXER_READ_KNOB, IN for SND_MIXER_WRITE_KNOB */ } snd_knob_value_t; /* IOCTLs */ #define SND_MIXER_GET_KNOBS 0 #define SND_MIXER_GET_KNOB_INFO 1 #define SND_MIXER_READ_KNOB 2 #define SND_MIXER_WRITE_KNOB 3 ================================================ FILE: base/usr/include/kernel/module.h ================================================ #pragma once #include struct Module { const char * name; int (*init)(int argc, char * argv[]); int (*fini)(void); }; struct LoadedModule { struct Module * metadata; uintptr_t baseAddress; size_t fileSize; size_t loadedSize; }; hashmap_t * modules_get_list(void); ================================================ FILE: base/usr/include/kernel/mouse.h ================================================ #pragma once #include typedef enum { LEFT_CLICK = 0x01, RIGHT_CLICK = 0x02, MIDDLE_CLICK = 0x04, MOUSE_SCROLL_UP = 0x10, MOUSE_SCROLL_DOWN = 0x20, } mouse_click_t; typedef struct { uint32_t magic; int32_t x_difference; int32_t y_difference; mouse_click_t buttons; } mouse_device_packet_t; #define MOUSE_MAGIC 0xFEED1234 ================================================ FILE: base/usr/include/kernel/multiboot.h ================================================ #pragma once #include #define MULTIBOOT_MAGIC 0x1BADB002 #define MULTIBOOT_EAX_MAGIC 0x2BADB002 #define MULTIBOOT_FLAG_MEM 0x001 #define MULTIBOOT_FLAG_DEVICE 0x002 #define MULTIBOOT_FLAG_CMDLINE 0x004 #define MULTIBOOT_FLAG_MODS 0x008 #define MULTIBOOT_FLAG_AOUT 0x010 #define MULTIBOOT_FLAG_ELF 0x020 #define MULTIBOOT_FLAG_MMAP 0x040 #define MULTIBOOT_FLAG_DRIVER 0x080 #define MULTIBOOT_FLAG_CONFIG 0x100 #define MULTIBOOT_FLAG_LOADER 0x200 #define MULTIBOOT_FLAG_APM 0x400 #define MULTIBOOT_FLAG_VBE 0x800 #define MULTIBOOT_FLAG_FB 0x1000 struct multiboot { uint32_t flags; uint32_t mem_lower; uint32_t mem_upper; uint32_t boot_device; uint32_t cmdline; uint32_t mods_count; uint32_t mods_addr; uint32_t num; uint32_t size; uint32_t addr; uint32_t shndx; uint32_t mmap_length; uint32_t mmap_addr; uint32_t drives_length; uint32_t drives_addr; uint32_t config_table; uint32_t boot_loader_name; uint32_t apm_table; uint32_t vbe_control_info; uint32_t vbe_mode_info; uint16_t vbe_mode; uint16_t vbe_interface_seg; uint16_t vbe_interface_off; uint16_t vbe_interface_len; uint64_t framebuffer_addr; uint32_t framebuffer_pitch; uint32_t framebuffer_width; uint32_t framebuffer_height; uint8_t framebuffer_bpp; uint8_t framebuffer_type; } __attribute__ ((packed)); typedef struct { uint16_t attributes; uint8_t winA, winB; uint16_t granularity; uint16_t winsize; uint16_t segmentA, segmentB; uint32_t realFctPtr; uint16_t pitch; uint16_t Xres, Yres; uint8_t Wchar, Ychar, planes, bpp, banks; uint8_t memory_model, bank_size, image_pages; uint8_t reserved0; uint8_t red_mask, red_position; uint8_t green_mask, green_position; uint8_t blue_mask, blue_position; uint8_t rsv_mask, rsv_position; uint8_t directcolor_attributes; uint32_t physbase; uint32_t reserved1; uint16_t reserved2; } __attribute__ ((packed)) vbe_info_t; typedef struct { uint32_t mod_start; uint32_t mod_end; uint32_t cmdline; uint32_t reserved; } __attribute__ ((packed)) mboot_mod_t; typedef struct { uint32_t size; uint64_t base_addr; uint64_t length; uint32_t type; } __attribute__ ((packed)) mboot_memmap_t; ================================================ FILE: base/usr/include/kernel/mutex.h ================================================ /** * Mutex that sleeps... and can be owned across sleeping... * * @copyright 2014-2021 K. Lange * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md */ #include #include #include typedef struct { spin_lock_t inner_lock; volatile int status; process_t * owner; list_t * waiters; } sched_mutex_t; extern sched_mutex_t * mutex_init(const char * name); extern int mutex_acquire(sched_mutex_t * mutex); extern int mutex_release(sched_mutex_t * mutex); ================================================ FILE: base/usr/include/kernel/net/e1000.h ================================================ #pragma once #include #define E1000_REG_CTRL 0x0000 #define E1000_REG_STATUS 0x0008 #define E1000_REG_EEPROM 0x0014 #define E1000_REG_CTRL_EXT 0x0018 #define E1000_REG_ICR 0x00C0 #define E1000_REG_ITR 0x00c4 #define E1000_REG_IMS 0x00d0 #define E1000_REG_IMC 0x00d8 #define E1000_REG_RCTRL 0x0100 #define E1000_REG_RXDESCLO 0x2800 #define E1000_REG_RXDESCHI 0x2804 #define E1000_REG_RXDESCLEN 0x2808 #define E1000_REG_RXDESCHEAD 0x2810 #define E1000_REG_RXDESCTAIL 0x2818 #define E1000_REG_RDTR 0x2820 #define E1000_REG_TCTRL 0x0400 #define E1000_REG_TXDESCLO 0x3800 #define E1000_REG_TXDESCHI 0x3804 #define E1000_REG_TXDESCLEN 0x3808 #define E1000_REG_TXDESCHEAD 0x3810 #define E1000_REG_TXDESCTAIL 0x3818 #define E1000_REG_RXADDR 0x5400 #define E1000_NUM_RX_DESC 512 #define E1000_NUM_TX_DESC 512 #define RCTL_EN (1 << 1) /* Receiver Enable */ #define RCTL_SBP (1 << 2) /* Store Bad Packets */ #define RCTL_UPE (1 << 3) /* Unicast Promiscuous Enabled */ #define RCTL_MPE (1 << 4) /* Multicast Promiscuous Enabled */ #define RCTL_LPE (1 << 5) /* Long Packet Reception Enable */ #define RCTL_LBM_NONE (0 << 6) /* No Loopback */ #define RCTL_LBM_PHY (3 << 6) /* PHY or external SerDesc loopback */ #define RCTL_RDMTS_HALF (0 << 8) /* Free Buffer Threshold is 1/2 of RDLEN */ #define RCTL_RDMTS_QUARTER (1 << 8) /* Free Buffer Threshold is 1/4 of RDLEN */ #define RCTL_RDMTS_EIGHTH (2 << 8) /* Free Buffer Threshold is 1/8 of RDLEN */ #define RCTL_MO_36 (0 << 12) /* Multicast Offset - bits 47:36 */ #define RCTL_MO_35 (1 << 12) /* Multicast Offset - bits 46:35 */ #define RCTL_MO_34 (2 << 12) /* Multicast Offset - bits 45:34 */ #define RCTL_MO_32 (3 << 12) /* Multicast Offset - bits 43:32 */ #define RCTL_BAM (1 << 15) /* Broadcast Accept Mode */ #define RCTL_VFE (1 << 18) /* VLAN Filter Enable */ #define RCTL_CFIEN (1 << 19) /* Canonical Form Indicator Enable */ #define RCTL_CFI (1 << 20) /* Canonical Form Indicator Bit Value */ #define RCTL_DPF (1 << 22) /* Discard Pause Frames */ #define RCTL_PMCF (1 << 23) /* Pass MAC Control Frames */ #define RCTL_SECRC (1 << 26) /* Strip Ethernet CRC */ #define RCTL_BSIZE_256 (3 << 16) #define RCTL_BSIZE_512 (2 << 16) #define RCTL_BSIZE_1024 (1 << 16) #define RCTL_BSIZE_2048 (0 << 16) #define RCTL_BSIZE_4096 ((3 << 16) | (1 << 25)) #define RCTL_BSIZE_8192 ((2 << 16) | (1 << 25)) #define RCTL_BSIZE_16384 ((1 << 16) | (1 << 25)) #define TCTL_EN (1 << 1) /* Transmit Enable */ #define TCTL_PSP (1 << 3) /* Pad Short Packets */ #define TCTL_CT_SHIFT 4 /* Collision Threshold */ #define TCTL_COLD_SHIFT 12 /* Collision Distance */ #define TCTL_SWXOFF (1 << 22) /* Software XOFF Transmission */ #define TCTL_RTLC (1 << 24) /* Re-transmit on Late Collision */ #define CMD_EOP (1 << 0) /* End of Packet */ #define CMD_IFCS (1 << 1) /* Insert FCS */ #define CMD_IC (1 << 2) /* Insert Checksum */ #define CMD_RS (1 << 3) /* Report Status */ #define CMD_RPS (1 << 4) /* Report Packet Sent */ #define CMD_VLE (1 << 6) /* VLAN Packet Enable */ #define CMD_IDE (1 << 7) /* Interrupt Delay Enable */ #define ICR_TXDW (1 << 0) #define ICR_TXQE (1 << 1) /* Transmit queue is empty */ #define ICR_LSC (1 << 2) /* Link status changed */ #define ICR_RXSEQ (1 << 3) /* Receive sequence count error */ #define ICR_RXDMT0 (1 << 4) /* Receive descriptor minimum threshold */ /* what's 5 (0x20)? */ #define ICR_RXO (1 << 6) /* Receive overrun */ #define ICR_RXT0 (1 << 7) /* Receive timer interrupt? */ #define ICR_ACK (1 << 17) #define ICR_SRPD (1 << 16) struct e1000_rx_desc { volatile uint64_t addr; volatile uint16_t length; volatile uint16_t checksum; volatile uint8_t status; volatile uint8_t errors; volatile uint16_t special; } __attribute__((packed)); /* this looks like it should pack fine as-is */ struct e1000_tx_desc { volatile uint64_t addr; volatile uint16_t length; volatile uint8_t cso; volatile uint8_t cmd; volatile uint8_t status; volatile uint8_t css; volatile uint16_t special; } __attribute__((packed)); ================================================ FILE: base/usr/include/kernel/net/eth.h ================================================ #pragma once #include #define ETHERNET_TYPE_IPV4 0x0800 #define ETHERNET_TYPE_ARP 0x0806 #define ETHERNET_BROADCAST_MAC (uint8_t[]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} #define MAC_FORMAT "%02x:%02x:%02x:%02x:%02x:%02x" #define FORMAT_MAC(m) (m)[0], (m)[1], (m)[2], (m)[3], (m)[4], (m)[5] struct ethernet_packet { uint8_t destination[6]; uint8_t source[6]; uint16_t type; uint8_t payload[]; } __attribute__((packed)) __attribute__((aligned(2))); void net_eth_handle(struct ethernet_packet * frame, fs_node_t * nic, size_t size); struct EthernetDevice { char if_name[32]; uint8_t mac[6]; size_t mtu; /* XXX: just to get things going */ uint32_t ipv4_addr; uint32_t ipv4_subnet; uint32_t ipv4_gateway; uint8_t ipv6_addr[16]; /* TODO: Address lists? */ fs_node_t * device_node; }; void net_eth_send(struct EthernetDevice *, size_t, void*, uint16_t, uint8_t*); struct ArpCacheEntry { uint8_t hwaddr[6]; uint16_t flags; struct EthernetDevice * iface; }; struct ArpCacheEntry * net_arp_cache_get(uint32_t addr); void net_arp_cache_add(struct EthernetDevice * iface, uint32_t addr, uint8_t * hwaddr, uint16_t flags); void net_arp_ask(uint32_t addr, fs_node_t * fsnic); ================================================ FILE: base/usr/include/kernel/net/ipv4.h ================================================ #pragma once #include struct ipv4_packet { uint8_t version_ihl; uint8_t dscp_ecn; uint16_t length; uint16_t ident; uint16_t flags_fragment; uint8_t ttl; uint8_t protocol; uint16_t checksum; uint32_t source; uint32_t destination; uint8_t payload[]; } __attribute__ ((packed)) __attribute__((aligned(2))); struct icmp_header { uint8_t type; uint8_t code; uint16_t csum; uint16_t identifier; uint8_t data[]; } __attribute__((packed)) __attribute__((aligned(2))); struct udp_packet { uint16_t source_port; uint16_t destination_port; uint16_t length; uint16_t checksum; uint8_t payload[]; } __attribute__ ((packed)) __attribute__((aligned(2))); struct tcp_header { uint16_t source_port; uint16_t destination_port; uint32_t seq_number; uint32_t ack_number; uint16_t flags; uint16_t window_size; uint16_t checksum; uint16_t urgent; uint8_t payload[]; } __attribute__((packed)) __attribute__((aligned(2))); struct tcp_check_header { uint32_t source; uint32_t destination; uint8_t zeros; uint8_t protocol; uint16_t tcp_len; uint8_t tcp_header[]; }; #define IPV4_PROT_UDP 17 #define IPV4_PROT_TCP 6 ================================================ FILE: base/usr/include/kernel/net/netif.h ================================================ #pragma once #include #include #define htonl(l) ( (((l) & 0xFF) << 24) | (((l) & 0xFF00) << 8) | (((l) & 0xFF0000) >> 8) | (((l) & 0xFF000000) >> 24)) #define htons(s) ( (((s) & 0xFF) << 8) | (((s) & 0xFF00) >> 8) ) #define ntohl(l) htonl((l)) #define ntohs(s) htons((s)) int net_add_interface(const char * name, fs_node_t * deviceNode); fs_node_t * net_if_lookup(const char * name); fs_node_t * net_if_route(uint32_t addr); typedef struct SockData { fs_node_t _fnode; spin_lock_t alert_lock; spin_lock_t rx_lock; list_t * alert_wait; list_t * rx_wait; list_t * rx_queue; uint16_t priv[4]; long (*sock_recv)(struct SockData * sock, struct msghdr * msg, int flags); long (*sock_send)(struct SockData * sock, const struct msghdr *msg, int flags); void (*sock_close)(struct SockData * sock); long (*sock_connect)(struct SockData * sock, const struct sockaddr *addr, socklen_t addrlen); long (*sock_bind)(struct SockData * sock, const struct sockaddr *addr, socklen_t addrlen); long (*sock_getsockname)(struct SockData * sock, struct sockaddr *addr, socklen_t *addrlen); long (*sock_getpeername)(struct SockData * sock, struct sockaddr *addr, socklen_t *addrlen); struct sockaddr dest; uint32_t priv32[4]; size_t unread; char * buf; int nonblocking; } sock_t; void net_sock_alert(sock_t * sock); void net_sock_add(sock_t * sock, void * frame, size_t size); void * net_sock_get(sock_t * sock); sock_t * net_sock_create(void); extern long net_socket(int,int,int); extern long net_setsockopt(int,int,int,const void*,socklen_t); extern long net_bind(int, const struct sockaddr*, socklen_t); extern long net_accept(int, struct sockaddr*, socklen_t*); extern long net_listen(int,int); extern long net_connect(int, const struct sockaddr*, socklen_t); extern long net_getsockopt(int,int,int,void*,socklen_t*); extern long net_recv(int,struct msghdr*,int); extern long net_send(int, const struct msghdr*, int); extern long net_shutdown(int, int); extern long net_getsockname(int,struct sockaddr*,socklen_t*); extern long net_getpeername(int,struct sockaddr*,socklen_t*); ================================================ FILE: base/usr/include/kernel/pci.h ================================================ #pragma once #include #define PCI_VENDOR_ID 0x00 // 2 #define PCI_DEVICE_ID 0x02 // 2 #define PCI_COMMAND 0x04 // 2 #define PCI_STATUS 0x06 // 2 #define PCI_REVISION_ID 0x08 // 1 #define PCI_PROG_IF 0x09 // 1 #define PCI_SUBCLASS 0x0a // 1 #define PCI_CLASS 0x0b // 1 #define PCI_CACHE_LINE_SIZE 0x0c // 1 #define PCI_LATENCY_TIMER 0x0d // 1 #define PCI_HEADER_TYPE 0x0e // 1 #define PCI_BIST 0x0f // 1 #define PCI_BAR0 0x10 // 4 #define PCI_BAR1 0x14 // 4 #define PCI_BAR2 0x18 // 4 #define PCI_BAR3 0x1C // 4 #define PCI_BAR4 0x20 // 4 #define PCI_BAR5 0x24 // 4 #define PCI_INTERRUPT_LINE 0x3C // 1 #define PCI_INTERRUPT_PIN 0x3D #define PCI_SECONDARY_BUS 0x19 // 1 #define PCI_HEADER_TYPE_DEVICE 0 #define PCI_HEADER_TYPE_BRIDGE 1 #define PCI_HEADER_TYPE_CARDBUS 2 #define PCI_TYPE_BRIDGE 0x0604 #define PCI_TYPE_SATA 0x0106 #define PCI_ADDRESS_PORT 0xCF8 #define PCI_VALUE_PORT 0xCFC #define PCI_NONE 0xFFFF typedef void (*pci_func_t)(uint32_t device, uint16_t vendor_id, uint16_t device_id, void * extra); static inline int pci_extract_bus(uint32_t device) { return (uint8_t)((device >> 16)); } static inline int pci_extract_slot(uint32_t device) { return (uint8_t)((device >> 8)); } static inline int pci_extract_func(uint32_t device) { return (uint8_t)(device); } static inline uint32_t pci_get_addr(uint32_t device, int field) { return 0x80000000 | (pci_extract_bus(device) << 16) | (pci_extract_slot(device) << 11) | (pci_extract_func(device) << 8) | ((field) & 0xFC); } static inline uint32_t pci_box_device(int bus, int slot, int func) { return (uint32_t)((bus << 16) | (slot << 8) | func); } uint32_t pci_read_field(uint32_t device, int field, int size); void pci_write_field(uint32_t device, int field, int size, uint32_t value); uint16_t pci_find_type(uint32_t dev); void pci_scan_func(pci_func_t f, int type, int bus, int slot, int func, void * extra); void pci_scan_slot(pci_func_t f, int type, int bus, int slot, void * extra); void pci_scan_bus(pci_func_t f, int type, int bus, void * extra); void pci_scan(pci_func_t f, int type, void * extra); void pci_remap(void); int pci_get_interrupt(uint32_t device); ================================================ FILE: base/usr/include/kernel/pipe.h ================================================ #pragma once #include #include #include #include typedef struct _pipe_device { uint8_t * buffer; size_t write_ptr; size_t read_ptr; size_t size; size_t refcount; list_t * wait_queue_readers; list_t * wait_queue_writers; int dead; list_t * alert_waiters; spin_lock_t lock_read; spin_lock_t lock_write; spin_lock_t alert_lock; spin_lock_t wait_lock; spin_lock_t ptr_lock; } pipe_device_t; fs_node_t * make_pipe(size_t size); int pipe_size(fs_node_t * node); int pipe_unsize(fs_node_t * node); ================================================ FILE: base/usr/include/kernel/printf.h ================================================ #pragma once #include #include __attribute__((format(__printf__,1,2))) extern int printf(const char *fmt, ...); extern size_t (*printf_output)(size_t, uint8_t *); __attribute__((format(__printf__,3,4))) extern int snprintf(char * str, size_t size, const char * format, ...); extern size_t xvasprintf(int (*callback)(void *, char), void * userData, const char * fmt, va_list args); __attribute__((format(__printf__,1,2))) extern int dprintf(const char *fmt, ...); extern void console_set_output(size_t (*output)(size_t,uint8_t*)); ================================================ FILE: base/usr/include/kernel/process.h ================================================ #pragma once #include #include #include #include #include #include #include #include #include #include #ifdef __x86_64__ #include #endif #ifdef __aarch64__ #include #endif #define PROC_REUSE_FDS 0x0001 #define KERNEL_STACK_SIZE 0x9000 #define USER_ROOT_UID 0 typedef struct { intptr_t refcount; union PML * directory; spin_lock_t lock; } page_directory_t; typedef struct { uintptr_t sp; /* 0 */ uintptr_t bp; /* 8 */ uintptr_t ip; /* 16 */ uintptr_t tls_base; /* 24 */ #ifdef __x86_64__ uintptr_t saved[5]; /* XXX Arch dependent */ #elif defined(__aarch64__) uintptr_t saved[32]; #endif /** * 32: rbx * 40: r12 * 48: r13 * 56: r14 * 64: r15 */ } kthread_context_t; typedef struct thread { kthread_context_t context; uint8_t fp_regs[512] __attribute__((aligned(16))); page_directory_t * page_directory; } thread_t; typedef struct image { uintptr_t entry; uintptr_t heap; uintptr_t stack; uintptr_t shm_heap; uintptr_t userstack; spin_lock_t lock; } image_t; typedef struct file_descriptors { fs_node_t ** entries; uint64_t * offsets; int * modes; size_t length; size_t capacity; size_t refs; spin_lock_t lock; } fd_table_t; struct signal_config { uintptr_t handler; sigset_t mask; int flags; }; #define PROC_FLAG_IS_TASKLET 0x01 #define PROC_FLAG_FINISHED 0x02 #define PROC_FLAG_STARTED 0x04 #define PROC_FLAG_RUNNING 0x08 #define PROC_FLAG_SLEEP_INT 0x10 #define PROC_FLAG_SUSPENDED 0x20 #define PROC_FLAG_TRACE_SYSCALLS 0x40 #define PROC_FLAG_TRACE_SIGNALS 0x80 #define PROC_FLAG_RESTORE_SIGMASK 0x100 typedef struct process { pid_t id; /* PID */ pid_t group; /* thread group */ pid_t job; /* tty job */ pid_t session; /* tty session */ int status; /* status code */ unsigned int flags; /* finished, started, running, isTasklet */ int owner; uid_t user; uid_t real_user; gid_t user_group; gid_t real_user_group; unsigned int mask; char * name; char * description; char ** cmdline; char * wd_name; fs_node_t * wd_node; fd_table_t * fds; /* File descriptor table */ tree_node_t * tree_entry; struct regs * syscall_registers; list_t * wait_queue; list_t * shm_mappings; list_t * node_waits; node_t sched_node; node_t sleep_node; node_t * timed_sleep_node; node_t * timeout_node; struct timeval start; int awoken_index; thread_t thread; image_t image; spin_lock_t sched_lock; struct signal_config signals[NUMSIGNALS+1]; sigset_t blocked_signals; sigset_t pending_signals; sigset_t awaited_signals; int supplementary_group_count; gid_t * supplementary_group_list; /* Process times */ uint64_t time_prev; /* user time from previous update of usage[] */ uint64_t time_total; /* user time */ uint64_t time_sys; /* system time */ uint64_t time_in; /* tsc stamp of when this process last entered the running state */ uint64_t time_switch; /* tsc stamp of when this process last started doing system things */ uint64_t time_children; /* sum of user times from waited-for children */ uint64_t time_sys_children; /* sum of sys times from waited-for children */ uint16_t usage[4]; /* four permille samples over some period (currently 4Hz) */ /* Tracing */ pid_t tracer; spin_lock_t wait_lock; list_t * tracees; /* Syscall restarting */ long interrupted_system_call; sigset_t restored_signals; } process_t; typedef struct { uint64_t end_tick; uint64_t end_subtick; process_t * process; int is_fswait; } sleeper_t; struct ProcessorLocal { /** * @brief The running process on this core. * * The current_process is a pointer to the process struct for * the process, userspace-thread, or kernel tasklet currently * executing. Once the scheduler is active, this should always * be set. If a core is not currently doing, its current_process * should be the core's idle task. * * Because a process's data can be modified by nested interrupt * contexts, we mark them as volatile to avoid making assumptions * based on register-stored cached values. */ volatile process_t * current_process; /** * @brief Idle loop. * * This is a special kernel tasklet that sits in a loop * waiting for an interrupt from a preemption source or hardware * device. Its context should never be saved, it should never * be added to a sleep queue, and it should be scheduled whenever * there is nothing else to do. */ process_t * kernel_idle_task; /** * @brief Process this core was last scheduled to run. */ volatile process_t * previous_process; int cpu_id; union PML * current_pml; #ifdef __x86_64__ int lapic_id; /* Processor information loaded at startup. */ int cpu_model; int cpu_family; char cpu_model_name[48]; const char * cpu_manufacturer; /* 0x68 */ uintptr_t syscall_stack; /* 0x70: Should match TSS.RSP[0] */ uintptr_t user_sysret_stack; /* 0x78: Used only at start of SYSCALL entry to store user RSP before pushing it */ #endif #ifdef __aarch64__ uintptr_t sp_el1; uint64_t midr; #endif }; extern struct ProcessorLocal processor_local_data[]; extern int processor_count; /** * @brief Core-local kernel data. * * x86-64: Marking this as __seg_gs makes it %gs-base-relative. * aarch64: We shove this in x18 and ref off of that; -ffixed-x18 and don't forget to reload it from TPIDR_EL1 */ #ifdef __x86_64__ static struct ProcessorLocal __seg_gs * const this_core = 0; #else register struct ProcessorLocal * this_core asm("x18"); #endif extern unsigned long process_append_fd(process_t * proc, fs_node_t * node); extern long process_move_fd(process_t * proc, long src, long dest); extern void initialize_process_tree(void); extern process_t * process_from_pid(pid_t pid); extern void process_delete(process_t * proc); extern void make_process_ready(volatile process_t * proc); extern volatile process_t * next_ready_process(void); extern int wakeup_queue(list_t * queue); extern int wakeup_queue_interrupted(list_t * queue); extern int sleep_on(list_t * queue); extern int sleep_on_unlocking(list_t * queue, spin_lock_t * release); extern int process_alert_node(process_t * process, void * value); extern void sleep_until(process_t * process, unsigned long seconds, unsigned long subseconds); extern void switch_task(uint8_t reschedule); extern int process_wait_nodes(process_t * process,fs_node_t * nodes[], int timeout); extern process_t * process_get_parent(process_t * process); extern int process_is_ready(process_t * proc); extern void wakeup_sleepers(unsigned long seconds, unsigned long subseconds); extern void task_exit(int retval); extern __attribute__((noreturn)) void switch_next(void); extern int process_awaken_from_fswait(process_t * process, int index); extern void process_awaken_signal(process_t * process); extern void process_release_directory(page_directory_t * dir); extern process_t * spawn_worker_thread(void (*entrypoint)(void * argp), const char * name, void * argp); extern pid_t fork(void); extern pid_t clone(uintptr_t new_stack, uintptr_t thread_func, uintptr_t arg); extern int waitpid(int pid, int * status, int options); extern int exec(const char * path, int argc, char *const argv[], char *const env[], int interp_depth); extern void update_process_usage(uint64_t clock_ticks, uint64_t perf_scale); extern void update_process_times_on_exit(void); extern tree_t * process_tree; /* Parent->Children tree */ extern list_t * process_list; /* Flat storage */ extern list_t * process_queue; /* Ready queue */ extern list_t * sleep_queue; extern void arch_enter_tasklet(void); extern __attribute__((noreturn)) void arch_resume_user(void); extern __attribute__((noreturn)) void arch_restore_context(volatile thread_t * buf); extern __attribute__((returns_twice)) int arch_save_context(volatile thread_t * buf); extern void arch_restore_floating(process_t * proc); extern void arch_save_floating(process_t * proc); extern void arch_set_kernel_stack(uintptr_t); extern void arch_enter_user(uintptr_t entrypoint, int argc, char * argv[], char * envp[], uintptr_t stack); __attribute__((noreturn)) extern void arch_enter_signal_handler(uintptr_t,int,struct regs*); extern void arch_wakeup_others(void); extern int arch_return_from_signal_handler(struct regs *r); ================================================ FILE: base/usr/include/kernel/procfs.h ================================================ #pragma once #include typedef void (*procfs_populate_t)(fs_node_t * node); struct procfs_entry { intptr_t id; const char * name; procfs_populate_t func; }; extern int procfs_install(struct procfs_entry * entry); extern void procfs_initialize(void); extern int procfs_printf(fs_node_t * node, const char * fmt, ...); ================================================ FILE: base/usr/include/kernel/ptrace.h ================================================ #pragma once long ptrace_attach(pid_t pid); long ptrace_self(void); long ptrace_signal(int signal, int reason); long ptrace_continue(pid_t pid, int signum); ================================================ FILE: base/usr/include/kernel/pty.h ================================================ #pragma once #include #include #include #include #include typedef struct pty { /* the PTY number */ intptr_t name; /* Master and slave endpoints */ fs_node_t * master; fs_node_t * slave; /* term io "window size" struct (width/height) */ struct winsize size; /* termios data structure */ struct termios tios; /* directional pipes */ ring_buffer_t * in; ring_buffer_t * out; char * canon_buffer; size_t canon_bufsize; size_t canon_buflen; pid_t ct_proc; /* Controlling process (shell) */ pid_t fg_proc; /* Foreground process (might also be shell) */ void (*write_in)(struct pty *, uint8_t); void (*write_out)(struct pty *, uint8_t); int next_is_verbatim; void (*fill_name)(struct pty *, char *); void * _private; } pty_t; void tty_output_process_slave(pty_t * pty, uint8_t c); void tty_output_process(pty_t * pty, uint8_t c); void tty_input_process(pty_t * pty, uint8_t c); pty_t * pty_new(struct winsize * size, int index); ================================================ FILE: base/usr/include/kernel/ramdisk.h ================================================ #pragma once #include #include #include extern fs_node_t * ramdisk_mount(uintptr_t, size_t); ================================================ FILE: base/usr/include/kernel/ringbuffer.h ================================================ #pragma once #include #include #include #include typedef struct { unsigned char * buffer; size_t write_ptr; size_t read_ptr; size_t size; spin_lock_t lock; list_t * wait_queue_readers; list_t * wait_queue_writers; int internal_stop; list_t * alert_waiters; int discard; int soft_stop; } ring_buffer_t; size_t ring_buffer_unread(ring_buffer_t * ring_buffer); size_t ring_buffer_size(fs_node_t * node); size_t ring_buffer_available(ring_buffer_t * ring_buffer); size_t ring_buffer_read(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer); size_t ring_buffer_write(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer); ring_buffer_t * ring_buffer_create(size_t size); void ring_buffer_destroy(ring_buffer_t * ring_buffer); void ring_buffer_interrupt(ring_buffer_t * ring_buffer); void ring_buffer_alert_waiters(ring_buffer_t * ring_buffer); void ring_buffer_select_wait(ring_buffer_t * ring_buffer, void * process); void ring_buffer_eof(ring_buffer_t * ring_buffer); void ring_buffer_discard(ring_buffer_t * ring_buffer); ================================================ FILE: base/usr/include/kernel/shm.h ================================================ #pragma once #include #include #include #define SHM_PATH_SEPARATOR "." /* Types */ struct shm_node; typedef struct { struct shm_node * parent; volatile uint8_t lock; ssize_t ref_count; size_t num_frames; uintptr_t *frames; } shm_chunk_t; typedef struct shm_node { char name[256]; shm_chunk_t * chunk; } shm_node_t; typedef struct { shm_chunk_t * chunk; uint8_t volatile lock; size_t num_vaddrs; uintptr_t *vaddrs; } shm_mapping_t; /* Syscalls */ extern void * shm_obtain(char * path, size_t * size); extern int shm_release(char * path); /* Other exposed functions */ extern void shm_install(void); extern void shm_release_all(process_t * proc); ================================================ FILE: base/usr/include/kernel/signal.h ================================================ #pragma once #include #include #include #if defined(__x86_64__) #include #elif defined(__aarch64__) #include #else #error "no regs" #endif typedef struct { int signum; } signal_t; extern void fix_signal_stacks(void); extern int send_signal(pid_t process, int signal, int force_root); extern int group_send_signal(pid_t group, int signal, int force_root); extern void return_from_signal_handler(struct regs*); extern void process_check_signals(struct regs*); extern int signal_await(sigset_t awaited, int * sig); extern int handle_signal(process_t * proc, int signum, struct regs *r); ================================================ FILE: base/usr/include/kernel/spinlock.h ================================================ #pragma once #include #include #include typedef volatile struct { volatile int latch[1]; int owner; const char * func; } spin_lock_t; #define spin_init(lock) do { (lock).owner = 0; (lock).latch[0] = 0; (lock).func = NULL; } while (0) #ifdef __aarch64__ extern void arch_spin_lock_acquire(const char * name, spin_lock_t * lock, const char * func); extern void arch_spin_lock_release(spin_lock_t * lock); #define spin_lock(lock) arch_spin_lock_acquire(#lock, &lock, __func__) #define spin_unlock(lock) arch_spin_lock_release(&lock) #else #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) #define spin_unlock(lock) do { (lock).func = NULL; (lock).owner = -1; __sync_lock_release((lock).latch); } while (0) #endif #include ================================================ FILE: base/usr/include/kernel/string.h ================================================ #pragma once #include extern void * memcpy(void * restrict dest, const void * restrict src, size_t n); extern void * memset(void * dest, int c, size_t n); extern unsigned short * memsetw(unsigned short * dest, unsigned short val, int count); extern void * memchr(const void * src, int c, size_t n); extern void * memrchr(const void * m, int c, size_t n); extern void * memmove(void *dest, const void *src, size_t n); extern int memcmp(const void *vl, const void *vr, size_t n); extern char * strdup(const char * s); extern char * stpcpy(char * restrict d, const char * restrict s); extern char * strcpy(char * restrict dest, const char * restrict src); extern char * strchrnul(const char * s, int c); extern char * strchr(const char * s, int c); extern char * strrchr(const char * s, int c); extern char * strpbrk(const char * s, const char * b); extern char * strstr(const char * h, const char * n); extern int strcmp(const char * l, const char * r); extern size_t strcspn(const char * s, const char * c); extern size_t strspn(const char * s, const char * c); extern size_t strlen(const char * s); extern int atoi(const char * s); extern char * strtok_r(char * str, const char * delim, char ** saveptr); extern void * __attribute__ ((malloc)) malloc(uintptr_t size); extern void * __attribute__ ((malloc)) realloc(void * ptr, uintptr_t size); extern void * __attribute__ ((malloc)) calloc(uintptr_t nmemb, uintptr_t size); extern void * __attribute__ ((malloc)) valloc(uintptr_t size); extern void free(void * ptr); extern uint8_t startswith(const char * str, const char * accept); ================================================ FILE: base/usr/include/kernel/symboltable.h ================================================ #pragma once extern char kernel_symbols_start[]; extern char kernel_symbols_end[]; typedef struct { uintptr_t addr; char name[]; } kernel_symbol_t; ================================================ FILE: base/usr/include/kernel/syscall.h ================================================ #pragma once #include #include #define FD_INRANGE(FD) \ ((FD) < (int)this_core->current_process->fds->length && (FD) >= 0) #define FD_ENTRY(FD) \ (this_core->current_process->fds->entries[(FD)]) #define FD_CHECK(FD) \ (FD_INRANGE(FD) && FD_ENTRY(FD)) #define FD_OFFSET(FD) \ (this_core->current_process->fds->offsets[(FD)]) #define FD_MODE(FD) \ (this_core->current_process->fds->modes[(FD)]) #define PTR_INRANGE(PTR) \ ((uintptr_t)(PTR) > this_core->current_process->image.entry && ((uintptr_t)(PTR) < 0x8000000000000000)) #define PTR_VALIDATE(PTR) \ do { if (ptr_validate((void *)(PTR), __func__)) return -EINVAL; } while (0) extern int ptr_validate(void * ptr, const char * syscall); extern long arch_syscall_number(struct regs * r); extern long arch_syscall_arg0(struct regs * r); extern long arch_syscall_arg1(struct regs * r); extern long arch_syscall_arg2(struct regs * r); extern long arch_syscall_arg3(struct regs * r); extern long arch_syscall_arg4(struct regs * r); extern long arch_stack_pointer(struct regs * r); extern long arch_user_ip(struct regs * r); extern void arch_syscall_return(struct regs * r, long retval); extern void syscall_handler(struct regs * r); ================================================ FILE: base/usr/include/kernel/time.h ================================================ #pragma once #include extern void relative_time(unsigned long, unsigned long, unsigned long *, unsigned long *); extern uint64_t now(void); extern uint64_t arch_perf_timer(void); ================================================ FILE: base/usr/include/kernel/tmpfs.h ================================================ #pragma once #include #include #include #include fs_node_t * tmpfs_create(char * name); struct tmpfs_file { spin_lock_t lock; char * name; int type; int mask; uid_t uid; uid_t gid; unsigned int atime; unsigned int mtime; unsigned int ctime; fs_node_t * mount; size_t length; size_t block_count; size_t pointers; uintptr_t * blocks; char * target; }; struct tmpfs_dir; struct tmpfs_dir { spin_lock_t lock; char * name; int type; int mask; uid_t uid; uid_t gid; unsigned int atime; unsigned int mtime; unsigned int ctime; fs_node_t * mount; list_t * files; struct tmpfs_dir * parent; spin_lock_t nest_lock; }; ================================================ FILE: base/usr/include/kernel/tokenize.h ================================================ #pragma once int tokenize(char * str, const char * sep, char **buf); ================================================ FILE: base/usr/include/kernel/tree.h ================================================ #pragma once #include typedef struct tree_node { void * value; list_t * children; struct tree_node * parent; } tree_node_t; typedef struct { size_t nodes; tree_node_t * root; } tree_t; typedef uint8_t (*tree_comparator_t) (void *, void *); extern tree_t * tree_create(void); extern void tree_set_root(tree_t * tree, void * value); extern void tree_node_destroy(tree_node_t * node); extern void tree_destroy(tree_t * tree); extern void tree_free(tree_t * tree); extern tree_node_t * tree_node_create(void * value); extern void tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node); extern tree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value); extern tree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle); extern void tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node); extern void tree_node_remove(tree_t * tree, tree_node_t * node); extern void tree_remove(tree_t * tree, tree_node_t * node); extern tree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator); extern void tree_break_off(tree_t * tree, tree_node_t * node); ================================================ FILE: base/usr/include/kernel/types.h ================================================ #pragma once #include #include #include #define asm __asm__ #define volatile __volatile__ #define ALIGN (sizeof(size_t)) #define ONES ((size_t)-1/UCHAR_MAX) #define HIGHS (ONES * (UCHAR_MAX/2+1)) #define HASZERO(X) (((X)-ONES) & ~(X) & HIGHS) ================================================ FILE: base/usr/include/kernel/version.h ================================================ #pragma once extern const char * __kernel_name; extern const char * __kernel_version_format; extern int __kernel_version_major; extern int __kernel_version_minor; extern int __kernel_version_lower; extern const char * __kernel_version_suffix; extern const char * __kernel_version_codename; extern const char * __kernel_arch; extern const char * __kernel_build_date; extern const char * __kernel_build_time; extern const char * __kernel_compiler_version; ================================================ FILE: base/usr/include/kernel/vfs.h ================================================ #pragma once #include #include #include #include #include #define PATH_SEPARATOR '/' #define PATH_SEPARATOR_STRING "/" #define PATH_UP ".." #define PATH_DOT "." #include #define FS_FILE 0x01 #define FS_DIRECTORY 0x02 #define FS_CHARDEVICE 0x04 #define FS_BLOCKDEVICE 0x08 #define FS_PIPE 0x10 #define FS_SYMLINK 0x20 #define FS_MOUNTPOINT 0x40 #define FS_SOCKET 0x80 #define _IFMT 0170000 /* type of file */ #define _IFDIR 0040000 /* directory */ #define _IFCHR 0020000 /* character special */ #define _IFBLK 0060000 /* block special */ #define _IFREG 0100000 /* regular */ #define _IFLNK 0120000 /* symbolic link */ #define _IFSOCK 0140000 /* socket */ #define _IFIFO 0010000 /* fifo */ struct fs_node; typedef ssize_t (*read_type_t) (struct fs_node *, off_t, size_t, uint8_t *); typedef ssize_t (*write_type_t) (struct fs_node *, off_t, size_t, uint8_t *); typedef void (*open_type_t) (struct fs_node *, unsigned int flags); typedef void (*close_type_t) (struct fs_node *); typedef struct dirent *(*readdir_type_t) (struct fs_node *, unsigned long); typedef struct fs_node *(*finddir_type_t) (struct fs_node *, char *name); typedef int (*create_type_t) (struct fs_node *, char *name, mode_t permission); typedef int (*unlink_type_t) (struct fs_node *, char *name); typedef int (*mkdir_type_t) (struct fs_node *, char *name, mode_t permission); typedef int (*ioctl_type_t) (struct fs_node *, unsigned long request, void * argp); typedef int (*get_size_type_t) (struct fs_node *); typedef int (*chmod_type_t) (struct fs_node *, mode_t mode); typedef int (*symlink_type_t) (struct fs_node *, char * name, char * value); typedef ssize_t (*readlink_type_t) (struct fs_node *, char * buf, size_t size); typedef int (*selectcheck_type_t) (struct fs_node *); typedef int (*selectwait_type_t) (struct fs_node *, void * process); typedef int (*chown_type_t) (struct fs_node *, uid_t, gid_t); typedef int (*truncate_type_t) (struct fs_node *, size_t size); typedef int (*rename_type_t) (struct fs_node *, struct fs_node *, const char *, struct fs_node *, const char *); typedef struct fs_node { struct fs_node * mount; /* Root fs_node_t entry of mountpoint. */ char name[256]; /* The filename. */ void * device; /* Device object (optional) */ mode_t mask; /* The permissions mask. */ uid_t uid; /* The owning user. */ uid_t gid; /* The owning group. */ uint64_t flags; /* Flags (node type, etc). */ uint64_t inode; /* Inode number. */ uint64_t length; /* Size of the file, in byte. */ uint64_t impl; /* Used to keep track which fs it belongs to. */ uint64_t open_flags; /* Flags passed to open (read/write/append, etc.) */ struct fs_node *ptr; /* Alias pointer, for symlinks. */ int64_t refcount; /* Node reference count */ uint64_t nlink; /* Number of links in underlying filesystem */ /* times */ time_t atime; /* Accessed */ time_t mtime; /* Modified */ time_t ctime; /* Created */ /* File operations */ read_type_t read; write_type_t write; open_type_t open; close_type_t close; readdir_type_t readdir; finddir_type_t finddir; create_type_t create; mkdir_type_t mkdir; ioctl_type_t ioctl; get_size_type_t get_size; chmod_type_t chmod; unlink_type_t unlink; symlink_type_t symlink; readlink_type_t readlink; truncate_type_t truncate; selectcheck_type_t selectcheck; selectwait_type_t selectwait; chown_type_t chown; rename_type_t rename; } fs_node_t; struct vfs_entry { char * name; fs_node_t * file; char * device; char * fs_type; }; extern fs_node_t *fs_root; extern int pty_create(void *size, fs_node_t ** fs_master, fs_node_t ** fs_slave); int has_permission(fs_node_t *node, int permission_bit); ssize_t read_fs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer); ssize_t write_fs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer); void open_fs(fs_node_t *node, unsigned int flags); void close_fs(fs_node_t *node); struct dirent *readdir_fs(fs_node_t *node, unsigned long index); fs_node_t *finddir_fs(fs_node_t *node, char *name); int mkdir_fs(char *name, mode_t permission); int create_file_fs(char *name, mode_t permission); fs_node_t *kopen(const char *filename, unsigned int flags); char *canonicalize_path(const char *cwd, const char *input); fs_node_t *clone_fs(fs_node_t * source); int ioctl_fs(fs_node_t *node, unsigned long request, void * argp); int chmod_fs(fs_node_t *node, mode_t mode); int chown_fs(fs_node_t *node, uid_t uid, gid_t gid); int unlink_fs(char * name); int symlink_fs(char * value, char * name); ssize_t readlink_fs(fs_node_t * node, char * buf, size_t size); int selectcheck_fs(fs_node_t * node); int selectwait_fs(fs_node_t * node, void * process); int truncate_fs(fs_node_t * node, size_t size); void vfs_install(void); void * vfs_mount(const char * path, fs_node_t * local_root, const char * type, const char * options); typedef fs_node_t * (*vfs_mount_callback)(const char * arg, const char * mount_point); int vfs_register(const char * name, vfs_mount_callback callback); int vfs_mount_type(const char * type, const char * arg, const char * mountpoint); void vfs_lock(fs_node_t * node); /* Debug purposes only, please */ void debug_print_vfs_tree(void); void map_vfs_directory(const char *); int make_unix_pipe(fs_node_t ** pipes); int fprintf(fs_node_t * f, const char * fmt, ...); ================================================ FILE: base/usr/include/kernel/video.h ================================================ #pragma once #include #define IO_VID_WIDTH 0x5001 #define IO_VID_HEIGHT 0x5002 #define IO_VID_DEPTH 0x5003 #define IO_VID_ADDR 0x5004 #define IO_VID_SIGNAL 0x5005 #define IO_VID_SET 0x5006 #define IO_VID_STRIDE 0x5007 #define IO_VID_DRIVER 0x5008 #define IO_VID_REINIT 0x5009 struct vid_size { uint32_t width; uint32_t height; }; #ifdef _KERNEL_ extern void lfb_set_resolution(uint16_t x, uint16_t y); extern uint16_t lfb_resolution_x; extern uint16_t lfb_resolution_y; extern uint16_t lfb_resolution_b; extern uint32_t lfb_resolution_s; extern uint8_t * lfb_vid_memory; extern const char * lfb_driver_name; extern void (*lfb_resolution_impl)(uint16_t,uint16_t); extern int framebuffer_initialize(void); extern size_t lfb_memsize; #endif ================================================ FILE: base/usr/include/libgen.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header extern char * dirname(char * path); extern char * basename(char * path); _End_C_Header ================================================ FILE: base/usr/include/libintl.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header extern char * gettext (const char * msgid); extern char * dgettext (const char * domainname, const char * msgid); extern char * dcgettext (const char * domainname, const char * msgid, int category); extern char * ngettext (const char * msgid, const char * msgid_plural, unsigned long int n); extern char * dngettext (const char * domainname, const char * msgid, const char * msgid_plural, unsigned long int n); extern char * dcngettext (const char * domainname, const char * msgid, const char * msgid_plural, unsigned long int n, int category); _End_C_Header ================================================ FILE: base/usr/include/limits.h ================================================ #pragma once #define _LITTLE_ENDIAN /* dummy */ ================================================ FILE: base/usr/include/locale.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header #define LC_ALL 0 #define LC_COLLATE 1 #define LC_CTYPE 2 #define LC_MONETARY 3 #define LC_NUMERIC 4 #define LC_TIME 5 #define LC_MESSAGES 6 struct lconv { char * decimal_point; char * thousands_sep; char * grouping; char * int_curr_symbol; char * currency_symbol; char * mon_decimal_point; char * mon_thousands_sep; char * mon_grouping; char * positive_sign; char * negative_sign; char int_frac_digits; char frac_digits; char p_cs_precedes; char p_sep_by_space; char n_cs_precedes; char n_sep_by_space; char p_sign_posn; char n_sign_posn; }; extern struct lconv * localeconv(void); extern char * setlocale(int category, const char *locale); _End_C_Header ================================================ FILE: base/usr/include/math.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header #define M_PI 3.14159265358979323846 #define M_E 2.7182818284590452354 #define NAN (__builtin_nanf("")) #define INFINITY (__builtin_inff()) extern double floor(double x); extern int abs(int j); extern double pow(double x, double y); extern double exp(double x); extern double fmod(double x, double y); extern double sqrt(double x); extern float sqrtf(float x); extern double fabs(double x); extern float fabsf(float x); extern double sin(double x); extern double cos(double x); double frexp(double x, int *exp); #define HUGE_VAL (__builtin_huge_val()) /* Unimplemented, but stubbed */ extern double acos(double x); extern double asin(double x); extern double atan2(double y, double x); extern double ceil(double x); extern double cosh(double x); extern double ldexp(double a, int exp); extern double log(double x); extern double log10(double x); extern double log2(double x); extern double sinh(double x); extern double tan(double x); extern double tanh(double x); extern double atan(double x); extern double log1p(double x); extern double expm1(double x); extern double modf(double x, double *iptr); extern double hypot(double x, double y); extern double trunc(double x); extern double acosh(double x); extern double asinh(double x); extern double atanh(double x); extern double erf(double x); extern double erfc(double x); extern double gamma(double x); extern double tgamma(double x); extern double lgamma(double x); extern double copysign(double x, double y); extern double remainder(double x, double y); enum { FP_NAN, FP_INFINITE, FP_ZERO, FP_SUBNORMAL, FP_NORMAL }; extern int fpclassify(double x); #define isfinite(x) ((fpclassify(x) != FP_NAN && fpclassify(x) != FP_INFINITE)) #define isnormal(x) (fpclassify(x) == FP_NORMAL) #define isnan(x) (fpclassify(x) == FP_NAN) #define isinf(x) (fpclassify(x) == FP_INFINITE) extern float ceilf(float x); extern double round(double x); extern float roundf(float x); extern long lroundf(float x); _End_C_Header ================================================ FILE: base/usr/include/memory.h ================================================ #pragma once #include ================================================ FILE: base/usr/include/net/if.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header /* I would love to fully implement the Linux API for these, but * for now these are just cleaner versions of the temporary API * we currently provide. */ #define SIOCGIFHWADDR 0x12340001 /* Get hardware address */ #define SIOCGIFADDR 0x12340002 /* Get IPv4 address */ #define SIOCSIFADDR 0x12340012 /* Set IPv4 address */ #define SIOCGIFNETMASK 0x12340004 /* Get IPv4 subnet mask */ #define SIOCSIFNETMASK 0x12340014 /* Set IPv4 subnet mask */ #define SIOCGIFADDR6 0x12340003 /* Get IPv6 address */ #define SIOCSIFADDR6 0x12340013 /* Set IPv6 address */ #define SIOCGIFFLAGS 0x12340005 /* Get interface flags */ #define SIOCGIFMTU 0x12340006 /* Get interface mtu */ #define SIOCGIFGATEWAY 0x12340007 #define SIOCSIFGATEWAY 0x12340017 #define SIOCGIFCOUNTS 0x12340018 /** * Flags for interface status */ #define IFF_UP 0x0001 #define IFF_BROADCAST 0x0002 #define IFF_DEBUG 0x0004 #define IFF_LOOPBACK 0x0008 #define IFF_RUNNING 0x0010 #define IFF_MULTICAST 0x0020 typedef struct { size_t tx_count; size_t tx_bytes; size_t rx_count; size_t rx_bytes; } netif_counters_t; _End_C_Header ================================================ FILE: base/usr/include/netdb.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); extern int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); extern void freeaddrinfo(struct addrinfo *res); struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ }; extern struct hostent * gethostbyname(const char * name); #ifndef _KERNEL_ #define h_addr h_addr_list[0] #endif #define NI_NUMERICHOST 1 #define NI_MAXHOST 255 /* Defined error codes returned by getaddrinfo */ #define EAI_AGAIN -1 #define EAI_BADFLAGS -2 #define EAI_BADEXFLAGS -3 #define EAI_FAMILY -4 #define EAI_MEMORY -5 #define EAI_NONAME -6 #define EAI_SERVICE -7 #define EAI_SOCKTYPE -8 _End_C_Header ================================================ FILE: base/usr/include/netinet/in.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header typedef uint32_t in_addr_t; typedef uint16_t in_port_t; struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { short sin_family; // e.g. AF_INET, AF_INET6 unsigned short sin_port; // e.g. htons(3490) struct in_addr sin_addr; // see struct in_addr, below char sin_zero[8]; // zero this if you want to }; #define IP_TTL 2 #define IP_RECVTTL 12 _End_C_Header ================================================ FILE: base/usr/include/poll.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header #define POLLIN 0x0001 #define POLLOUT 0x0002 #define POLLRDHUP 0x0004 #define POLLERR 0x0008 #define POLLHUP 0x0010 #define POLLNVAL 0x0020 #define POLLPRI 0x0040 typedef unsigned int nfds_t; struct pollfd { int fd; short events; short revents; }; extern int poll(struct pollfd * fds, nfds_t nfds, int timeout); _End_C_Header ================================================ FILE: base/usr/include/pthread.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header typedef struct __pthread * pthread_t; typedef unsigned int pthread_attr_t; typedef struct { int volatile atomic_lock; int volatile readers; int writerPid; } pthread_rwlock_t; extern int pthread_create(pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg); extern void pthread_exit(void * value); extern int pthread_kill(pthread_t thread, int sig); extern int clone(uintptr_t,uintptr_t,void*); extern int gettid(); extern void pthread_cleanup_push(void (*routine)(void *), void *arg); extern void pthread_cleanup_pop(int execute); typedef int volatile pthread_mutex_t; typedef int pthread_mutexattr_t; extern int pthread_join(pthread_t thread, void **retval); #define PTHREAD_MUTEX_INITIALIZER 0 extern int pthread_mutex_lock(pthread_mutex_t *mutex); extern int pthread_mutex_trylock(pthread_mutex_t *mutex); extern int pthread_mutex_unlock(pthread_mutex_t *mutex); extern int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); extern int pthread_mutex_destroy(pthread_mutex_t *mutex); extern int pthread_attr_init(pthread_attr_t *attr); extern int pthread_attr_destroy(pthread_attr_t *attr); extern int pthread_rwlock_init(pthread_rwlock_t * lock, void * args); extern int pthread_rwlock_wrlock(pthread_rwlock_t * lock); extern int pthread_rwlock_rdlock(pthread_rwlock_t * lock); extern int pthread_rwlock_unlock(pthread_rwlock_t * lock); extern int pthread_rwlock_destroy(pthread_rwlock_t * lock); _End_C_Header ================================================ FILE: base/usr/include/pty.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern int openpty(int * amaster, int * aslave, char * name, const struct termios *termp, const struct winsize * winp); _End_C_Header ================================================ FILE: base/usr/include/pwd.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header struct passwd { char * pw_name; // username char * pw_passwd; // password (not meaningful) uid_t pw_uid; // user id gid_t pw_gid; // group id char * pw_comment; // used for decoration settings in toaruos char * pw_gecos; // full name char * pw_dir; // home directory char * pw_shell; // shell }; struct passwd * fgetpwent(FILE * stream); struct passwd * getpwent(void); void setpwent(void); void endpwent(void); struct passwd * getpwnam(const char * name); struct passwd * getpwuid(uid_t uid); _End_C_Header ================================================ FILE: base/usr/include/sched.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header extern int sched_yield(void); _End_C_Header ================================================ FILE: base/usr/include/setjmp.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header /* i386 */ #ifdef __aarch64__ #define _JBLEN 32 #else #define _JBLEN 9 #endif typedef long long jmp_buf[_JBLEN]; extern void longjmp(jmp_buf j, int r); extern int setjmp(jmp_buf j); _End_C_Header ================================================ FILE: base/usr/include/signal.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header typedef _sig_func_ptr sighandler_t; #define SIG_DFL ((_sig_func_ptr)0)/* Default action */ #define SIG_IGN ((_sig_func_ptr)1)/* Ignore action */ #define SIG_ERR ((_sig_func_ptr)-1)/* Error return */ typedef int sig_atomic_t; extern sighandler_t signal(int signum, sighandler_t handler); extern int raise(int sig); extern int sigaction(int signum, struct sigaction *act, struct sigaction *oldact); extern int sigemptyset(sigset_t *); extern int sigfillset(sigset_t * set); extern int sigaddset(sigset_t * set, int signum); extern int sigdelset(sigset_t * set, int signum); extern int sigismember(sigset_t * set, int signum); extern int sigprocmask(int how, const sigset_t * set, sigset_t * oset); extern int sigpending(sigset_t * set); extern int sigsuspend(const sigset_t * set); extern int sigwait(const sigset_t * set, int * sig); _End_C_Header ================================================ FILE: base/usr/include/stdint.h ================================================ /* * Copyright (c) 2004, 2005 by * Ralf Corsepius, Ulm/Germany. All rights reserved. * * Permission to use, copy, modify, and distribute this software * is freely granted, provided that this notice is preserved. */ #ifndef _STDINT_H #define _STDINT_H #ifdef __cplusplus extern "C" { #endif #if defined(__GNUC__) && \ ( (__GNUC__ >= 4) || \ ( (__GNUC__ >= 3) && defined(__GNUC_MINOR__) && (__GNUC_MINOR__ > 2) ) ) /* gcc > 3.2 implicitly defines the values we are interested */ #define __STDINT_EXP(x) __##x##__ #else #define __STDINT_EXP(x) x #include #endif /* Check if "long long" is 64bit wide */ /* Modern GCCs provide __LONG_LONG_MAX__, SUSv3 wants LLONG_MAX */ #if ( defined(__LONG_LONG_MAX__) && (__LONG_LONG_MAX__ > 0x7fffffff) ) \ || ( defined(LLONG_MAX) && (LLONG_MAX > 0x7fffffff) ) #define __have_longlong64 1 #endif /* Check if "long" is 64bit or 32bit wide */ #if __STDINT_EXP(LONG_MAX) > 0x7fffffff #define __have_long64 1 #elif __STDINT_EXP(LONG_MAX) == 0x7fffffff && !defined(__SPU__) #define __have_long32 1 #endif #if __STDINT_EXP(SCHAR_MAX) == 0x7f typedef signed char int8_t ; typedef unsigned char uint8_t ; #define __int8_t_defined 1 #endif #if __int8_t_defined typedef signed char int_least8_t; typedef unsigned char uint_least8_t; #define __int_least8_t_defined 1 #endif #if __STDINT_EXP(SHRT_MAX) == 0x7fff typedef signed short int16_t; typedef unsigned short uint16_t; #define __int16_t_defined 1 #elif __STDINT_EXP(INT_MAX) == 0x7fff typedef signed int int16_t; typedef unsigned int uint16_t; #define __int16_t_defined 1 #elif __STDINT_EXP(SCHAR_MAX) == 0x7fff typedef signed char int16_t; typedef unsigned char uint16_t; #define __int16_t_defined 1 #endif #if __int16_t_defined typedef int16_t int_least16_t; typedef uint16_t uint_least16_t; #define __int_least16_t_defined 1 #if !__int_least8_t_defined typedef int16_t int_least8_t; typedef uint16_t uint_least8_t; #define __int_least8_t_defined 1 #endif #endif #if __have_long32 typedef signed long int32_t; typedef unsigned long uint32_t; #define __int32_t_defined 1 #elif __STDINT_EXP(INT_MAX) == 0x7fffffffL typedef signed int int32_t; typedef unsigned int uint32_t; #define __int32_t_defined 1 #elif __STDINT_EXP(SHRT_MAX) == 0x7fffffffL typedef signed short int32_t; typedef unsigned short uint32_t; #define __int32_t_defined 1 #elif __STDINT_EXP(SCHAR_MAX) == 0x7fffffffL typedef signed char int32_t; typedef unsigned char uint32_t; #define __int32_t_defined 1 #endif #if __int32_t_defined typedef int32_t int_least32_t; typedef uint32_t uint_least32_t; #define __int_least32_t_defined 1 #if !__int_least8_t_defined typedef int32_t int_least8_t; typedef uint32_t uint_least8_t; #define __int_least8_t_defined 1 #endif #if !__int_least16_t_defined typedef int32_t int_least16_t; typedef uint32_t uint_least16_t; #define __int_least16_t_defined 1 #endif #endif #if __have_long64 typedef signed long int64_t; typedef unsigned long uint64_t; #define __int64_t_defined 1 #elif __have_longlong64 typedef signed long long int64_t; typedef unsigned long long uint64_t; #define __int64_t_defined 1 #elif __STDINT_EXP(INT_MAX) > 0x7fffffff typedef signed int int64_t; typedef unsigned int uint64_t; #define __int64_t_defined 1 #endif #if __int64_t_defined typedef int64_t int_least64_t; typedef uint64_t uint_least64_t; #define __int_least64_t_defined 1 #if !__int_least8_t_defined typedef int64_t int_least8_t; typedef uint64_t uint_least8_t; #define __int_least8_t_defined 1 #endif #if !__int_least16_t_defined typedef int64_t int_least16_t; typedef uint64_t uint_least16_t; #define __int_least16_t_defined 1 #endif #if !__int_least32_t_defined typedef int64_t int_least32_t; typedef uint64_t uint_least32_t; #define __int_least32_t_defined 1 #endif #endif /* * Fastest minimum-width integer types * * Assume int to be the fastest type for all types with a width * less than __INT_MAX__ rsp. INT_MAX */ #if __STDINT_EXP(INT_MAX) >= 0x7f typedef signed int int_fast8_t; typedef unsigned int uint_fast8_t; #define __int_fast8_t_defined 1 #endif #if __STDINT_EXP(INT_MAX) >= 0x7fff typedef signed int int_fast16_t; typedef unsigned int uint_fast16_t; #define __int_fast16_t_defined 1 #endif #if __STDINT_EXP(INT_MAX) >= 0x7fffffff typedef signed int int_fast32_t; typedef unsigned int uint_fast32_t; #define __int_fast32_t_defined 1 #endif #if __STDINT_EXP(INT_MAX) > 0x7fffffff typedef signed int int_fast64_t; typedef unsigned int uint_fast64_t; #define __int_fast64_t_defined 1 #endif /* * Fall back to [u]int_least_t for [u]int_fast_t types * not having been defined, yet. * Leave undefined, if [u]int_least_t should not be available. */ #if !__int_fast8_t_defined #if __int_least8_t_defined typedef int_least8_t int_fast8_t; typedef uint_least8_t uint_fast8_t; #define __int_fast8_t_defined 1 #endif #endif #if !__int_fast16_t_defined #if __int_least16_t_defined typedef int_least16_t int_fast16_t; typedef uint_least16_t uint_fast16_t; #define __int_fast16_t_defined 1 #endif #endif #if !__int_fast32_t_defined #if __int_least32_t_defined typedef int_least32_t int_fast32_t; typedef uint_least32_t uint_fast32_t; #define __int_fast32_t_defined 1 #endif #endif #if !__int_fast64_t_defined #if __int_least64_t_defined typedef int_least64_t int_fast64_t; typedef uint_least64_t uint_fast64_t; #define __int_fast64_t_defined 1 #endif #endif /* Greatest-width integer types */ /* Modern GCCs provide __INTMAX_TYPE__ */ #if defined(__INTMAX_TYPE__) typedef __INTMAX_TYPE__ intmax_t; #elif __have_longlong64 typedef signed long long intmax_t; #else typedef signed long intmax_t; #endif /* Modern GCCs provide __UINTMAX_TYPE__ */ #if defined(__UINTMAX_TYPE__) typedef __UINTMAX_TYPE__ uintmax_t; #elif __have_longlong64 typedef unsigned long long uintmax_t; #else typedef unsigned long uintmax_t; #endif /* * GCC doesn't provide an appropriate macro for [u]intptr_t * For now, use __PTRDIFF_TYPE__ */ #if defined(__PTRDIFF_TYPE__) typedef signed __PTRDIFF_TYPE__ intptr_t; typedef unsigned __PTRDIFF_TYPE__ uintptr_t; #define INTPTR_MAX PTRDIFF_MAX #define INTPTR_MIN PTRDIFF_MIN #ifdef __UINTPTR_MAX__ #define UINTPTR_MAX __UINTPTR_MAX__ #else #define UINTPTR_MAX (2UL * PTRDIFF_MAX + 1) #endif #else /* * Fallback to hardcoded values, * should be valid on cpu's with 32bit int/32bit void* */ typedef signed long intptr_t; typedef unsigned long uintptr_t; #define INTPTR_MAX __STDINT_EXP(LONG_MAX) #define INTPTR_MIN (-__STDINT_EXP(LONG_MAX) - 1) #define UINTPTR_MAX (__STDINT_EXP(LONG_MAX) * 2UL + 1) #endif /* Limits of Specified-Width Integer Types */ #if __int8_t_defined #define INT8_MIN -128 #define INT8_MAX 127 #define UINT8_MAX 255 #endif #if __int_least8_t_defined #define INT_LEAST8_MIN -128 #define INT_LEAST8_MAX 127 #define UINT_LEAST8_MAX 255 #else #error required type int_least8_t missing #endif #if __int16_t_defined #define INT16_MIN -32768 #define INT16_MAX 32767 #define UINT16_MAX 65535 #endif #if __int_least16_t_defined #define INT_LEAST16_MIN -32768 #define INT_LEAST16_MAX 32767 #define UINT_LEAST16_MAX 65535 #else #error required type int_least16_t missing #endif #if __int32_t_defined #if __have_long32 #define INT32_MIN (-2147483647L-1) #define INT32_MAX 2147483647L #define UINT32_MAX 4294967295UL #else #define INT32_MIN (-2147483647-1) #define INT32_MAX 2147483647 #define UINT32_MAX 4294967295U #endif #endif #if __int_least32_t_defined #if __have_long32 #define INT_LEAST32_MIN (-2147483647L-1) #define INT_LEAST32_MAX 2147483647L #define UINT_LEAST32_MAX 4294967295UL #else #define INT_LEAST32_MIN (-2147483647-1) #define INT_LEAST32_MAX 2147483647 #define UINT_LEAST32_MAX 4294967295U #endif #else #error required type int_least32_t missing #endif #if __int64_t_defined #if __have_long64 #define INT64_MIN (-9223372036854775807L-1L) #define INT64_MAX 9223372036854775807L #define UINT64_MAX 18446744073709551615U #elif __have_longlong64 #define INT64_MIN (-9223372036854775807LL-1LL) #define INT64_MAX 9223372036854775807LL #define UINT64_MAX 18446744073709551615ULL #endif #endif #if __int_least64_t_defined #if __have_long64 #define INT_LEAST64_MIN (-9223372036854775807L-1L) #define INT_LEAST64_MAX 9223372036854775807L #define UINT_LEAST64_MAX 18446744073709551615U #elif __have_longlong64 #define INT_LEAST64_MIN (-9223372036854775807LL-1LL) #define INT_LEAST64_MAX 9223372036854775807LL #define UINT_LEAST64_MAX 18446744073709551615ULL #endif #endif #if __int_fast8_t_defined #if __STDINT_EXP(INT_MAX) >= 0x7f #define INT_FAST8_MIN (-__STDINT_EXP(INT_MAX)-1) #define INT_FAST8_MAX __STDINT_EXP(INT_MAX) #define UINT_FAST8_MAX (__STDINT_EXP(INT_MAX)*2U+1U) #else #define INT_FAST8_MIN INT_LEAST8_MIN #define INT_FAST8_MAX INT_LEAST8_MAX #define UINT_FAST8_MAX UINT_LEAST8_MAX #endif #endif #if __int_fast16_t_defined #if __STDINT_EXP(INT_MAX) >= 0x7fff #define INT_FAST16_MIN (-__STDINT_EXP(INT_MAX)-1) #define INT_FAST16_MAX __STDINT_EXP(INT_MAX) #define UINT_FAST16_MAX (__STDINT_EXP(INT_MAX)*2U+1U) #else #define INT_FAST16_MIN INT_LEAST16_MIN #define INT_FAST16_MAX INT_LEAST16_MAX #define UINT_FAST16_MAX UINT_LEAST16_MAX #endif #endif #if __int_fast32_t_defined #if __STDINT_EXP(INT_MAX) >= 0x7fffffff #define INT_FAST32_MIN (-__STDINT_EXP(INT_MAX)-1) #define INT_FAST32_MAX __STDINT_EXP(INT_MAX) #define UINT_FAST32_MAX (__STDINT_EXP(INT_MAX)*2U+1U) #else #define INT_FAST32_MIN INT_LEAST32_MIN #define INT_FAST32_MAX INT_LEAST32_MAX #define UINT_FAST32_MAX UINT_LEAST32_MAX #endif #endif #if __int_fast64_t_defined #if __STDINT_EXP(INT_MAX) > 0x7fffffff #define INT_FAST64_MIN (-__STDINT_EXP(INT_MAX)-1) #define INT_FAST64_MAX __STDINT_EXP(INT_MAX) #define UINT_FAST64_MAX (__STDINT_EXP(INT_MAX)*2U+1U) #else #define INT_FAST64_MIN INT_LEAST64_MIN #define INT_FAST64_MAX INT_LEAST64_MAX #define UINT_FAST64_MAX UINT_LEAST64_MAX #endif #endif #ifdef __INTMAX_MAX__ #define INTMAX_MAX __INTMAX_MAX__ #define INTMAX_MIN (-INTMAX_MAX - 1) #elif defined(__INTMAX_TYPE__) /* All relevant GCC versions prefer long to long long for intmax_t. */ #define INTMAX_MAX INT64_MAX #define INTMAX_MIN INT64_MIN #endif #ifdef __UINTMAX_MAX__ #define UINTMAX_MAX __UINTMAX_MAX__ #elif defined(__UINTMAX_TYPE__) /* All relevant GCC versions prefer long to long long for intmax_t. */ #define UINTMAX_MAX UINT64_MAX #endif /* This must match size_t in stddef.h, currently long unsigned int */ #ifdef __SIZE_MAX__ #define SIZE_MAX __SIZE_MAX__ #else #define SIZE_MAX (__STDINT_EXP(LONG_MAX) * 2UL + 1) #endif /* This must match sig_atomic_t in (currently int) */ #define SIG_ATOMIC_MIN (-__STDINT_EXP(INT_MAX) - 1) #define SIG_ATOMIC_MAX __STDINT_EXP(INT_MAX) /* This must match ptrdiff_t in (currently long int) */ #ifdef __PTRDIFF_MAX__ #define PTRDIFF_MAX __PTRDIFF_MAX__ #else #define PTRDIFF_MAX __STDINT_EXP(LONG_MAX) #endif #define PTRDIFF_MIN (-PTRDIFF_MAX - 1) #ifdef __WCHAR_MAX__ #define WCHAR_MAX __WCHAR_MAX__ #endif #ifdef __WCHAR_MIN__ #define WCHAR_MIN __WCHAR_MIN__ #endif /* wint_t is unsigned int on almost all GCC targets. */ #ifdef __WINT_MAX__ #define WINT_MAX __WINT_MAX__ #else #define WINT_MAX (__STDINT_EXP(INT_MAX) * 2U + 1U) #endif #ifdef __WINT_MIN__ #define WINT_MIN __WINT_MIN__ #else #define WINT_MIN 0U #endif /** Macros for minimum-width integer constant expressions */ #define INT8_C(x) x #if __STDINT_EXP(INT_MAX) > 0x7f #define UINT8_C(x) x #else #define UINT8_C(x) x##U #endif #define INT16_C(x) x #if __STDINT_EXP(INT_MAX) > 0x7fff #define UINT16_C(x) x #else #define UINT16_C(x) x##U #endif #if __have_long32 #define INT32_C(x) x##L #define UINT32_C(x) x##UL #else #define INT32_C(x) x #define UINT32_C(x) x##U #endif #if __int64_t_defined #if __have_long64 #define INT64_C(x) x##L #define UINT64_C(x) x##UL #else #define INT64_C(x) x##LL #define UINT64_C(x) x##ULL #endif #endif /** Macros for greatest-width integer constant expression */ #if __have_long64 #define INTMAX_C(x) x##L #define UINTMAX_C(x) x##UL #else #define INTMAX_C(x) x##LL #define UINTMAX_C(x) x##ULL #endif #ifdef __cplusplus } #endif #endif /* _STDINT_H */ ================================================ FILE: base/usr/include/stdio.h ================================================ #pragma once #include <_cheader.h> #include #include #include _Begin_C_Header typedef struct _FILE FILE; #define __DEFINED_FILE #define BUFSIZ 8192 extern FILE * stdin; extern FILE * stdout; extern FILE * stderr; #define EOF (-1) #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 extern FILE * fopen(const char *path, const char *mode); extern int fclose(FILE * stream); extern int fseek(FILE * stream, long offset, int whence); extern long ftell(FILE * stream); extern FILE * fdopen(int fd, const char *mode); extern FILE * freopen(const char *path, const char *mode, FILE * stream); extern size_t fread(void *ptr, size_t size, size_t nmemb, FILE * stream); extern size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE * stream); extern int fileno(FILE * stream); extern int fflush(FILE * stream); extern int asprintf(char ** ret, const char * fmt, ...); extern int vasprintf(char ** buf, const char *fmt, va_list args); extern int sprintf(char *buf, const char *fmt, ...); extern int fprintf(FILE *stream, const char *fmt, ...); extern int printf(const char *fmt, ...); extern int snprintf(char * buf, size_t size, const char * fmt, ...); extern int vsprintf(char * buf, const char *fmt, va_list args); extern int vsnprintf(char * buf, size_t size, const char *fmt, va_list args); extern int vfprintf(FILE * device, const char *format, va_list ap); extern int vprintf(const char *format, va_list ap); extern int puts(const char *s); extern int fputs(const char *s, FILE *stream); extern int fputc(int c, FILE *stream); extern int putc(int c, FILE *stream); extern int putchar(int c); extern int fgetc(FILE *stream); extern int getc(FILE *stream); extern char *fgets(char *s, int size, FILE *stream); extern int getchar(void); extern void rewind(FILE *stream); extern void setbuf(FILE * stream, char * buf); extern void perror(const char *s); extern int ungetc(int c, FILE * stream); extern int feof(FILE * stream); extern void clearerr(FILE * stream); extern int ferror(FILE * stream); extern int _fwouldblock(FILE * stream); extern FILE * tmpfile(void); extern int setvbuf(FILE * stream, char * buf, int mode, size_t size); extern int remove(const char * pathname); extern int rename(const char * oldpath, const char * newpath); #define _IONBF 0 #define _IOLBF 1 #define _IOFBF 2 extern char * tmpnam(char * s); #define L_tmpnam 256 extern int vsscanf(const char *str, const char *format, va_list ap); extern int sscanf(const char *str, const char *format, ...); extern int vfscanf(FILE * stream, const char *format, va_list ap); extern int fscanf(FILE *stream, const char *format, ...); extern int scanf(const char *format, ...); typedef long fpos_t; extern int fgetpos(FILE *stream, fpos_t *pos); extern int fsetpos(FILE *stream, const fpos_t *pos); /* Compatibility */ #define FILENAME_MAX 1024 extern ssize_t getdelim(char **restrict lineptr, size_t *restrict n, int delimiter, FILE *restrict stream); extern ssize_t getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream); _End_C_Header; ================================================ FILE: base/usr/include/stdlib.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern void exit(int status); extern char * getenv(const char *name); extern void *malloc(size_t size); extern void free(void *ptr); extern void *calloc(size_t nmemb, size_t size); extern void *realloc(void *ptr, size_t size); extern void *valloc(size_t size); extern void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void*,const void*)); extern void qsort_r(void * base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void * arg); extern int system(const char * command); extern int abs(int j); extern int putenv(char * name); extern int setenv(const char *name, const char *value, int overwrite); extern int unsetenv(const char * str); extern double strtod(const char *nptr, char **endptr); extern float strtof(const char *nptr, char **endptr); extern double atof(const char * nptr); extern int atoi(const char * nptr); extern long atol(const char * nptr); extern long int labs(long int j); extern long int strtol(const char * s, char **endptr, int base); extern long long int strtoll(const char *nptr, char **endptr, int base); extern unsigned long int strtoul(const char *nptr, char **endptr, int base); extern unsigned long long int strtoull(const char *nptr, char **endptr, int base); extern void srand(unsigned int); extern int rand(void); #define ATEXIT_MAX 32 extern int atexit(void (*h)(void)); extern void _handle_atexit(void); #define RAND_MAX 0x7FFFFFFF extern void abort(void); #define EXIT_SUCCESS 0 #define EXIT_FAILURE 1 #define NULL 0 extern void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); extern char * mktemp(char *); extern int mkstemp(char *); extern size_t mbstowcs(wchar_t *dest, const char *src, size_t n); extern size_t wcstombs(char * dest, const wchar_t *src, size_t n); typedef struct { int quot; int rem; } div_t; typedef struct { long int quot; long int rem; } ldiv_t; extern div_t div(int numerator, int denominator); extern ldiv_t ldiv(long numerator, long denominator); /* These are supposed to be in limits, but gcc screwed us */ #define PATH_MAX 4096 #define NAME_MAX 255 extern char *realpath(const char *path, char *resolved_path); _End_C_Header ================================================ FILE: base/usr/include/string.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern void * memset(void * dest, int c, size_t n); extern void * memcpy(void * dest, const void * src, size_t n); extern void * memmove(void * dest, const void * src, size_t n); extern void * memchr(const void * src, int c, size_t n); extern void * memrchr(const void * m, int c, size_t n); extern int memcmp(const void *vl, const void *vr, size_t n); extern char * strdup(const char * s); extern char * stpcpy(char * d, const char * s); extern char * strcpy(char * dest, const char * src); extern char * strchrnul(const char * s, int c); extern char * strchr(const char * s, int c); extern char * strrchr(const char * s, int c); extern char * strpbrk(const char * s, const char * b); extern char * strstr(const char * h, const char * n); extern char * strncpy(char * dest, const char * src, size_t n); extern int strcmp(const char * l, const char * r); extern int strncmp(const char *s1, const char *s2, size_t n); extern int strcoll(const char * s1, const char * s2); extern size_t strcspn(const char * s, const char * c); extern size_t strspn(const char * s, const char * c); extern size_t strlen(const char * s); extern char * strcat(char *dest, const char *src); extern char * strncat(char *dest, const char *src, size_t n); extern char * strtok(char * str, const char * delim); extern char * strtok_r(char * str, const char * delim, char ** saveptr); extern char * strncpy(char *dest, const char *src, size_t n); extern char * strerror(int errnum); extern size_t strxfrm(char *dest, const char *src, size_t n); extern char * strsignal(int sign); extern const char * const sys_siglist[]; _End_C_Header #include ================================================ FILE: base/usr/include/strings.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern int strcasecmp(const char *s1, const char *s2); extern int strncasecmp(const char *s1, const char *s2, size_t n); _End_C_Header ================================================ FILE: base/usr/include/sys/fswait.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header extern int fswait(int count, int * fds); extern int fswait2(int count, int * fds, int timeout); extern int fswait3(int count, int * fds, int timeout, int * out); _End_C_Header ================================================ FILE: base/usr/include/sys/ioctl.h ================================================ #pragma once #include #define IOCTLDTYPE 0x4F00 #define IOCTL_DTYPE_UNKNOWN -1 #define IOCTL_DTYPE_FILE 1 #define IOCTL_DTYPE_TTY 2 #define IOCTLTTYNAME 0x4F01 #define IOCTLTTYLOGIN 0x4F02 #define IOCTLSYNC 0x4F03 #define IOCTL_PACKETFS_QUEUED 0x5050 #define FIONBIO 0x4e424c4b ================================================ FILE: base/usr/include/sys/mman.h ================================================ #pragma once /* Nothing here, we don't have an mmap implementation yet? */ ================================================ FILE: base/usr/include/sys/mount.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header extern int mount(char * source, char * target, char * type, unsigned long flags, void * data); _End_C_Header ================================================ FILE: base/usr/include/sys/param.h ================================================ #pragma once #include #include #include #include #define __LITTLE_ENDIAN 1234 #define __BIG_ENDIAN 4321 #define __BYTE_ORDER __LITLE_ENDIAN #define BIG_ENDIAN __BIG_ENDIAN #define LITTLE_ENDIAN __LITTLE_ENDIAN #define BYTE_ORDER __BYTE_ORDER ================================================ FILE: base/usr/include/sys/ptrace.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header enum __ptrace_request { PTRACE_ATTACH, PTRACE_CONT, PTRACE_DETACH, PTRACE_TRACEME, PTRACE_GETREGS, PTRACE_PEEKDATA, PTRACE_SIGNALS_ONLY_PLZ, PTRACE_POKEDATA, PTRACE_SINGLESTEP, PTRACE_SETREGS }; enum __ptrace_event { PTRACE_EVENT_SYSCALL_ENTER, PTRACE_EVENT_SYSCALL_EXIT, PTRACE_EVENT_SINGLESTEP, }; #ifndef __kernel__ extern long ptrace(enum __ptrace_request request, pid_t pid, void * addr, void * data); #endif _End_C_Header ================================================ FILE: base/usr/include/sys/shm.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern void * shm_obtain(const char * path, size_t * size); extern int shm_release(const char * path); _End_C_Header ================================================ FILE: base/usr/include/sys/signal.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header #define SIGEV_NONE 1 #define SIGEV_SIGNAL 2 #define SIGEV_THREAD 3 #define SI_USER 1 #define SI_QUEUE 2 #define SI_TIMER 3 #define SI_ASYNCIO 4 #define SI_MESGQ 5 #define SA_NOCLDSTOP 1 #define SA_SIGINFO 2 #define SA_NODEFER 4 #define SA_RESETHAND 8 #define SA_RESTART 16 #define SIG_SETMASK 0 #define SIG_BLOCK 1 #define SIG_UNBLOCK 2 #define sa_handler _signal_handlers._handler #define sa_sigaction _signal_handlers._sigaction union sigval { int sival_int; void *sival_ptr; }; struct sigevent { int sigev_notify; int sigev_signo; union sigval sigev_value; }; typedef struct { int si_signo; int si_code; union sigval si_value; } siginfo_t; typedef unsigned long sigset_t; typedef void (*_sig_func_ptr)(int); struct sigaction { int sa_flags; sigset_t sa_mask; union { _sig_func_ptr _handler; void (*_sigaction)( int, siginfo_t *, void * ); } _signal_handlers; }; extern int kill(pid_t, int); _End_C_Header ================================================ FILE: base/usr/include/sys/signal_defs.h ================================================ #pragma once /* Signal names (from the Unix specification on signals) */ #define SIGHUP 1 /* Hangup */ #define SIGINT 2 /* Interupt */ #define SIGQUIT 3 /* Quit */ #define SIGILL 4 /* Illegal instruction */ #define SIGTRAP 5 /* A breakpoint or trace instruction has been reached */ #define SIGABRT 6 /* Another process has requested that you abort */ #define SIGEMT 7 /* Emulation trap XXX */ #define SIGFPE 8 /* Floating-point arithmetic exception */ #define SIGKILL 9 /* You have been stabbed repeated with a large knife */ #define SIGBUS 10 /* Bus error (device error) */ #define SIGSEGV 11 /* Segmentation fault */ #define SIGSYS 12 /* Bad system call */ #define SIGPIPE 13 /* Attempted to read or write from a broken pipe */ #define SIGALRM 14 /* This is your wakeup call. */ #define SIGTERM 15 /* You have been Schwarzenegger'd */ #define SIGUSR1 16 /* User Defined Signal #1 */ #define SIGUSR2 17 /* User Defined Signal #2 */ #define SIGCHLD 18 /* Child status report */ #define SIGPWR 19 /* We need moar powah! */ #define SIGWINCH 20 /* Your containing terminal has changed size */ #define SIGURG 21 /* An URGENT! event (On a socket) */ #define SIGPOLL 22 /* XXX OBSOLETE; socket i/o possible */ #define SIGSTOP 23 /* Stopped (signal) */ #define SIGTSTP 24 /* ^Z (suspend) */ #define SIGCONT 25 /* Unsuspended (please, continue) */ #define SIGTTIN 26 /* TTY input has stopped */ #define SIGTTOUT 27 /* TTY output has stopped */ #define SIGVTALRM 28 /* Virtual timer has expired */ #define SIGPROF 29 /* Profiling timer expired */ #define SIGXCPU 30 /* CPU time limit exceeded */ #define SIGXFSZ 31 /* File size limit exceeded */ #define SIGWAITING 32 /* Herp */ #define SIGDIAF 33 /* Die in a fire */ #define SIGHATE 34 /* The sending process does not like you */ #define SIGWINEVENT 35 /* Window server event */ #define SIGCAT 36 /* Everybody loves cats */ #define SIGTTOU 37 #define NUMSIGNALS 38 #define NSIG NUMSIGNALS ================================================ FILE: base/usr/include/sys/socket.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header #define AF_UNSPEC 0 #define AF_INET 1 #define AF_RAW 2 #define PF_INET AF_INET #define SOCK_STREAM 1 #define SOCK_DGRAM 2 #define SOCK_RAW 3 #define IPPROTO_IP 0 #define IPPROTO_ICMP 1 #define IPPROTO_TCP 6 #define IPPROTO_UDP 17 #define SOL_SOCKET 1 #define SO_KEEPALIVE 1 #define SO_REUSEADDR 2 #define SO_BINDTODEVICE 3 typedef size_t socklen_t; struct sockaddr { unsigned short sa_family; // address family, AF_xxx char sa_data[14]; // 14 bytes of protocol address }; struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; socklen_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; }; struct iovec { /* Scatter/gather array items */ void *iov_base; /* Starting address */ size_t iov_len; /* Number of bytes to transfer */ }; struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ }; struct sockaddr_storage { unsigned short ss_family; char _ss_pad[128]; }; struct cmsghdr { socklen_t cmsg_len; int cmsg_level; int cmsg_type; unsigned char cmsg_data[]; }; #define CMSG_DATA(cmsg) (&((struct cmsghdr*)(cmsg))->cmsg_data) #ifndef _KERNEL_ extern ssize_t recv(int sockfd, void *buf, size_t len, int flags); extern ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); extern ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); extern int socket(int domain, int type, int protocol); extern int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); extern int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen); extern int accept4(int sockfd, struct sockaddr * addr, socklen_t * addrlen, int flags); extern int listen(int sockfd, int backlog); extern int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen); extern int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); extern int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen); extern int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); extern int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen); extern int shutdown(int sockfd, int how); #endif _End_C_Header ================================================ FILE: base/usr/include/sys/stat.h ================================================ #pragma once #include <_cheader.h> #include #include #include _Begin_C_Header struct stat { dev_t st_dev; ino_t st_ino; mode_t st_mode; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size; struct timespec st_atim; struct timespec st_mtim; struct timespec st_ctim; blksize_t st_blksize; blkcnt_t st_blocks; #define st_atime st_atim.tv_sec #define st_mtime st_mtim.tv_sec #define st_ctime st_ctim.tv_sec }; #define _IFMT 0170000 /* type of file */ #define _IFDIR 0040000 /* directory */ #define _IFCHR 0020000 /* character special */ #define _IFBLK 0060000 /* block special */ #define _IFREG 0100000 /* regular */ #define _IFLNK 0120000 /* symbolic link */ #define _IFSOCK 0140000 /* socket */ #define _IFIFO 0010000 /* fifo */ #define S_BLKSIZE 1024 /* size of a block */ #define S_ISUID 0004000 /* set user id on execution */ #define S_ISGID 0002000 /* set group id on execution */ #define S_ISVTX 0001000 /* save swapped text even after use */ #define S_IREAD 0000400 /* read permission, owner */ #define S_IWRITE 0000200 /* write permission, owner */ #define S_IEXEC 0000100 /* execute/search permission, owner */ #define S_ENFMT 0002000 /* enforcement-mode locking */ #define S_IFMT _IFMT #define S_IFDIR _IFDIR #define S_IFCHR _IFCHR #define S_IFBLK _IFBLK #define S_IFREG _IFREG #define S_IFLNK _IFLNK #define S_IFSOCK _IFSOCK #define S_IFIFO _IFIFO #define S_IRWXU (S_IRUSR | S_IWUSR | S_IXUSR) #define S_IRUSR 0000400 /* read permission, owner */ #define S_IWUSR 0000200 /* write permission, owner */ #define S_IXUSR 0000100/* execute/search permission, owner */ #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP) #define S_IRGRP 0000040 /* read permission, group */ #define S_IWGRP 0000020 /* write permission, grougroup */ #define S_IXGRP 0000010/* execute/search permission, group */ #define S_IRWXO (S_IROTH | S_IWOTH | S_IXOTH) #define S_IROTH 0000004 /* read permission, other */ #define S_IWOTH 0000002 /* write permission, other */ #define S_IXOTH 0000001/* execute/search permission, other */ #define S_ISBLK(m) (((m)&_IFMT) == _IFBLK) #define S_ISCHR(m) (((m)&_IFMT) == _IFCHR) #define S_ISDIR(m) (((m)&_IFMT) == _IFDIR) #define S_ISFIFO(m) (((m)&_IFMT) == _IFIFO) #define S_ISREG(m) (((m)&_IFMT) == _IFREG) #define S_ISLNK(m) (((m)&_IFMT) == _IFLNK) #define S_ISSOCK(m) (((m)&_IFMT) == _IFSOCK) extern int stat(const char *file, struct stat *st); int stat(const char*, struct stat*) __asm__("__statns"); extern int lstat(const char *path, struct stat *st); int lstat(const char *, struct stat*) __asm__("__lstatns"); extern int fstat(int fd, struct stat *st); int fstat(int fd, struct stat*) __asm__("__fstatns"); extern int mkdir(const char *pathname, mode_t mode); extern mode_t umask(mode_t mask); _End_C_Header ================================================ FILE: base/usr/include/sys/sysfunc.h ================================================ #pragma once /** * The sysfunc interface is deprecated. Anything still using these * should be migrated to real system calls. The sysfunc interface * exists because it was annoying to add new syscall bindings to * newlib, but we're not using newlib anymore, so adding new system * calls should be easy. */ #include <_cheader.h> /* Privileged */ #define TOARU_SYS_FUNC_SYNC 3 #define TOARU_SYS_FUNC_LOGHERE 4 #define TOARU_SYS_FUNC_KDEBUG 7 #define TOARU_SYS_FUNC_INSMOD 8 /* Unpriviliged */ #define TOARU_SYS_FUNC_SETHEAP 9 #define TOARU_SYS_FUNC_MMAP 10 #define TOARU_SYS_FUNC_THREADNAME 11 #define TOARU_SYS_FUNC_SETVGACURSOR 13 #define TOARU_SYS_FUNC_SETGSBASE 14 #define TOARU_SYS_FUNC_NPROC 15 /* Experimental */ #define TOARU_SYS_FUNC_CLEARICACHE 42 #define TOARU_SYS_FUNC_MUNMAP 43 _Begin_C_Header extern int sysfunc(int command, char ** args); _End_C_Header ================================================ FILE: base/usr/include/sys/termios.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header /* Technically part of ioctl */ struct winsize { unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; unsigned short ws_ypixel; }; typedef unsigned int tcflag_t; typedef unsigned int speed_t; typedef unsigned char cc_t; /* reserving 0 for particular reason */ #define VEOF 1 /* ^D (end of file) */ #define VEOL 2 /* NULL (end of line) */ #define VERASE 3 /* ^H (backspace/del) */ #define VINTR 4 /* ^C (interrupt) */ #define VKILL 5 /* ^U (erase input buffer) */ #define VMIN 6 /* minimum number of characters for non-canonical read */ #define VQUIT 7 /* ^\ send SIGQUIT */ #define VSTART 8 /* ^Q restart STOPped input */ #define VSTOP 9 /* ^S stop input */ #define VSUSP 10 /* ^Z suspend foreground applicatioan (send SIGTSTP) */ #define VTIME 11 /* Timeout for non-canonical read, deciseconds */ #define VLNEXT 12 /* ^V literal next */ #define VWERASE 13 /* ^W erase word */ /* flags for input modes */ #define BRKINT 0000001 #define ICRNL 0000002 #define IGNBRK 0000004 #define IGNCR 0000010 #define IGNPAR 0000020 #define INLCR 0000040 #define INPCK 0000100 #define ISTRIP 0000200 #define IUCLC 0000400 #define IXANY 0001000 #define IXOFF 0002000 #define IXON 0004000 #define PARMRK 0010000 /* flags for output modes */ #define OPOST 0000001 #define OLCUC 0000002 #define ONLCR 0000004 #define OCRNL 0000010 #define ONOCR 0000020 #define ONLRET 0000040 #define OFILL 0000100 #define OFDEL 0000200 #define NLDLY 0000400 #define NL0 0000000 #define NL1 0000400 #define CRDLY 0003000 #define CR0 0000000 #define CR1 0001000 #define CR2 0002000 #define CR3 0003000 #define TABDLY 0014000 #define TAB0 0000000 #define TAB1 0004000 #define TAB2 0010000 #define TAB3 0014000 #define BSDLY 0020000 #define BS0 0000000 #define BS1 0020000 #define FFDLY 0100000 #define FF0 0000000 #define FF1 0100000 #define VTDLY 0040000 #define VT0 0000000 #define VT1 0040000 /* baud rates */ #define CBAUD 0100017 #define B0 0000000 #define B50 0000001 #define B75 0000002 #define B110 0000003 #define B134 0000004 #define B150 0000005 #define B200 0000006 #define B300 0000007 #define B600 0000010 #define B1200 0000011 #define B1800 0000012 #define B2400 0000013 #define B4800 0000014 #define B9600 0000015 #define B19200 0000016 #define B38400 0000017 #define B57600 0100000 #define B115200 0100001 #define B230400 0100002 #define B460800 0100003 #define B921600 0100004 /* control modes */ #define CSIZE 0000060 #define CS5 0000000 #define CS6 0000020 #define CS7 0000040 #define CS8 0000060 #define CSTOPB 0000100 #define CREAD 0000200 #define PARENB 0000400 #define PARODD 0001000 #define HUPCL 0002000 #define CLOCAL 0004000 /* local modes */ #define ISIG 0000001 #define ICANON 0000002 #define XCASE 0000004 #define ECHO 0000010 #define ECHOE 0000020 #define ECHOK 0000040 #define ECHONL 0000100 #define NOFLSH 0000200 #define TOSTOP 0000400 #define IEXTEN 0001000 #define ECHOCTL 0002000 /* attributes */ #define TCSANOW 0x0001 #define TCSADRAIN 0x0002 #define TCSAFLUSH 0x0004 #define TCIFLUSH 0x0001 #define TCIOFLUSH 0x0003 #define TCOFLUSH 0x0002 #define TCIOFF 0x0001 #define TCION 0x0002 #define TCOOFF 0x0004 #define TCOON 0x0008 #define NCCS 32 struct termios { tcflag_t c_iflag; tcflag_t c_oflag; tcflag_t c_cflag; tcflag_t c_lflag; cc_t c_cc[NCCS]; }; /* ioctl commands */ #define TCGETS 0x4000 /* Get termios struct */ #define TCSETS 0x4001 /* Set termios struct */ #define TCSETSW 0x4002 /* Set, but let drain first */ #define TCSETSF 0x4003 /* Set, but let flush first */ #define TCGETA TCGETS #define TCSETA TCSETS #define TCGETAW TCGETSW #define TCGETAF TCGETSF #define TCSBRK 0x4004 #define TCXONC 0x4005 #define TCFLSH 0x4006 #define TIOCEXCL 0x4007 #define TIOCNXCL 0x4008 #define TIOCSCTTY 0x4009 #define TIOCGPGRP 0x400A #define TIOCSPGRP 0x400B #define TIOCOUTQ 0x400C #define TIOCSTI 0x400D #define TIOCGWINSZ 0x400E #define TIOCSWINSZ 0x400F #define TIOCMGET 0x4010 #define TIOCMBIS 0x4011 #define TIOCMBIC 0x4012 #define TIOCMSET 0x4013 #define TIOCGSOFTCAR 0x4014 #define TIOCSSOFTCAR 0x4015 /* termios functions */ #ifndef _KERNEL_ extern speed_t cfgetispeed(const struct termios *); extern speed_t cfgetospeed(const struct termios *); extern int cfsetispeed(struct termios *, speed_t); extern int cfsetospeed(struct termios *, speed_t); extern int tcdrain(int); extern int tcflow(int, int); extern int tcflush(int, int); extern int tcgetattr(int, struct termios *); extern pid_t tcgetsid(int); extern int tcsendbreak(int, int); extern int tcsetattr(int, int, struct termios *); extern int ioctl(int, unsigned long, void*); #endif /* ndef _KERNEL_ */ _End_C_Header ================================================ FILE: base/usr/include/sys/time.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }; struct timezone { int tz_minuteswest; /* minutes west of Greenwich */ int tz_dsttime; /* type of DST correction */ }; extern int gettimeofday(struct timeval *p, void *z); extern int settimeofday(struct timeval *p, void *z); _End_C_Header ================================================ FILE: base/usr/include/sys/times.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header struct tms { clock_t tms_utime; clock_t tms_stime; clock_t tms_cutime; clock_t tms_cstime; }; extern clock_t times(struct tms *buf); _End_C_Header ================================================ FILE: base/usr/include/sys/types.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header typedef int gid_t; typedef int uid_t; typedef unsigned long dev_t; typedef unsigned long ino_t; typedef int mode_t; typedef int caddr_t; typedef unsigned short nlink_t; typedef unsigned long blksize_t; typedef unsigned long blkcnt_t; typedef long off_t; typedef long time_t; typedef long clock_t; #define __need_size_t #include typedef long ssize_t; typedef unsigned long useconds_t; typedef long suseconds_t; typedef int pid_t; #define FD_SETSIZE 64 /* compatibility with newlib */ typedef unsigned int fd_mask; typedef struct _fd_set { fd_mask fds_bits[1]; /* should be 64 bits */ } fd_set; _End_C_Header ================================================ FILE: base/usr/include/sys/uregs.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header #if defined(__x86_64__) # include # define uregs_syscall_result(r) ((r)->rax) # define uregs_syscall_num(r) ((r)->rax) # define uregs_syscall_arg1(r) ((r)->rdi) # define uregs_syscall_arg2(r) ((r)->rsi) # define uregs_syscall_arg3(r) ((r)->rdx) # define uregs_syscall_arg4(r) ((r)->r10) # define uregs_syscall_arg5(r) ((r)->r8) # define uregs_ip(r) ((r)->rip) # define uregs_bp(r) ((r)->rbp) # define UREGS_FMT \ " $rip=0x%016lx\n" \ " $rsi=0x%016lx,$rdi=0x%016lx,$rbp=0x%016lx,$rsp=0x%016lx\n" \ " $rax=0x%016lx,$rbx=0x%016lx,$rcx=0x%016lx,$rdx=0x%016lx\n" \ " $r8= 0x%016lx,$r9= 0x%016lx,$r10=0x%016lx,$r11=0x%016lx\n" \ " $r12=0x%016lx,$r13=0x%016lx,$r14=0x%016lx,$r15=0x%016lx\n" \ " cs=0x%016lx ss=0x%016lx rflags=0x%016lx int=0x%02lx err=0x%02lx\n" # define UREGS_ARGS(r) \ (r)->rip, \ (r)->rsi, (r)->rdi, (r)->rbp, (r)->rsp, \ (r)->rax, (r)->rbx, (r)->rcx, (r)->rdx, \ (r)->r8, (r)->r9, (r)->r10, (r)->r11, \ (r)->r12, (r)->r13, (r)->r14, (r)->r15, \ (r)->cs, (r)->ss, (r)->rflags, (r)->int_no, (r)->err_code #elif defined(__aarch64__) # include # define uregs_syscall_result(r) ((r)->x0) # define uregs_syscall_num(r) ((r)->x0) # define uregs_syscall_arg1(r) ((r)->x1) # define uregs_syscall_arg2(r) ((r)->x2) # define uregs_syscall_arg3(r) ((r)->x3) # define uregs_syscall_arg4(r) ((r)->x4) # define uregs_syscall_arg5(r) ((r)->x5) # define uregs_ip(r) ((r)->elr) # define uregs_bp(r) ((r)->x29) # define UREGS_FMT \ " $x00=0x%016lx,$x01=0x%016lx,$x02=0x%016lx,$x03=0x%016lx\n" \ " $x04=0x%016lx,$x05=0x%016lx,$x06=0x%016lx,$x07=0x%016lx\n" \ " $x08=0x%016lx,$x09=0x%016lx,$x10=0x%016lx,$x11=0x%016lx\n" \ " $x12=0x%016lx,$x13=0x%016lx,$x14=0x%016lx,$x15=0x%016lx\n" \ " $x16=0x%016lx,$x17=0x%016lx,$x18=0x%016lx,$x19=0x%016lx\n" \ " $x20=0x%016lx,$x21=0x%016lx,$x22=0x%016lx,$x23=0x%016lx\n" \ " $x24=0x%016lx,$x25=0x%016lx,$x26=0x%016lx,$x27=0x%016lx\n" \ " $x28=0x%016lx,$x29=0x%016lx,$x30=0x%016lx\n" \ " sp=0x%016lx elr=0x%016lx\n" # define UREGS_ARGS(r) \ (r)->x0, (r)->x1, (r)->x2, (r)->x3, (r)->x4, (r)->x5, (r)->x6, (r)->x7, \ (r)->x8, (r)->x9, (r)->x10, (r)->x11, (r)->x12, (r)->x13, (r)->x14, (r)->x15, \ (r)->x16, (r)->x17, (r)->x18, (r)->x19, (r)->x20, (r)->x21, (r)->x22, (r)->x23, \ (r)->x24, (r)->x25, (r)->x26, (r)->x27, (r)->x28, (r)->x29, (r)->x30, \ (r)->user_sp, (r)->elr #else # error Unsupported architecture #endif struct URegs { struct regs; #if defined(__aarch64__) uint64_t elr; #endif }; _End_C_Header ================================================ FILE: base/usr/include/sys/utsname.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header #define _UTSNAME_LENGTH 256 struct utsname { char sysname[_UTSNAME_LENGTH]; char nodename[_UTSNAME_LENGTH]; char release[_UTSNAME_LENGTH]; char version[_UTSNAME_LENGTH]; char machine[_UTSNAME_LENGTH]; char domainname[_UTSNAME_LENGTH]; }; #ifndef _KERNEL_ extern int uname(struct utsname *__name); #endif _End_C_Header ================================================ FILE: base/usr/include/sys/wait.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header #define WNOHANG 0x0001 #define WUNTRACED 0x0002 #define WSTOPPED 0x0004 #define WNOKERN 0x0010 /* This were taken from newlib, but they remain true */ #define WIFEXITED(w) (((w) & 0xff) == 0) #define WIFSIGNALED(w) (((w) & 0x7f) > 0 && (((w) & 0x7f) < 0x7f)) #define WIFSTOPPED(w) (((w) & 0xff) == 0x7f) #define WEXITSTATUS(w) (((w) >> 8) & 0xff) #define WTERMSIG(w) ((w) & 0x7f) #define WSTOPSIG WEXITSTATUS #ifndef _KERNEL_ extern pid_t wait(int*); extern pid_t waitpid(pid_t, int *, int); #endif _End_C_Header ================================================ FILE: base/usr/include/syscall.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header #define DECL_SYSCALL0(fn) long syscall_##fn() #define DECL_SYSCALL1(fn,p1) long syscall_##fn(p1) #define DECL_SYSCALL2(fn,p1,p2) long syscall_##fn(p1,p2) #define DECL_SYSCALL3(fn,p1,p2,p3) long syscall_##fn(p1,p2,p3) #define DECL_SYSCALL4(fn,p1,p2,p3,p4) long syscall_##fn(p1,p2,p3,p4) #define DECL_SYSCALL5(fn,p1,p2,p3,p4,p5) long syscall_##fn(p1,p2,p3,p4,p5) #ifdef __x86_64__ #ifdef __SYSCALL_INT7F # define __SYSCALL_ENTRY_INST "int $0x7F" # define __SYSCALL_CLOBBERS "memory" #else # define __SYSCALL_ENTRY_INST "syscall" # define __SYSCALL_CLOBBERS "rcx", "r11", "memory" #endif #define DEFN_SYSCALL0(fn, num) \ long syscall_##fn() { \ long a = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST : "=a" (a) : "a" ((long)a) : __SYSCALL_CLOBBERS); \ return a; \ } #define DEFN_SYSCALL1(fn, num, P1) \ long syscall_##fn(P1 p1) { \ long __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \ : "=a" (__res) \ : "a" (__res), "D" ((long)(p1)) : __SYSCALL_CLOBBERS ); \ return __res; \ } #define DEFN_SYSCALL2(fn, num, P1, P2) \ long syscall_##fn(P1 p1, P2 p2) { \ long __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \ : "=a" (__res) \ : "a" (__res), "D" ((long)(p1)), "S"((long)(p2)) : __SYSCALL_CLOBBERS ); \ return __res; \ } #define DEFN_SYSCALL3(fn, num, P1, P2, P3) \ long syscall_##fn(P1 p1, P2 p2, P3 p3) { \ long __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \ : "=a" (__res) \ : "a" (__res), "D" ((long)(p1)), "S"((long)(p2)), "d"((long)(p3)) : __SYSCALL_CLOBBERS ); \ return __res; \ } #define DEFN_SYSCALL4(fn, num, P1, P2, P3, P4) \ long syscall_##fn(P1 p1, P2 p2, P3 p3, P4 p4) { \ register long p4_ __asm__("r10") = (long)p4; \ long __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \ : "=a" (__res) \ : "a" (__res), "D" ((long)(p1)), "S"((long)(p2)), "d"((long)(p3)), "r"((long)(p4_)) : __SYSCALL_CLOBBERS ); \ return __res; \ } #define DEFN_SYSCALL5(fn, num, P1, P2, P3, P4, P5) \ long syscall_##fn(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { \ register long p4_ __asm__("r10") = (long)p4; \ register long p5_ __asm__("r8") = (long)p5; \ long __res = num; __asm__ __volatile__(__SYSCALL_ENTRY_INST \ : "=a" (__res) \ : "a" (__res), "D" ((long)(p1)), "S"((long)(p2)), "d"((long)(p3)), "r"((long)(p4_)), "r"((long)(p5_)) : __SYSCALL_CLOBBERS ); \ return __res; \ } #elif defined(__aarch64__) #define DEFN_SYSCALL0(fn, num) \ long syscall_##fn() { \ register long __res __asm__ ("x0") = num; \ __asm__ __volatile__("svc 0" : "=r" (__res) : \ "r" (__res) \ ); \ return __res; \ } #define DEFN_SYSCALL1(fn, num, P1) \ long syscall_##fn(P1 p1) { \ register long __res __asm__ ("x0") = num; \ register long x1 __asm__("x1") = (long)p1; \ __asm__ __volatile__("svc 0" : "=r" (__res) : \ "r" (__res), \ "r" (x1) \ ); \ return __res; \ } #define DEFN_SYSCALL2(fn, num, P1, P2) \ long syscall_##fn(P1 p1, P2 p2) { \ register long __res __asm__ ("x0") = num; \ register long x1 __asm__("x1") = (long)p1; \ register long x2 __asm__("x2") = (long)p2; \ __asm__ __volatile__("svc 0" : "=r" (__res) : \ "r" (__res), \ "r" (x1), \ "r" (x2) \ ); \ return __res; \ } #define DEFN_SYSCALL3(fn, num, P1, P2, P3) \ long syscall_##fn(P1 p1, P2 p2, P3 p3) { \ register long __res __asm__ ("x0") = num; \ register long x1 __asm__("x1") = (long)p1; \ register long x2 __asm__("x2") = (long)p2; \ register long x3 __asm__("x3") = (long)p3; \ __asm__ __volatile__("svc 0" : "=r" (__res) : \ "r" (__res), \ "r" (x1), \ "r" (x2), \ "r" (x3) \ ); \ return __res; \ } #define DEFN_SYSCALL4(fn, num, P1, P2, P3, P4) \ long syscall_##fn(P1 p1, P2 p2, P3 p3, P4 p4) { \ register long __res __asm__ ("x0") = num; \ register long x1 __asm__("x1") = (long)p1; \ register long x2 __asm__("x2") = (long)p2; \ register long x3 __asm__("x3") = (long)p3; \ register long x4 __asm__("x4") = (long)p4; \ __asm__ __volatile__("svc 0" : "=r" (__res) : \ "r" (__res), \ "r" (x1), \ "r" (x2), \ "r" (x3), \ "r" (x4) \ ); \ return __res; \ } #define DEFN_SYSCALL5(fn, num, P1, P2, P3, P4, P5) \ long syscall_##fn(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5) { \ register long __res __asm__ ("x0") = num; \ register long x1 __asm__("x1") = (long)p1; \ register long x2 __asm__("x2") = (long)p2; \ register long x3 __asm__("x3") = (long)p3; \ register long x4 __asm__("x4") = (long)p4; \ register long x5 __asm__("x5") = (long)p5; \ __asm__ __volatile__("svc 0" : "=r" (__res) : \ "r" (__res), \ "r" (x1), \ "r" (x2), \ "r" (x3), \ "r" (x4), \ "r" (x5) \ ); \ return __res; \ } #else # error "Invalid target, no system call linkage." #endif DECL_SYSCALL1(exit, int); DECL_SYSCALL0(geteuid); DECL_SYSCALL3(open, const char *, int, int); DECL_SYSCALL3(read, int, char *, size_t); DECL_SYSCALL3(write, int, char *, size_t); DECL_SYSCALL1(close, int); DECL_SYSCALL2(gettimeofday, void *, void *); DECL_SYSCALL3(execve, char *, char **, char **); DECL_SYSCALL0(fork); DECL_SYSCALL0(getpid); DECL_SYSCALL1(sbrk, int); DECL_SYSCALL3(socket, int, int, int); DECL_SYSCALL1(uname, void *); DECL_SYSCALL5(openpty, int *, int *, char *, void *, void *); DECL_SYSCALL3(seek, int, long, int); DECL_SYSCALL2(stat, int, void *); DECL_SYSCALL5(setsockopt,int,int,int,const void*,size_t); DECL_SYSCALL3(bind,int,const void*,size_t); DECL_SYSCALL4(accept,int,void*,size_t*,int); DECL_SYSCALL2(listen,int,int); DECL_SYSCALL3(connect,int,const void*,size_t); DECL_SYSCALL2(dup2, int, int); DECL_SYSCALL0(getuid); DECL_SYSCALL1(setuid, unsigned int); DECL_SYSCALL5(getsockopt,int,int,int,void*,size_t*); DECL_SYSCALL0(reboot); DECL_SYSCALL3(readdir, int, int, void *); DECL_SYSCALL1(chdir, char *); DECL_SYSCALL2(getcwd, char *, size_t); DECL_SYSCALL3(clone, uintptr_t, uintptr_t, void *); DECL_SYSCALL1(sethostname, char *); DECL_SYSCALL1(gethostname, char *); DECL_SYSCALL2(mkdir, char *, unsigned int); DECL_SYSCALL2(shm_obtain, const char *, size_t *); DECL_SYSCALL1(shm_release, const char *); DECL_SYSCALL2(kill, int, int); DECL_SYSCALL2(signal, int, void *); DECL_SYSCALL3(recv,int,void*,int); DECL_SYSCALL3(send,int,const void*,int); DECL_SYSCALL0(gettid); DECL_SYSCALL0(yield); DECL_SYSCALL2(sysfunc, int, char **); DECL_SYSCALL2(shutdown, int, int); DECL_SYSCALL2(sleepabs, unsigned long, unsigned long); DECL_SYSCALL2(sleep, unsigned long, unsigned long); DECL_SYSCALL3(ioctl, int, unsigned long, void *); DECL_SYSCALL2(access, char *, int); DECL_SYSCALL2(statf, char *, void *); DECL_SYSCALL2(chmod, char *, int); DECL_SYSCALL1(umask, int); DECL_SYSCALL1(unlink, char *); DECL_SYSCALL3(waitpid, int, int *, int); DECL_SYSCALL1(pipe, int *); DECL_SYSCALL5(mount, char *, char *, char *, unsigned long, void *); DECL_SYSCALL2(symlink, const char *, const char *); DECL_SYSCALL3(readlink, char *, char *, int); DECL_SYSCALL2(lstat, char *, void *); DECL_SYSCALL2(fswait,int,int*); DECL_SYSCALL3(fswait2,int,int*,int); DECL_SYSCALL3(chown,char*,int,int); DECL_SYSCALL0(setsid); DECL_SYSCALL2(setpgid,int,int); DECL_SYSCALL1(getpgid,int); DECL_SYSCALL4(fswait3, int, int*, int, int*); DECL_SYSCALL0(getgid); DECL_SYSCALL0(getegid); DECL_SYSCALL1(setgid, unsigned int); DECL_SYSCALL2(getgroups, int, int*); DECL_SYSCALL2(setgroups, int, const int*); DECL_SYSCALL1(times, struct tms*); DECL_SYSCALL4(ptrace, int, int, void*, void*); DECL_SYSCALL2(settimeofday, void *, void *); _End_C_Header ================================================ FILE: base/usr/include/syscall_nums.h ================================================ #pragma once #define SYS_EXT 0 #define SYS_GETEUID 1 #define SYS_OPEN 2 #define SYS_READ 3 #define SYS_WRITE 4 #define SYS_CLOSE 5 #define SYS_GETTIMEOFDAY 6 #define SYS_EXECVE 7 #define SYS_FORK 8 #define SYS_GETPID 9 #define SYS_SBRK 10 #define SYS_SOCKET 11 #define SYS_UNAME 12 #define SYS_OPENPTY 13 #define SYS_SEEK 14 #define SYS_STAT 15 #define SYS_SETSOCKOPT 16 #define SYS_BIND 17 #define SYS_ACCEPT 18 #define SYS_LISTEN 19 #define SYS_CONNECT 20 /* 21 - unused, was mkpipe */ #define SYS_DUP2 22 #define SYS_GETUID 23 #define SYS_SETUID 24 #define SYS_GETSOCKOPT 25 #define SYS_REBOOT 26 #define SYS_READDIR 27 #define SYS_CHDIR 28 #define SYS_GETCWD 29 #define SYS_CLONE 30 #define SYS_SETHOSTNAME 31 #define SYS_GETHOSTNAME 32 #define SYS_PTRACE 33 #define SYS_MKDIR 34 #define SYS_SHM_OBTAIN 35 #define SYS_SHM_RELEASE 36 #define SYS_KILL 37 #define SYS_SIGNAL 38 #define SYS_RECV 39 #define SYS_SEND 40 #define SYS_GETTID 41 #define SYS_YIELD 42 #define SYS_SYSFUNC 43 #define SYS_SHUTDOWN 44 #define SYS_SLEEPABS 45 #define SYS_SLEEP 46 #define SYS_IOCTL 47 #define SYS_ACCESS 48 #define SYS_STATF 49 #define SYS_CHMOD 50 #define SYS_UMASK 51 #define SYS_UNLINK 52 #define SYS_WAITPID 53 #define SYS_PIPE 54 #define SYS_MOUNT 55 #define SYS_SYMLINK 56 #define SYS_READLINK 57 #define SYS_LSTAT 58 #define SYS_FSWAIT 59 #define SYS_FSWAIT2 60 #define SYS_CHOWN 61 #define SYS_SETSID 62 #define SYS_SETPGID 63 #define SYS_GETPGID 64 #define SYS_FSWAIT3 65 #define SYS_GETGID 66 #define SYS_GETEGID 67 #define SYS_SETGID 68 #define SYS_GETGROUPS 69 #define SYS_SETGROUPS 70 #define SYS_TIMES 71 #define SYS_SETTIMEOFDAY 72 #define SYS_SIGACTION 73 #define SYS_SIGPENDING 74 #define SYS_SIGPROCMASK 75 #define SYS_SIGSUSPEND 76 #define SYS_SIGWAIT 77 #define SYS_GETSOCKNAME 78 #define SYS_GETPEERNAME 79 #define SYS_PREAD 80 #define SYS_PWRITE 81 #define SYS_RENAME 82 #define SYS_FCNTL 83 #define SYS_FCHMOD 84 #define SYS_FCHOWN 85 #define SYS_TRUNCATE 86 #define SYS_FTRUNCATE 87 ================================================ FILE: base/usr/include/termio.h ================================================ #pragma once #include #include ================================================ FILE: base/usr/include/termios.h ================================================ #pragma once #include ================================================ FILE: base/usr/include/time.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header struct tm { int tm_sec; /* Seconds (0-60) */ int tm_min; /* Minutes (0-59) */ int tm_hour; /* Hours (0-23) */ int tm_mday; /* Day of the month (1-31) */ int tm_mon; /* Month (0-11) */ int tm_year; /* Year - 1900 */ int tm_wday; /* Day of the week (0-6, Sunday = 0) */ int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */ int tm_isdst; /* Daylight saving time */ const char * _tm_zone_name; int _tm_zone_offset; }; extern struct tm *localtime(const time_t *timep); extern struct tm *gmtime(const time_t *timep); extern struct tm *localtime_r(const time_t *timep, struct tm * buf); extern struct tm *gmtime_r(const time_t *timep, struct tm * buf); extern size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); extern time_t time(time_t * out); extern double difftime(time_t a, time_t b); extern time_t mktime(struct tm *tm); extern char * asctime(const struct tm *tm); extern char * ctime(const time_t * timep); extern clock_t clock(void); #define CLOCKS_PER_SEC 1000000 #include typedef int clockid_t; #define CLOCK_REALTIME 0 #define CLOCK_MONOTONIC 1 extern int clock_gettime(clockid_t clk_id, struct timespec *tp); extern int clock_getres(clockid_t clk_id, struct timespec *res); _End_C_Header ================================================ FILE: base/usr/include/toaru/auth.h ================================================ /** * @brief Authentication Helpers * * This library allows multiple login programs (login, sudo, glogin) * to share authentication code by providing a single palce to check * passwords against /etc/master.passwd and to set typical login vars. * * @copyright * 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 */ #pragma once #include <_cheader.h> #include _Begin_C_Header /** * toaru_auth_check_pass * * Returns the uid for the request user on success, -1 on failure. */ extern int toaru_auth_check_pass(char * user, char * pass); /** * toaru_auth_set_vars * * Sets various environment variables (HOME, USER, SHELL, etc.) * for the current user. */ extern void toaru_auth_set_vars(void); /** * Set supplementary groups from /etc/groups */ extern void toaru_auth_set_groups(uid_t uid); /** * Do the above two steps, and setuid, and setgid... */ extern void toaru_set_credentials(uid_t uid); _End_C_Header ================================================ FILE: base/usr/include/toaru/button.h ================================================ /** * @brief Draws buttons. * * @copyright * 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 */ /* This definition will eventually change with the rest of the widget toolkit */ struct TTKButton { int x; int y; int width; int height; char * title; int hilight; }; extern void ttk_button_draw(gfx_context_t * ctx, struct TTKButton * button); ================================================ FILE: base/usr/include/toaru/confreader.h ================================================ /** * @brief Configuration File Reader * * Reads an implementation of the INI "standard". Note that INI * isn't actually a standard. We support the following: * - ; comments * - foo=bar keyword assignment * - [sections] * * @copyright * 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 */ #pragma once #include <_cheader.h> #include _Begin_C_Header /** * A configuration file is represented as a hashmap of sections, * which are themselves hashmaps. You may modify these hashmaps * to change key values, or add new sections. */ typedef struct { hashmap_t * sections; } confreader_t; /** * confreader_load * * Open a configuration file and read its contents. * Returns NULL if the requested file failed to open. */ extern confreader_t * confreader_load(const char * file); /** * confreader_get * * Retrieve a string value from the config file. * An empty string for `section` represents the default section. * If the value is not found, NULL is returned. */ extern char * confreader_get(confreader_t * ctx, char * section, char * value); /** * confreader_getd * * Retrieve a string value from the config file, falling back * to a default value if the requested key is not found. */ extern char * confreader_getd(confreader_t * ctx, char * section, char * value, char * def); /** * confreader_int * * Retrieve an integer value from the config file. * * This is a convenience wrapper that calls atoi(). * If the value is not found, 0 is returned. */ extern int confreader_int(confreader_t * ctx, char * section, char * value); /** * confreader_intd * * Retrieve an integer value from the config file, falling back * to a default if the requested key is not found. */ extern int confreader_intd(confreader_t * ctx, char * section, char * value, int def); /** * confreader_free * * Free the memory associated with a config file. */ extern void confreader_free(confreader_t * conf); /** * confreader_write * * Write a config file back out to a file. */ extern int confreader_write(confreader_t * config, const char * file); /** * confreader_create_empty * * Create an empty configuration file to be modified directly * through hashmap values. */ extern confreader_t * confreader_create_empty(void); _End_C_Header ================================================ FILE: base/usr/include/toaru/decodeutf8.h ================================================ /** * @brief Decode UTF8 to codepoints. * * This is a simple implementation of a UTF-8 decoder with an * equivalent API to the older third-party (and much cooler...) * version that ToaruOS used to use. Keep feeding it bytes and * will eventually set *codep to a codepoint. Should also be able * to detect bad UTF-8. * * @copyright * 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 */ #include #define UTF8_ACCEPT 0 #define UTF8_REJECT 1 /** * Conceptually similar to its predecessor, this implementation is much * less cool, as it uses three separate state tables and more shifts. */ static inline uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) { static int state_table[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */ 1,1,1,1,1,1,1,1, /* 10xxxxxx */ 2,2,2,2, /* 110xxxxx */ 3,3, /* 1110xxxx */ 4, /* 11110xxx */ 1 /* 11111xxx */ }; static int mask_bytes[32] = { 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x1F,0x1F,0x1F,0x1F, 0x0F,0x0F, 0x07, 0x00 }; static int next[5] = { 0, 1, 0, 2, 3 }; if (*state == UTF8_ACCEPT) { *codep = byte & mask_bytes[byte >> 3]; *state = state_table[byte >> 3]; } else if (*state > 0) { *codep = (byte & 0x3F) | (*codep << 6); *state = next[*state]; } return *state; } ================================================ FILE: base/usr/include/toaru/decorations.h ================================================ /** * @brief Client-side Window Decoration library * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2018 K. Lange */ #pragma once #include <_cheader.h> #include #include _Begin_C_Header /* * Render decorations to a window. A buffer pointer is * provided so that you may render in double-buffered mode. * * Run me at least once for each window, and any time you may need to * redraw them. */ extern void render_decorations(yutani_window_t * window, gfx_context_t * ctx, char * title); /** DEPRECATED */ extern void render_decorations_inactive(yutani_window_t * window, gfx_context_t * ctx, char * title); /** * Decoration boundaries */ struct decor_bounds { int top_height; int bottom_height; int left_width; int right_width; /* Convenience */ int width; int height; }; /* * Used by decoration libraries to set callbacks */ extern void (*decor_render_decorations)(yutani_window_t *, gfx_context_t *, char *, int); extern int (*decor_check_button_press)(yutani_window_t *, int x, int y); extern int (*decor_get_bounds)(yutani_window_t *, struct decor_bounds *); extern int decor_hover_button; extern yutani_window_t * decor_hover_window; /* * Run me once to set things up */ extern void init_decorations(); extern int decor_handle_event(yutani_t * yctx, yutani_msg_t * m); /* Callbacks for handle_event */ extern void decor_set_close_callback(void (*callback)(yutani_window_t *)); extern void decor_set_resize_callback(void (*callback)(yutani_window_t *)); extern void decor_set_maximize_callback(void (*callback)(yutani_window_t *)); extern yutani_window_t * decor_show_default_menu(yutani_window_t * window, int y, int x); /* Responses from handle_event */ #define DECOR_OTHER 1 /* Clicked on title bar but otherwise unimportant */ #define DECOR_CLOSE 2 /* Clicked on close button */ #define DECOR_RESIZE 3 /* Resize button */ #define DECOR_MAXIMIZE 4 #define DECOR_RIGHT 5 #define DECOR_MINIMIZE 6 #define DECOR_REDRAW 7 #define DECOR_ACTIVE 0 #define DECOR_INACTIVE 1 #define DECOR_FLAG_DECORATED (1 << 0) #define DECOR_FLAG_NO_MAXIMIZE (1 << 1) #define DECOR_FLAG_TILED (0xF << 2) #define DECOR_FLAG_TILE_LEFT (0x1 << 2) #define DECOR_FLAG_TILE_RIGHT (0x2 << 2) #define DECOR_FLAG_TILE_UP (0x4 << 2) #define DECOR_FLAG_TILE_DOWN (0x8 << 2) _End_C_Header ================================================ FILE: base/usr/include/toaru/drawstring.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header void draw_string(gfx_context_t * ctx, int x, int y, uint32_t _fg, char * str); int draw_string_width(char * str); _End_C_Header ================================================ FILE: base/usr/include/toaru/graphics.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header #define GFX_W(ctx) ((ctx)->width) /* Display width */ #define GFX_H(ctx) ((ctx)->height) /* Display height */ #define GFX_B(ctx) ((ctx)->depth / 8) /* Display byte depth */ #define GFX_S(ctx) ((ctx)->stride) /* Stride */ #define _RED(color) ((color & 0x00FF0000) / 0x10000) #define _GRE(color) ((color & 0x0000FF00) / 0x100) #define _BLU(color) ((color & 0x000000FF) / 0x1) #define _ALP(color) ((color & 0xFF000000) / 0x1000000) /* * Macros make verything easier. */ #define GFX(ctx,x,y) *((uint32_t *)&((ctx)->backbuffer)[(GFX_S(ctx) * (y) + (x) * GFX_B(ctx))]) #define GFXR(ctx,x,y) *((uint32_t *)&((ctx)->buffer)[(GFX_S(ctx) * (y) + (x) * GFX_B(ctx))]) #define SPRITE(sprite,x,y) sprite->bitmap[sprite->width * (y) + (x)] #define SMASKS(sprite,x,y) sprite->masks[sprite->width * (y) + (x)] typedef struct sprite { uint16_t width; uint16_t height; uint32_t * bitmap; uint32_t * masks; uint32_t blank; uint8_t alpha; } sprite_t; typedef struct context { uint16_t width; uint16_t height; uint16_t depth; uint32_t size; char * buffer; char * backbuffer; char * clips; int32_t clips_size; uint32_t stride; uint32_t _true_stride; } gfx_context_t; extern gfx_context_t * init_graphics_fullscreen(); extern gfx_context_t * init_graphics_fullscreen_double_buffer(); extern void reinit_graphics_fullscreen(gfx_context_t * ctx); #define ALPHA_OPAQUE 0 #define ALPHA_MASK 1 #define ALPHA_EMBEDDED 2 #define ALPHA_INDEXED 3 #define ALPHA_FORCE_SLOW_EMBEDDED 4 extern uint32_t rgb(uint8_t r, uint8_t g, uint8_t b); extern uint32_t rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a); extern uint32_t alpha_blend(uint32_t bottom, uint32_t top, uint32_t mask); extern uint32_t alpha_blend_rgba(uint32_t bottom, uint32_t top); extern uint32_t framebuffer_stride(void); extern void flip(gfx_context_t * ctx); void clear_buffer(gfx_context_t * ctx); extern gfx_context_t * init_graphics_sprite(sprite_t * sprite); extern sprite_t * create_sprite(size_t width, size_t height, int alpha); extern void blur_context(gfx_context_t * _dst, gfx_context_t * _src, double amount); extern void blur_context_no_vignette(gfx_context_t * _dst, gfx_context_t * _src, double amount); extern void blur_context_box(gfx_context_t * _src, int radius); extern void sprite_free(sprite_t * sprite); extern void draw_line(gfx_context_t * ctx, int32_t x0, int32_t x1, int32_t y0, int32_t y1, uint32_t color); extern 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); extern void draw_fill(gfx_context_t * ctx, uint32_t color); typedef double gfx_matrix_t[2][3]; extern int load_sprite(sprite_t * sprite, const char * filename); extern int load_sprite_bmp(sprite_t * sprite, const char * filename); extern void draw_sprite(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y); extern void draw_sprite_scaled(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, uint16_t width, uint16_t height); extern 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); extern void draw_sprite_alpha(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float alpha); extern void draw_sprite_alpha_paint(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float alpha, uint32_t c); extern void draw_sprite_rotate(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float rotation, float alpha); extern void draw_sprite_transform(gfx_context_t * ctx, const sprite_t * sprite, gfx_matrix_t matrix, float alpha); //extern void context_to_png(FILE * file, gfx_context_t * ctx); extern uint32_t premultiply(uint32_t color); extern void gfx_add_clip(gfx_context_t * ctx, int32_t x, int32_t y, int32_t w, int32_t h); extern void gfx_clear_clip(gfx_context_t * ctx); extern void gfx_no_clip(gfx_context_t * ctx); extern uint32_t interp_colors(uint32_t bottom, uint32_t top, uint8_t interp); extern 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); extern void draw_rectangle(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, uint32_t color); extern void draw_rectangle_solid(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, uint32_t color); extern 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); struct gfx_point { float x; float y; }; extern float gfx_point_distance(const struct gfx_point * a, const struct gfx_point * b); extern float gfx_point_distance_squared(const struct gfx_point * a, const struct gfx_point * b); extern float gfx_point_dot(const struct gfx_point * a, const struct gfx_point * b); extern struct gfx_point gfx_point_sub(const struct gfx_point * a, const struct gfx_point * b); extern struct gfx_point gfx_point_add(const struct gfx_point * a, const struct gfx_point * b); extern float gfx_line_distance(const struct gfx_point * p, const struct gfx_point * v, const struct gfx_point * w); extern void draw_line_aa_points(gfx_context_t * ctx, struct gfx_point *v, struct gfx_point *w, uint32_t color, float thickness); extern 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); struct gradient_definition { int height; int y; uint32_t top; uint32_t bottom; }; extern uint32_t gfx_vertical_gradient_pattern(int32_t x, int32_t y, double alpha, void * extra); extern gfx_context_t * init_graphics_subregion(gfx_context_t * base, int x, int y, int width, int height); extern void gfx_matrix_identity(gfx_matrix_t); extern void gfx_matrix_scale(gfx_matrix_t, double x, double y); extern void gfx_matrix_translate(gfx_matrix_t, double x, double y); extern void gfx_matrix_rotate(gfx_matrix_t, double rotation); extern void gfx_matrix_shear(gfx_matrix_t matrix, double x, double y); extern int gfx_matrix_invert(gfx_matrix_t m, gfx_matrix_t inverse); extern void gfx_apply_matrix(double x, double y, gfx_matrix_t matrix, double *out_x, double *out_y); _End_C_Header ================================================ FILE: base/usr/include/toaru/hashmap.h ================================================ #pragma once #include <_cheader.h> #ifdef _KERNEL_ # include #else # include # include # include #endif #include _Begin_C_Header typedef unsigned int (*hashmap_hash_t) (const void * key); typedef int (*hashmap_comp_t) (const void * a, const void * b); typedef void (*hashmap_free_t) (void *); typedef void * (*hashmap_dupe_t) (const void *); typedef struct hashmap_entry { char * key; void * value; struct hashmap_entry * next; } hashmap_entry_t; typedef struct hashmap { hashmap_hash_t hash_func; hashmap_comp_t hash_comp; hashmap_dupe_t hash_key_dup; hashmap_free_t hash_key_free; hashmap_free_t hash_val_free; size_t size; hashmap_entry_t ** entries; } hashmap_t; extern hashmap_t * hashmap_create(int size); extern hashmap_t * hashmap_create_int(int size); extern void * hashmap_set(hashmap_t * map, const void * key, void * value); extern void * hashmap_get(hashmap_t * map, const void * key); extern void * hashmap_remove(hashmap_t * map, const void * key); extern int hashmap_has(hashmap_t * map, const void * key); extern list_t * hashmap_keys(hashmap_t * map); extern list_t * hashmap_values(hashmap_t * map); extern void hashmap_free(hashmap_t * map); extern unsigned int hashmap_string_hash(const void * key); extern int hashmap_string_comp(const void * a, const void * b); extern void * hashmap_string_dupe(const void * key); extern int hashmap_is_empty(hashmap_t * map); _End_C_Header ================================================ FILE: base/usr/include/toaru/icon_cache.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern sprite_t * icon_get_16(const char * name); extern sprite_t * icon_get_48(const char * name); _End_C_Header ================================================ FILE: base/usr/include/toaru/inflate.h ================================================ /** * @brief DEFLATE inflater * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2020 K. Lange */ #pragma once #include <_cheader.h> #include _Begin_C_Header struct huff_ring; struct inflate_context { /* Consumer-private pointers for input/output storage (eg. FILE *) */ void * input_priv; void * output_priv; /* Methods for reading / writing from the input /output */ uint8_t (*get_input)(struct inflate_context * ctx); void (*write_output)(struct inflate_context * ctx, unsigned int sym); /* Bit buffer, which holds at most 8 bits from the input */ int bit_buffer; int buffer_size; /* Output ringbuffer for backwards lookups */ struct huff_ring * ring; }; int deflate_decompress(struct inflate_context * ctx); int gzip_decompress(struct inflate_context * ctx); _End_C_Header ================================================ FILE: base/usr/include/toaru/jpeg.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern int load_sprite_jpg(sprite_t * sprite, char * filename); _End_C_Header ================================================ FILE: base/usr/include/toaru/json.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header #define JSON_TYPE_OBJECT 0 #define JSON_TYPE_ARRAY 1 #define JSON_TYPE_STRING 2 #define JSON_TYPE_NUMBER 3 #define JSON_TYPE_BOOL 4 #define JSON_TYPE_NULL 5 struct JSON_Value { int type; union { char * string; double number; list_t * array; hashmap_t * object; int boolean; }; }; #define JSON_KEY(v,k) ((struct JSON_Value *)(hashmap_get(v->object,k))) #define JSON_IND(v,i) ((struct JSON_Value *)(list_index(v->array,i))) /** * json_free * * Free a struct JSON_Value, and its contents recursively if it's an array, * object, string, etc. */ extern void json_free(struct JSON_Value *); /** * json_parse * * Parse a string into a JSON_Value */ extern struct JSON_Value * json_parse(const char *); /** * json_parse_file * * Open a file path and parse its contents as JSON * (Convenience function) */ extern struct JSON_Value * json_parse_file(const char * filename); _End_C_Header ================================================ FILE: base/usr/include/toaru/kbd.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header #define KEY_NONE 0 #define KEY_BACKSPACE 8 #define KEY_CTRL_A 1 #define KEY_CTRL_B 2 #define KEY_CTRL_C 3 #define KEY_CTRL_D 4 #define KEY_CTRL_E 5 #define KEY_CTRL_F 6 #define KEY_CTRL_G 7 #define KEY_CTRL_H 8 #define KEY_CTRL_I 9 #define KEY_CTRL_J 10 #define KEY_CTRL_K 11 #define KEY_CTRL_L 12 #define KEY_CTRL_M 13 #define KEY_CTRL_N 14 #define KEY_CTRL_O 15 #define KEY_CTRL_P 16 #define KEY_CTRL_Q 17 #define KEY_CTRL_R 18 #define KEY_CTRL_S 19 #define KEY_CTRL_T 20 #define KEY_CTRL_U 21 #define KEY_CTRL_V 22 #define KEY_CTRL_W 23 #define KEY_CTRL_X 24 #define KEY_CTRL_Y 25 #define KEY_CTRL_Z 26 #define KEY_ESCAPE 27 #define KEY_NORMAL_MAX 256 #define KEY_ARROW_UP 257 #define KEY_ARROW_DOWN 258 #define KEY_ARROW_RIGHT 259 #define KEY_ARROW_LEFT 260 #define KEY_LEFT_CTRL 1001 #define KEY_LEFT_SHIFT 1002 #define KEY_LEFT_ALT 1003 #define KEY_LEFT_SUPER 1004 #define KEY_RIGHT_CTRL 1011 #define KEY_RIGHT_SHIFT 1012 #define KEY_RIGHT_ALT 1013 #define KEY_RIGHT_SUPER 1014 #define KEY_F1 2001 #define KEY_F2 2002 #define KEY_F3 2003 #define KEY_F4 2004 #define KEY_F5 2005 #define KEY_F6 2006 #define KEY_F7 2007 #define KEY_F8 2008 #define KEY_F9 2009 #define KEY_F10 2010 #define KEY_F11 2011 #define KEY_F12 2012 #define KEY_PAGE_DOWN 2013 #define KEY_PAGE_UP 2014 #define KEY_HOME 2015 #define KEY_END 2016 #define KEY_DEL 2017 #define KEY_INSERT 2018 #define KEY_PAUSE 2019 #define KEY_SCROLL_LOCK 2020 #define KEY_PRINT_SCREEN 2021 #define KEY_APP 2022 #define KEY_NUM_0 2500 #define KEY_NUM_1 2501 #define KEY_NUM_2 2502 #define KEY_NUM_3 2503 #define KEY_NUM_4 2504 #define KEY_NUM_5 2505 #define KEY_NUM_6 2506 #define KEY_NUM_7 2507 #define KEY_NUM_8 2508 #define KEY_NUM_9 2509 #define KEY_NUM_DOT 2510 #define KEY_NUM_DIV 2511 #define KEY_NUM_STAR 2512 #define KEY_NUM_MINUS 2513 #define KEY_NUM_PLUS 2514 #define KEY_NUM_ENTER 2515 #define KEY_MOD_LEFT_CTRL 0x01 #define KEY_MOD_LEFT_SHIFT 0x02 #define KEY_MOD_LEFT_ALT 0x04 #define KEY_MOD_LEFT_SUPER 0x08 #define KEY_MOD_RIGHT_CTRL 0x10 #define KEY_MOD_RIGHT_SHIFT 0x20 #define KEY_MOD_RIGHT_ALT 0x40 #define KEY_MOD_RIGHT_SUPER 0x80 #define KEY_ACTION_DOWN 0x01 #define KEY_ACTION_UP 0x02 typedef unsigned int kbd_key_t; typedef unsigned int kbd_mod_t; typedef unsigned char kbd_act_t; typedef struct { kbd_key_t keycode; kbd_mod_t modifiers; kbd_act_t action; unsigned 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/. */ } key_event_t; typedef struct { int kbd_state; int kbd_s_state; int k_ctrl; int k_shift; int k_alt; int k_super; int kl_ctrl; int kl_shift; int kl_alt; int kl_super; int kr_ctrl; int kr_shift; int kr_alt; int kr_super; int kbd_esc_buf; } key_event_state_t; extern int kbd_scancode(key_event_state_t * state, unsigned char c, key_event_t * event); _End_C_Header ================================================ FILE: base/usr/include/toaru/list.h ================================================ #pragma once #include <_cheader.h> #ifdef _KERNEL_ # include #else # include # include # include #endif _Begin_C_Header typedef struct node { struct node * next; struct node * prev; void * value; void * owner; } __attribute__((packed)) node_t; typedef struct { node_t * head; node_t * tail; size_t length; } __attribute__((packed)) list_t; extern void list_destroy(list_t * list); extern void list_free(list_t * list); extern void list_append(list_t * list, node_t * item); extern node_t * list_insert(list_t * list, void * item); extern list_t * list_create(void); extern node_t * list_find(list_t * list, void * value); extern int list_index_of(list_t * list, void * value); extern void list_remove(list_t * list, size_t index); extern void list_delete(list_t * list, node_t * node); extern node_t * list_pop(list_t * list); extern node_t * list_dequeue(list_t * list); extern list_t * list_copy(list_t * original); extern void list_merge(list_t * target, list_t * source); extern void * list_index(list_t * list, int index); extern void list_append_after(list_t * list, node_t * before, node_t * node); extern node_t * list_insert_after(list_t * list, node_t * before, void * item); extern void list_append_before(list_t * list, node_t * after, node_t * node); extern node_t * list_insert_before(list_t * list, node_t * after, void * item); /* Known to conflict with some popular third-party libraries. */ #ifndef TOARU_LIST_NO_FOREACH # define foreach(i, list) for (node_t * i = (list)->head; i != NULL; i = i->next) # define foreachr(i, list) for (node_t * i = (list)->tail; i != NULL; i = i->prev) #endif _End_C_Header ================================================ FILE: base/usr/include/toaru/markup.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header struct markup_tag { char * name; hashmap_t * options; }; struct markup_state; typedef int (*markup_callback_tag_open)(struct markup_state * self, void * user, struct markup_tag * tag); typedef int (*markup_callback_tag_close)(struct markup_state * self, void * user, char * tag_name); typedef int (*markup_callback_data)(struct markup_state * self, void * user, char * data); extern struct markup_state * markup_init(void * user, markup_callback_tag_open open, markup_callback_tag_close close, markup_callback_data data); extern int markup_free_tag(struct markup_tag * tag); extern int markup_parse(struct markup_state * state, char c); extern int markup_finish(struct markup_state * state); _End_C_Header ================================================ FILE: base/usr/include/toaru/markup_text.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header int markup_string_width(const char * str); int markup_string_height(const char * str); int markup_draw_string(gfx_context_t * ctx, int x, int y, const char * str, uint32_t color); void markup_text_init(void); struct MarkupState * markup_setup_renderer(gfx_context_t * ctx, int x, int y, uint32_t color, int dryrun); void markup_set_base_font_size(struct MarkupState * state, int size); void markup_set_base_state(struct MarkupState * state, int mode); int markup_push_string(struct MarkupState * state, const char * str); int markup_push_raw_string(struct MarkupState * state, const char * str); int markup_finish_renderer(struct MarkupState * state); #define MARKUP_TEXT_STATE_BOLD (1 << 0) #define MARKUP_TEXT_STATE_OBLIQUE (1 << 1) #define MARKUP_TEXT_STATE_HEADING (1 << 2) #define MARKUP_TEXT_STATE_SMALL (1 << 3) #define MARKUP_TEXT_STATE_MONO (1 << 4) _End_C_Header ================================================ FILE: base/usr/include/toaru/menu.h ================================================ #pragma once #include <_cheader.h> #include #include #include #include _Begin_C_Header enum MenuEntry_Type { MenuEntry_Unknown, MenuEntry_Normal, MenuEntry_Submenu, MenuEntry_Separator, MenuEntry_Toggle, }; struct MenuList; struct MenuEntry; struct MenuEntryVTable { size_t methods; void (*renderer)(gfx_context_t *, struct MenuEntry *, int); void (*focus_change)(struct MenuEntry *, int); void (*activate)(struct MenuEntry *, int); int (*mouse_event)(struct MenuEntry *, struct yutani_msg_window_mouse_event *); }; struct MenuEntry { enum MenuEntry_Type _type; struct MenuList * _owner; void * _private; int height; /* All must have a height, so put it here. */ int width; /* Actual width */ int rwidth; /* Requested width */ int hilight; /* Is currently hilighted */ int offset; /* Our offset when we were rendered */ struct MenuEntryVTable * vtable; void (*callback)(struct MenuEntry *); }; struct MenuEntry_Normal { struct MenuEntry; /* dependent on plan9 extensions */ char * icon; char * title; char * action; unsigned long flags; }; struct MenuEntry_Toggle { struct MenuEntry_Normal; int set; }; struct MenuEntry_Submenu { struct MenuEntry_Normal; struct MenuList * _my_child; }; struct MenuEntry_Separator { struct MenuEntry; }; struct MenuList { list_t * entries; gfx_context_t * ctx; yutani_window_t * window; struct MenuSet * set; struct MenuList * child; struct MenuList * parent; struct menu_bar * _bar; int closed; int flags; int tail_offset; yutani_window_t * main_window; }; struct MenuSet { hashmap_t * _menus; }; extern struct MenuEntry * menu_create_normal(const char * icon, const char * action, const char * title, void (*callback)(struct MenuEntry *)); extern struct MenuEntry * menu_create_toggle(const char * action, const char * title, int set, void (*callback)(struct MenuEntry *)); extern struct MenuEntry * menu_create_submenu(const char * icon, const char * action, const char * title); extern struct MenuEntry * menu_create_separator(void); extern struct MenuList * menu_create(void); extern struct MenuSet * menu_set_from_description(const char * path, void (*callback)(struct MenuEntry *)); extern void menu_insert(struct MenuList * menu, struct MenuEntry * entry); extern void menu_prepare(struct MenuList * menu, yutani_t * yctx); extern void menu_show_at(struct MenuList * menu, yutani_window_t * parent, int x, int y); extern int menu_process_event(yutani_t * yctx, yutani_msg_t * m); extern struct MenuList * menu_set_get_root(struct MenuSet * menu); extern struct MenuList * menu_set_get_menu(struct MenuSet * menu, char * submenu); extern void menu_calculate_dimensions(struct MenuList * menu, int * height, int * width); extern void menu_free_entry(struct MenuEntry * ptr); extern void menu_free_menu(struct MenuList * ptr); extern void menu_free_set(struct MenuSet * ptr); extern hashmap_t * menu_get_windows_hash(void); extern int menu_definitely_close(struct MenuList * menu); extern struct MenuSet * menu_set_create(void); extern void menu_set_insert(struct MenuSet * set, char * action, struct MenuList * menu); extern void menu_update_title(struct MenuEntry * self, char * new_title); extern void menu_force_redraw(struct MenuList * menu); extern void menu_update_icon(struct MenuEntry * self, char * newIcon); extern void menu_update_toggle_state(struct MenuEntry * self, int state); extern void menu_update_enabled(struct MenuEntry * self, int state); #define MENU_FLAG_BUBBLE_CENTER (1 << 0) #define MENU_FLAG_BUBBLE_LEFT (1 << 1) #define MENU_FLAG_BUBBLE_RIGHT (1 << 2) #define MENU_FLAG_BUBBLE (MENU_FLAG_BUBBLE_LEFT | MENU_FLAG_BUBBLE_RIGHT | MENU_FLAG_BUBBLE_CENTER) #define MENU_FLAG_TAIL_POSITION (1 << 3) #define MENU_BAR_HEIGHT 24 struct menu_bar_entries { char * title; char * action; }; struct menu_bar { int x; int y; int width; struct menu_bar_entries * entries; struct MenuSet * set; struct menu_bar_entries * active_entry; struct MenuList * active_menu; int active_menu_wid; int active_entry_idx; yutani_window_t * window; int num_entries; void * _private; void (*redraw_callback)(struct menu_bar *); }; extern void menu_bar_render(struct menu_bar * self, gfx_context_t * ctx); extern 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); extern void menu_bar_show_menu(yutani_t * yctx, yutani_window_t * window, struct menu_bar * self, int offset, struct menu_bar_entries * _entries); _End_C_Header ================================================ FILE: base/usr/include/toaru/mouse.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header typedef enum { LEFT_CLICK = 0x01, RIGHT_CLICK = 0x02, MIDDLE_CLICK = 0x04, MOUSE_SCROLL_UP = 0x10, MOUSE_SCROLL_DOWN = 0x20, } mouse_click_t; typedef struct { uint32_t magic; int32_t x_difference; int32_t y_difference; mouse_click_t buttons; } mouse_device_packet_t; #define MOUSE_MAGIC 0xFEED1234 _End_C_Header ================================================ FILE: base/usr/include/toaru/panel.h ================================================ /** * @brief Panel extensions header * * Exposed API for the panel * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #pragma once #include <_cheader.h> #include #include #include _Begin_C_Header struct PanelContext { uint32_t color_text_normal; uint32_t color_text_hilighted; uint32_t color_text_focused; uint32_t color_icon_normal; uint32_t color_special; int font_size_default; yutani_window_t * basewindow; struct TT_Font * font; struct TT_Font * font_bold; struct TT_Font * font_mono; struct TT_Font * font_mono_bold; int extra_widget_spacing; }; struct PanelWidget { struct PanelContext * pctx; int highlighted; int left; int width; int fill; int (*click)(struct PanelWidget *, struct yutani_msg_window_mouse_event *); int (*right_click)(struct PanelWidget *, struct yutani_msg_window_mouse_event *); int (*leave)(struct PanelWidget *, struct yutani_msg_window_mouse_event *); int (*enter)(struct PanelWidget *, struct yutani_msg_window_mouse_event *); int (*move)(struct PanelWidget *, struct yutani_msg_window_mouse_event *); int (*draw)(struct PanelWidget *, gfx_context_t * ctx); int (*update)(struct PanelWidget *, int *force_updates); int (*onkey)(struct PanelWidget *, struct yutani_msg_key_event *); }; extern yutani_t * yctx; extern list_t * widgets_enabled; extern struct PanelWidget * widget_new(void); extern void launch_application_menu(struct MenuEntry * self); struct window_ad { yutani_wid_t wid; uint32_t flags; char * name; char * icon; char * strings; int left; uint32_t bufid; uint32_t width; uint32_t height; }; extern struct window_ad * ads_by_z[]; extern list_t * window_list; extern void redraw(void); extern int panel_menu_show(struct PanelWidget * this, struct MenuList * menu); extern int panel_menu_show_at(struct MenuList * menu, int x); extern void panel_highlight_widget(struct PanelWidget * this, gfx_context_t * ctx, int active); _End_C_Header ================================================ FILE: base/usr/include/toaru/pex.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header typedef struct pex_packet { uintptr_t source; size_t size; uint8_t data[]; } pex_packet_t; #define MAX_PACKET_SIZE 1024 #define PACKET_SIZE (sizeof(pex_packet_t) + MAX_PACKET_SIZE) typedef struct pex_header { uintptr_t target; uint8_t data[]; } pex_header_t; extern size_t pex_send(FILE * sock, uintptr_t rcpt, size_t size, char * blob); extern size_t pex_broadcast(FILE * sock, size_t size, char * blob); extern size_t pex_listen(FILE * sock, pex_packet_t * packet); extern size_t pex_reply(FILE * sock, size_t size, char * blob); extern size_t pex_recv(FILE * sock, char * blob); extern size_t pex_query(FILE * sock); extern FILE * pex_bind(char * target); extern FILE * pex_connect(char * target); _End_C_Header ================================================ FILE: base/usr/include/toaru/png.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern int load_sprite_png(sprite_t * sprite, char * filename); _End_C_Header ================================================ FILE: base/usr/include/toaru/rline.h ================================================ #pragma once struct rline_callback; typedef struct { char * buffer; struct rline_callback * callbacks; int collected; int requested; int newline; int cancel; int offset; int tabbed; int quiet; } rline_context_t; typedef void (*rline_callback_t)(rline_context_t * context); typedef struct rline_callback { rline_callback_t tab_complete; rline_callback_t redraw_prompt; rline_callback_t special_key; rline_callback_t key_up; rline_callback_t key_down; rline_callback_t key_left; rline_callback_t key_right; rline_callback_t rev_search; } rline_callbacks_t; typedef enum { /* Base colors */ RLINE_STYLE_MAIN, RLINE_STYLE_ALT, /* Syntax flags */ RLINE_STYLE_KEYWORD, RLINE_STYLE_STRING, RLINE_STYLE_COMMENT, RLINE_STYLE_TYPE, RLINE_STYLE_PRAGMA, RLINE_STYLE_NUMERAL, } rline_style_t; extern int rline(char * buffer, int buf_size); extern int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width); extern int rline_exp_set_shell_commands(char ** cmds, int len); extern int rline_exp_set_tab_complete_func(rline_callback_t func); extern int rline_exp_set_syntax(char * name); extern void rline_history_insert(char * str); extern void rline_history_append_line(char * str); extern char * rline_history_get(int item); extern char * rline_history_prev(int item); extern void rline_place_cursor(void); extern void rline_set_colors(rline_style_t style); extern void rline_redraw(rline_context_t * context); extern void rline_insert(rline_context_t * context, const char * what); extern int rline_terminal_width; #define RLINE_HISTORY_ENTRIES 128 extern char * rline_history[RLINE_HISTORY_ENTRIES]; extern int rline_history_count; extern int rline_history_offset; extern int rline_scroll; extern char * rline_exit_string; extern char * rline_preload; ================================================ FILE: base/usr/include/toaru/spinlock.h ================================================ #pragma once #ifndef spin_lock static void spin_lock(int volatile * lock) { while(__sync_lock_test_and_set(lock, 0x01)) { syscall_yield(); } } static void spin_unlock(int volatile * lock) { __sync_lock_release(lock); } #endif ================================================ FILE: base/usr/include/toaru/termemu.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header #define TERM_BUF_LEN 128 /* A terminal cell represents a single character on screen */ typedef struct { uint32_t c; /* codepoint */ uint32_t fg; /* background indexed color */ uint32_t bg; /* foreground indexed color */ uint32_t flags; /* other flags */ } term_cell_t; typedef struct { void (*writer)(char); void (*set_color)(uint32_t, uint32_t); void (*set_csr)(int,int); int (*get_csr_x)(void); int (*get_csr_y)(void); void (*set_cell)(int,int,uint32_t); void (*cls)(int); void (*scroll)(int); void (*redraw_cursor)(void); void (*input_buffer_stuff)(char *); void (*set_title)(char *); void (*set_cell_contents)(int,int,char *); int (*get_cell_width)(void); int (*get_cell_height)(void); void (*set_csr_on)(int); void (*switch_buffer)(int); void (*insert_delete_lines)(int); void (*full_reset)(void); } term_callbacks_t; typedef struct { uint16_t x; /* Current cursor location */ uint16_t y; /* " " " */ uint16_t save_x; /* Last cursor save */ uint16_t save_y; uint32_t width; /* Terminal width */ uint32_t height; /* " height */ uint32_t fg; /* Current foreground color */ uint32_t bg; /* Current background color */ uint8_t flags; /* Bright, etc. */ uint8_t escape; /* Escape status */ uint8_t box; uint8_t buflen; /* Buffer Length */ char buffer[TERM_BUF_LEN]; /* Previous buffer */ term_callbacks_t * callbacks; int volatile lock; uint8_t mouse_on; uint32_t img_collected; uint32_t img_size; char * img_data; uint8_t paste_mode; } term_state_t; /* Triggers escape mode. */ #define ANSI_ESCAPE 27 /* Escape verify */ #define ANSI_BRACKET '[' #define ANSI_BRACKET_RIGHT ']' #define ANSI_OPEN_PAREN '(' /* Anything in this range (should) exit escape mode. */ #define ANSI_LOW 'A' #define ANSI_HIGH 'z' /* Escape commands */ #define ANSI_CUU 'A' /* CUrsor Up */ #define ANSI_CUD 'B' /* CUrsor Down */ #define ANSI_CUF 'C' /* CUrsor Forward */ #define ANSI_CUB 'D' /* CUrsor Back */ #define ANSI_CNL 'E' /* Cursor Next Line */ #define ANSI_CPL 'F' /* Cursor Previous Line */ #define ANSI_CHA 'G' /* Cursor Horizontal Absolute */ #define ANSI_CUP 'H' /* CUrsor Position */ #define ANSI_ED 'J' /* Erase Data */ #define ANSI_EL 'K' /* Erase in Line */ #define ANSI_SU 'S' /* Scroll Up */ #define ANSI_SD 'T' /* Scroll Down */ #define ANSI_HVP 'f' /* Horizontal & Vertical Pos. */ #define ANSI_SGR 'm' /* Select Graphic Rendition */ #define ANSI_DSR 'n' /* Device Status Report */ #define ANSI_SCP 's' /* Save Cursor Position */ #define ANSI_RCP 'u' /* Restore Cursor Position */ #define ANSI_HIDE 'l' /* DECTCEM - Hide Cursor */ #define ANSI_SHOW 'h' /* DECTCEM - Show Cursor */ #define ANSI_IL 'L' /* Insert Line(s) */ #define ANSI_DL 'M' /* Delete Line(s) */ /* Display flags */ #define ANSI_BOLD 0x01 #define ANSI_UNDERLINE 0x02 #define ANSI_ITALIC 0x04 #define ANSI_ALTFONT 0x08 /* Character should use alternate font */ #define ANSI_SPECBG 0x10 #define ANSI_BORDER 0x20 #define ANSI_WIDE 0x40 /* Character is double width */ #define ANSI_CROSS 0x80 /* And that's all I'm going to support (for now) */ #define ANSI_EXT_IMG 0x100 /* Cell is actually an image, use fg color as pointer */ #define ANSI_EXT_IOCTL 'z' /* These are special escapes only we support */ /* Default color settings */ #define TERM_DEFAULT_FG 0x07 /* Index of default foreground */ #define TERM_DEFAULT_BG 0x10 /* Index of default background */ #define TERM_DEFAULT_FLAGS 0x00 /* Default flags for a cell */ #define TERM_DEFAULT_OPAC 0xF2 /* For background, default transparency */ #define TERMEMU_MOUSE_ENABLE 0x01 #define TERMEMU_MOUSE_DRAG 0x02 #define TERMEMU_MOUSE_SGR 0x04 /* TODO: _MOUSE_UTF8 0x08 */ /* TODO: _MOUSE_URXVT 0x10 */ extern term_state_t * ansi_init(term_state_t * s, int w, int y, term_callbacks_t * callbacks_in); extern void ansi_put(term_state_t * s, char c); _End_C_Header ================================================ FILE: base/usr/include/toaru/text.h ================================================ #pragma once /** * @file toaru/text.h * @brief TrueType glyph rasterizer. * * Exposed API for the TrueType renderer. */ #include /* Methods for loading fonts */ extern struct TT_Font * tt_font_from_file(const char * fileName); extern struct TT_Font * tt_font_from_shm(const char * identifier); /* Methods for changing font sizes */ extern void tt_set_size(struct TT_Font * font, float sizeInEm); extern void tt_set_size_px(struct TT_Font * font, float sizeInPx); /* Methods for dealing with glyphs */ extern void tt_draw_glyph(gfx_context_t * ctx, struct TT_Font * font, int x_offset, int y_offset, unsigned int glyph, uint32_t color); extern int tt_glyph_for_codepoint(struct TT_Font * font, unsigned int codepoint); extern int tt_xadvance_for_glyph(struct TT_Font * font, unsigned int ind); extern float tt_glyph_width(struct TT_Font * font, unsigned int glyph); extern sprite_t * tt_bake_glyph(struct TT_Font * font, unsigned int glyph, uint32_t color, int *_x, int *_y, float xadjust); /* Convenience functions for dealing with whole strings */ extern int tt_string_width(struct TT_Font * font, const char * s); extern int tt_string_width_int(struct TT_Font * font, const char * s); extern int tt_draw_string(gfx_context_t * ctx, struct TT_Font * font, int x, int y, const char * s, uint32_t color); extern 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); struct TT_FontMetrics { float ascender; float descender; float lineGap; }; extern int tt_measure_font(struct TT_Font * font, struct TT_FontMetrics * metrics); /* Vector rasterizer engine */ extern struct TT_Contour * tt_contour_start(float x, float y); extern struct TT_Shape * tt_contour_finish(const struct TT_Contour * in); extern struct TT_Contour * tt_contour_stroke_contour(const struct TT_Contour * in, float width); extern struct TT_Shape * tt_contour_stroke_shape(const struct TT_Contour * in, float width); extern struct TT_Contour * tt_contour_line_to(struct TT_Contour * shape, float x, float y); extern struct TT_Contour * tt_contour_move_to(struct TT_Contour * shape, float x, float y); extern void tt_path_paint(gfx_context_t * ctx, const struct TT_Shape * shape, uint32_t color); extern void tt_contour_transform(struct TT_Contour * cnt, gfx_matrix_t matrix); /* Internal methods to draw paths into vector contours */ extern struct TT_Contour * tt_draw_glyph_into(struct TT_Contour * contour, struct TT_Font * font, float x_offset, float y_offset, unsigned int glyph); extern struct TT_Contour * tt_prepare_string(struct TT_Font * font, float x, float y, const char * s, float * out_width); extern 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); /* Draw with texture from sprite */ extern void tt_path_paint_sprite(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix); extern 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); /* Truncate text with an ellipsis to fit a requested width. */ extern char * tt_ellipsify(const char * input, int font_size, struct TT_Font * font, int max_width, int * out_width); /* Path painting options */ #define TT_PATH_FILTER_BILINEAR 0 /* Bilinear filter. Good quality, slow. Default. */ #define TT_PATH_FILTER_NEAREST 1 /* Nearest neighbor. Low quality, fast. */ #define TT_PATH_WRAP_REPEAT 0 /* Repeat the sprite texture, simple modulo. Default. */ #define TT_PATH_WRAP_NONE 1 /* Do not repeat the sprite texture. Space outside of the texture is treated as empty. */ #define TT_PATH_WRAP_PAD 2 /* Extend the edges of the texture linearly. */ ================================================ FILE: base/usr/include/toaru/trace.h ================================================ #pragma once #include #include #include #ifndef TRACE #define TRACE(msg,...) do { \ struct timeval t; gettimeofday(&t, NULL); \ fprintf(stderr, "%06d.%06d [" TRACE_APP_NAME "] %s:%05d - " msg "\n", t.tv_sec, t.tv_usec, __FILE__, __LINE__, ##__VA_ARGS__); \ } while (0) #endif ================================================ FILE: base/usr/include/toaru/tree.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header typedef struct tree_node { void * value; list_t * children; struct tree_node * parent; } tree_node_t; typedef struct { size_t nodes; tree_node_t * root; } tree_t; typedef uint8_t (*tree_comparator_t) (void *, void *); extern tree_t * tree_create(void); extern void tree_set_root(tree_t * tree, void * value); extern void tree_node_destroy(tree_node_t * node); extern void tree_destroy(tree_t * tree); extern void tree_free(tree_t * tree); extern tree_node_t * tree_node_create(void * value); extern void tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node); extern tree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value); extern tree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle); extern void tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node); extern void tree_node_remove(tree_t * tree, tree_node_t * node); extern void tree_remove(tree_t * tree, tree_node_t * node); extern tree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator); extern void tree_break_off(tree_t * tree, tree_node_t * node); _End_C_Header ================================================ FILE: base/usr/include/toaru/yutani-internal.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header #define YUTANI_SHMKEY(server_ident,buf,sz,win) sprintf(buf, "sys.%s.%d", server_ident, win->bufid); #define YUTANI_SHMKEY_EXP(server_ident,buf,sz,bufid) sprintf(buf, "sys.%s.%d", server_ident, bufid); #define yutani_msg_buildx_hello_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; #define yutani_msg_buildx_subscribe_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE; #define yutani_msg_buildx_unsubscribe_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE; #define yutani_msg_buildx_query_windows_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE; #define yutani_msg_buildx_notify_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE; #define yutani_msg_buildx_session_end_alloc(out) char _yutani_tmp_ ## LINE [sizeof(struct yutani_message)]; yutani_msg_t * out = (void *)&_yutani_tmp_ ## LINE; #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; #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; #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; #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; #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; #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; #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; #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; #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; #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; extern void yutani_msg_buildx_hello(yutani_msg_t * msg); extern void yutani_msg_buildx_flip(yutani_msg_t * msg, yutani_wid_t wid); extern void yutani_msg_buildx_welcome(yutani_msg_t * msg, uint32_t width, uint32_t height); extern void yutani_msg_buildx_window_new(yutani_msg_t * msg, uint32_t width, uint32_t height); extern 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); extern void yutani_msg_buildx_window_init(yutani_msg_t * msg, yutani_wid_t wid, uint32_t width, uint32_t height, uint32_t bufid); extern void yutani_msg_buildx_window_close(yutani_msg_t * msg, yutani_wid_t wid); extern void yutani_msg_buildx_key_event(yutani_msg_t * msg, yutani_wid_t wid, key_event_t * event, key_event_state_t * state); extern void yutani_msg_buildx_mouse_event(yutani_msg_t * msg, yutani_wid_t wid, mouse_device_packet_t * event, int32_t type); extern void yutani_msg_buildx_window_move(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y); extern 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); extern void yutani_msg_buildx_window_set_parent(yutani_msg_t * msg, yutani_wid_t wid, yutani_wid_t wid2); extern void yutani_msg_buildx_window_stack(yutani_msg_t * msg, yutani_wid_t wid, int z); extern void yutani_msg_buildx_window_focus_change(yutani_msg_t * msg, yutani_wid_t wid, int focused); extern 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); extern 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); extern 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); extern 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); extern void yutani_msg_buildx_subscribe(yutani_msg_t * msg); extern void yutani_msg_buildx_unsubscribe(yutani_msg_t * msg); extern void yutani_msg_buildx_query_windows(yutani_msg_t * msg); extern void yutani_msg_buildx_notify(yutani_msg_t * msg); extern void yutani_msg_buildx_session_end(yutani_msg_t * msg); extern void yutani_msg_buildx_window_focus(yutani_msg_t * msg, yutani_wid_t wid); extern void yutani_msg_buildx_key_bind(yutani_msg_t * msg, kbd_key_t key, kbd_mod_t mod, int response); extern void yutani_msg_buildx_window_drag_start(yutani_msg_t * msg, yutani_wid_t wid); extern void yutani_msg_buildx_window_update_shape(yutani_msg_t * msg, yutani_wid_t wid, int set_shape); extern void yutani_msg_buildx_window_warp_mouse(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y); extern void yutani_msg_buildx_window_show_mouse(yutani_msg_t * msg, yutani_wid_t wid, int32_t show_mouse); extern void yutani_msg_buildx_window_resize_start(yutani_msg_t * msg, yutani_wid_t wid, yutani_scale_direction_t direction); extern void yutani_msg_buildx_special_request(yutani_msg_t * msg, yutani_wid_t wid, uint32_t request); extern void yutani_msg_buildx_clipboard(yutani_msg_t * msg, char * content); extern 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); _End_C_Header ================================================ FILE: base/usr/include/toaru/yutani-server.h ================================================ /** * @brief Internal definitions used by the Yutani compositor. * * @copyright * 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-2021 K. Lange */ #pragma once #include <_cheader.h> #include #include #include _Begin_C_Header /* Mouse resolution scaling */ #define MOUSE_SCALE 3 #define YUTANI_INCOMING_MOUSE_SCALE * 3 /* Mouse cursor hotspot */ #define MOUSE_OFFSET_X 26 #define MOUSE_OFFSET_Y 26 /* Mouse cursor size */ #define MOUSE_WIDTH 64 #define MOUSE_HEIGHT 64 /* How much the mouse needs to move to break off a tiled window */ #define UNTILE_SENSITIVITY (MOUSE_SCALE * 5) /* Screenshot modes */ #define YUTANI_SCREENSHOT_FULL 1 #define YUTANI_SCREENSHOT_WINDOW 2 /* * Animation effect types. * XXX: Should this be in the client library? */ typedef enum { YUTANI_EFFECT_NONE, /* Basic animations */ YUTANI_EFFECT_FADE_IN, YUTANI_EFFECT_FADE_OUT, /* XXX: Are these used? */ YUTANI_EFFECT_MINIMIZE, YUTANI_EFFECT_UNMINIMIZE, /* Dialog animations, faster than the fades */ YUTANI_EFFECT_SQUEEZE_IN, YUTANI_EFFECT_SQUEEZE_OUT, YUTANI_EFFECT_DISAPPEAR, } yutani_effect; /* Animation lengths */ static int yutani_animation_lengths[] = { 0, /* None */ 200, /* Fade In */ 200, /* Fade Out */ 200, /* Minimize */ 200, /* Unminimized */ 100, /* Squeeze in */ 100, /* Squeeze out */ 10, /* Disappear */ }; static int yutani_is_closing_animation[] = { 0, 0, 1, 0, 0, 0, 1, 1, }; static int yutani_is_minimizing_animation[] = { 0, 0, 0, 1, 0, 0, 0, 0 }; /* Debug Options */ #define YUTANI_DEBUG_WINDOW_BOUNDS 1 #define YUTANI_DEBUG_WINDOW_SHAPES 1 /* Command line flag values */ struct { int nested; int nest_width; int nest_height; } yutani_options = { .nested = 0, .nest_width = 640, .nest_height = 480, }; /* * Server window definitions */ typedef struct YutaniServerWindow { /* Window identifier number */ yutani_wid_t wid; /* Window location */ signed long x; signed long y; /* Stack order */ unsigned short z; /* Window size */ int32_t width; int32_t height; /* Canvas buffer */ uint8_t * buffer; uint32_t bufid; uint32_t newbufid; uint8_t * newbuffer; /* Connection that owns this window */ uintptr_t owner; /* Rotation of windows XXX */ int16_t rotation; /* Client advertisements */ uint32_t client_flags; uint32_t client_icon; uint32_t client_length; char * client_strings; /* Window animations */ uint64_t anim_mode; uint64_t anim_start; /* Alpha shaping threshold */ int alpha_threshold; /* * Mouse cursor selection * Originally, this specified whether the mouse was * hidden, but it plays double duty since client * control over mouse cursors was added. */ int show_mouse; int default_mouse; /* Tiling / untiling information */ int tiled; int32_t untiled_width; int32_t untiled_height; int32_t untiled_left; int32_t untiled_top; /* Client-configurable server behavior flags */ uint32_t server_flags; /* Window opacity */ int opacity; /* Window is hidden? */ int hidden; int minimized; int32_t icon_x, icon_y, icon_w, icon_h; yutani_wid_t parent; } yutani_server_window_t; typedef struct YutaniGlobals { /* Display resolution */ unsigned int width; unsigned int height; uint32_t stride; /* TODO: What about multiple screens? * * Obviously this is the whole canvas size, * but we need to be able to track different * monitors if/when we ever get support for that. */ /* Core graphics context */ void * backend_framebuffer; gfx_context_t * backend_ctx; /* Mouse location */ signed int mouse_x; signed int mouse_y; /* * Previous mouse location, so that events can have * both the new and old mouse location together */ signed int last_mouse_x; signed int last_mouse_y; /* List of all windows */ list_t * windows; /* Hash of window IDs to their objects */ hashmap_t * wids_to_windows; /* * Window stacking information * TODO: Support multiple top and bottom windows. */ yutani_server_window_t * bottom_z; list_t * mid_zs; list_t * menu_zs; list_t * overlay_zs; yutani_server_window_t * top_z; /* Damage region list */ list_t * update_list; /* Mouse cursors */ sprite_t mouse_sprite; sprite_t mouse_sprite_drag; sprite_t mouse_sprite_resize_v; sprite_t mouse_sprite_resize_h; sprite_t mouse_sprite_resize_da; sprite_t mouse_sprite_resize_db; sprite_t mouse_sprite_point; sprite_t mouse_sprite_ibeam; int current_cursor; /* Server backend communication identifier */ char * server_ident; FILE * server; /* Pointer to focused window */ yutani_server_window_t * focused_window; /* Mouse movement state */ int mouse_state; /* Pointer to window being manipulated by mouse actions */ yutani_server_window_t * mouse_window; /* Buffered information on mouse-moved window */ int mouse_win_x; int mouse_win_y; int mouse_init_x; int mouse_init_y; int mouse_init_r; int32_t mouse_click_x_orig; int32_t mouse_click_y_orig; int mouse_drag_button; int mouse_moved; int32_t mouse_click_x; int32_t mouse_click_y; /* Pointer to window being resized */ yutani_server_window_t * resizing_window; int32_t resizing_w; int32_t resizing_h; yutani_scale_direction_t resizing_direction; int32_t resizing_offset_x; int32_t resizing_offset_y; int resizing_button; /* List of clients subscribing to window information events */ list_t * window_subscribers; /* When the server started, used for timing functions */ time_t start_time; suseconds_t start_subtime; /* Pointer to last hovered window to allow exit events */ yutani_server_window_t * old_hover_window; /* Key bindings */ hashmap_t * key_binds; /* Windows to remove after the end of the rendering pass */ list_t * windows_to_remove; /* For nested mode, the host Yutani context and window */ yutani_t * host_context; yutani_window_t * host_window; /* Map of clients to their windows */ hashmap_t * clients_to_windows; /* Toggles for debugging window locations */ int debug_bounds; int debug_shapes; /* If the next rendered frame should be saved as a screenshot */ int screenshot_frame; /* Next frame should resize host context */ int resize_on_next; /* Last mouse buttons - used for some specialized mouse drivers */ uint32_t last_mouse_buttons; /* Clipboard buffer */ char clipboard[512]; int clipboard_size; /* VirtualBox Seamless mode support information */ int vbox_rects; int vbox_pointer; /* Renderer plugin context */ void * renderer_ctx; int reload_renderer; uint8_t active_modifiers; uint64_t resize_release_time; int32_t resizing_init_w; int32_t resizing_init_h; list_t * windows_to_minimize; list_t * minimized_zs; } yutani_globals_t; struct key_bind { uintptr_t owner; int response; }; /* Exported functions for plugins */ extern int yutani_window_is_top(yutani_globals_t * yg, yutani_server_window_t * window); extern int yutani_window_is_bottom(yutani_globals_t * yg, yutani_server_window_t * window); extern uint64_t yutani_time_since(yutani_globals_t * yg, uint64_t start_time); extern void yutani_window_to_device(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y); extern void yutani_device_to_window(yutani_server_window_t * window, int32_t x, int32_t y, int32_t * out_x, int32_t * out_y); extern uint32_t yutani_color_for_wid(yutani_wid_t wid); _End_C_Header ================================================ FILE: base/usr/include/toaru/yutani.h ================================================ /** * @brief Yutani Client Library * * Client library for the compositing window system. * * @copyright * 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-2021 K. Lange */ #pragma once #include <_cheader.h> #include #include #include #include #include #include #include #include _Begin_C_Header typedef unsigned int yutani_wid_t; /* * Server connection context. */ typedef struct yutani_context { FILE * sock; /* server display size */ size_t display_width; size_t display_height; /* Hash of window IDs to window objects */ hashmap_t * windows; /* queued events */ list_t * queued; /* server identifier string */ char * server_ident; } yutani_t; typedef struct yutani_window { /* Server window identifier, unique to each window */ yutani_wid_t wid; /* Window size */ uint32_t width; uint32_t height; /* Window backing buffer */ char * buffer; /* * Because the buffer can change during resizing, * buffers are indexed to ensure we are using * the one the server expects. */ uint32_t bufid; /* Window focused flag */ uint8_t focused; /* Old buffer ID */ uint32_t oldbufid; /* Generic pointer for client use */ void * user_data; /* Window position in the server; automatically updated */ int32_t x; int32_t y; /* Flags for the decorator library to use */ uint32_t decorator_flags; /* Server context that owns this window */ yutani_t * ctx; int32_t mouse_state; } yutani_window_t; typedef struct yutani_message { uint32_t magic; uint32_t type; uint32_t size; char data[]; } yutani_msg_t; struct yutani_msg_welcome { uint32_t display_width; uint32_t display_height; }; struct yutani_msg_flip { yutani_wid_t wid; }; struct yutani_msg_window_close { yutani_wid_t wid; }; struct yutani_msg_window_new { uint32_t width; uint32_t height; }; struct yutani_msg_window_new_flags { uint32_t width; uint32_t height; uint32_t flags; yutani_wid_t parent_wid; }; struct yutani_msg_window_init { yutani_wid_t wid; uint32_t width; uint32_t height; uint32_t bufid; }; struct yutani_msg_window_move { yutani_wid_t wid; int32_t x; int32_t y; }; struct yutani_msg_window_move_relative { yutani_wid_t wid_to_move; yutani_wid_t wid_base; int32_t x; int32_t y; }; struct yutani_msg_key_event { yutani_wid_t wid; key_event_t event; key_event_state_t state; }; struct yutani_msg_window_stack { yutani_wid_t wid; int z; }; struct yutani_msg_window_focus_change { yutani_wid_t wid; int focused; }; struct yutani_msg_window_mouse_event { 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; }; struct yutani_msg_mouse_event { yutani_wid_t wid; mouse_device_packet_t event; int32_t type; }; struct yutani_msg_flip_region { yutani_wid_t wid; int32_t x; int32_t y; int32_t width; int32_t height; }; struct yutani_msg_window_resize { yutani_wid_t wid; uint32_t width; uint32_t height; uint32_t bufid; uint32_t flags; }; struct yutani_msg_window_advertise { yutani_wid_t wid; uint32_t flags; /* Types, focused, etc. */ uint32_t icon; /* Icon offset in strings[] */ uint32_t bufid; uint32_t width; uint32_t height; uint32_t size; char strings[]; }; struct yutani_msg_window_focus { yutani_wid_t wid; }; struct yutani_msg_key_bind { kbd_key_t key; kbd_mod_t modifiers; int response; }; struct yutani_msg_window_drag_start { yutani_wid_t wid; }; struct yutani_msg_window_update_shape { yutani_wid_t wid; int set_shape; }; struct yutani_msg_window_warp_mouse { yutani_wid_t wid; int32_t x; int32_t y; }; struct yutani_msg_window_show_mouse { yutani_wid_t wid; int32_t show_mouse; }; typedef enum { SCALE_AUTO, SCALE_UP, SCALE_DOWN, SCALE_LEFT, SCALE_RIGHT, SCALE_UP_LEFT, SCALE_UP_RIGHT, SCALE_DOWN_LEFT, SCALE_DOWN_RIGHT, SCALE_NONE, } yutani_scale_direction_t; struct yutani_msg_window_resize_start { yutani_wid_t wid; yutani_scale_direction_t direction; }; struct yutani_msg_special_request { yutani_wid_t wid; uint32_t request; }; struct yutani_msg_clipboard { uint32_t size; char content[]; }; struct yutani_msg_window_panel_size { yutani_wid_t wid; int32_t x; int32_t y; int32_t w; int32_t h; }; struct yutani_msg_window_set_parent { yutani_wid_t wid; yutani_wid_t parent_wid; }; /* Magic value */ #define YUTANI_MSG__MAGIC 0xABAD1DEA /* Client messages */ #define YUTANI_MSG_HELLO 0x00000001 #define YUTANI_MSG_WINDOW_NEW 0x00000002 #define YUTANI_MSG_FLIP 0x00000003 #define YUTANI_MSG_KEY_EVENT 0x00000004 #define YUTANI_MSG_MOUSE_EVENT 0x00000005 #define YUTANI_MSG_WINDOW_MOVE 0x00000006 #define YUTANI_MSG_WINDOW_CLOSE 0x00000007 #define YUTANI_MSG_WINDOW_SHOW 0x00000008 #define YUTANI_MSG_WINDOW_HIDE 0x00000009 #define YUTANI_MSG_WINDOW_STACK 0x0000000A #define YUTANI_MSG_WINDOW_FOCUS_CHANGE 0x0000000B #define YUTANI_MSG_WINDOW_MOUSE_EVENT 0x0000000C #define YUTANI_MSG_FLIP_REGION 0x0000000D #define YUTANI_MSG_WINDOW_NEW_FLAGS 0x0000000E #define YUTANI_MSG_RESIZE_REQUEST 0x00000010 #define YUTANI_MSG_RESIZE_OFFER 0x00000011 #define YUTANI_MSG_RESIZE_ACCEPT 0x00000012 #define YUTANI_MSG_RESIZE_BUFID 0x00000013 #define YUTANI_MSG_RESIZE_DONE 0x00000014 #define YUTANI_MSG_WINDOW_MOVE_RELATIVE 0x00000015 #define YUTANI_MSG_WINDOW_SET_PARENT 0x00000016 /* Some session management / de stuff */ #define YUTANI_MSG_WINDOW_ADVERTISE 0x00000020 #define YUTANI_MSG_SUBSCRIBE 0x00000021 #define YUTANI_MSG_UNSUBSCRIBE 0x00000022 #define YUTANI_MSG_NOTIFY 0x00000023 #define YUTANI_MSG_QUERY_WINDOWS 0x00000024 #define YUTANI_MSG_WINDOW_FOCUS 0x00000025 #define YUTANI_MSG_WINDOW_DRAG_START 0x00000026 #define YUTANI_MSG_WINDOW_WARP_MOUSE 0x00000027 #define YUTANI_MSG_WINDOW_SHOW_MOUSE 0x00000028 #define YUTANI_MSG_WINDOW_RESIZE_START 0x00000029 #define YUTANI_MSG_WINDOW_PANEL_SIZE 0x0000002a #define YUTANI_MSG_SESSION_END 0x00000030 #define YUTANI_MSG_KEY_BIND 0x00000040 #define YUTANI_MSG_WINDOW_UPDATE_SHAPE 0x00000050 #define YUTANI_MSG_CLIPBOARD 0x00000060 #define YUTANI_MSG_GOODBYE 0x000000F0 /* Special request (eg. one-off single-shot requests like "please maximize me" */ #define YUTANI_MSG_SPECIAL_REQUEST 0x00000100 /* Server responses */ #define YUTANI_MSG_WELCOME 0x00010001 #define YUTANI_MSG_WINDOW_INIT 0x00010002 /* * YUTANI_ZORDER * * Specifies which stack set a window should appear in. */ #define YUTANI_ZORDER_MAX 0xFFFF #define YUTANI_ZORDER_TOP 0xFFFF #define YUTANI_ZORDER_MENU 0xFFFE #define YUTANI_ZORDER_OVERLAY 0xFFED #define YUTANI_ZORDER_BOTTOM 0x0000 /* * YUTANI_MOUSE_BUTTON * * Button specifiers. Multiple specifiers may be set. */ #define YUTANI_MOUSE_BUTTON_LEFT 0x01 #define YUTANI_MOUSE_BUTTON_RIGHT 0x02 #define YUTANI_MOUSE_BUTTON_MIDDLE 0x04 #define YUTANI_MOUSE_SCROLL_UP 0x10 #define YUTANI_MOUSE_SCROLL_DOWN 0x20 /* * YUTANI_MOUSE_STATE * * The mouse has for effective states internally: * * NORMAL: The mouse is performing normally. * MOVING: The mouse is engaged in moving a window. * DRAGGING: The mouse is down and sending drag events. * RESIZING: The mouse is engaged in resizing a window. */ #define YUTANI_MOUSE_STATE_NORMAL 0 #define YUTANI_MOUSE_STATE_MOVING 1 #define YUTANI_MOUSE_STATE_DRAGGING 2 #define YUTANI_MOUSE_STATE_RESIZING 3 #define YUTANI_MOUSE_STATE_ROTATING 4 /* * YUTANI_MOUSE_EVENT * * Mouse events have different types. * * Most of these should be self-explanatory. * * CLICK: A down-up click has occured. * DRAG: The mouse is down and moving. * RAISE: A mouse button was released. * DOWN: A mouse button has been pressed. * MOVE: The mouse has moved without a mouse button pressed. * LEAVE: The mouse has left the given window. * ENTER: The mouse has entered the given window. */ #define YUTANI_MOUSE_EVENT_CLICK 0 #define YUTANI_MOUSE_EVENT_DRAG 1 #define YUTANI_MOUSE_EVENT_RAISE 2 #define YUTANI_MOUSE_EVENT_DOWN 3 #define YUTANI_MOUSE_EVENT_MOVE 4 #define YUTANI_MOUSE_EVENT_LEAVE 5 #define YUTANI_MOUSE_EVENT_ENTER 6 /* * YUTANI_MOUSE_EVENT_TYPE * * (For mouse drivers) * * RELATIVE: Mouse positions are relative to the previous reported location. * ABSOLUTE: Mouse positions are in absolute coordinates. */ #define YUTANI_MOUSE_EVENT_TYPE_RELATIVE 0 #define YUTANI_MOUSE_EVENT_TYPE_ABSOLUTE 1 /* * YUTANI_KEY_MODIFIER * * These are sent with mouse events. The LEFT and RIGHT * version are specific to those keys. The non-LEFT/RIGHT * versions are masks that can match either key. * * Must match with the definitions. */ #define YUTANI_KEY_MODIFIER_LEFT_CTRL 0x01 #define YUTANI_KEY_MODIFIER_LEFT_SHIFT 0x02 #define YUTANI_KEY_MODIFIER_LEFT_ALT 0x04 #define YUTANI_KEY_MODIFIER_LEFT_SUPER 0x08 #define YUTANI_KEY_MODIFIER_RIGHT_CTRL 0x10 #define YUTANI_KEY_MODIFIER_RIGHT_SHIFT 0x20 #define YUTANI_KEY_MODIFIER_RIGHT_ALT 0x40 #define YUTANI_KEY_MODIFIER_RIGHT_SUPER 0x80 #define YUTANI_KEY_MODIFIER_CTRL 0x11 #define YUTANI_KEY_MODIFIER_SHIFT 0x22 #define YUTANI_KEY_MODIFIER_ALT 0x44 #define YUTANI_KEY_MODIFIER_SUPER 0x88 /* * YUTANI_BIND * * Used to control keyboard binding modes. * * PASSTHROUGH: The key event will continue to the window that would have normally received. * STEAL: The key event will not be passed to the next window and is stolen by the bound window. */ #define YUTANI_BIND_PASSTHROUGH 0 #define YUTANI_BIND_STEAL 1 /* * YUTANI_SHAPE_THRESHOLD * * Used with yutani_window_update_shape to set the alpha threshold for window shaping. * All windows are shaped based on their transparency (alpha channel). The default * mode is NONE - meaning the alpha channel is ignored. * * NONE: The window is always clickable, regardless of alpha transparency. * CLEAR: Only completely clear (alpha = 0) regions will pass through. * HALF: Threshold of 50% - alpha values below 127 will pass through. Good for most cases. * ANY: Any amount of alpha transparency will pass through - only fully opaque regions are kept. * PASSTHROUGH: All clicks pass through. Useful for tooltips / overlays. */ #define YUTANI_SHAPE_THRESHOLD_NONE 0 #define YUTANI_SHAPE_THRESHOLD_CLEAR 1 #define YUTANI_SHAPE_THRESHOLD_HALF 127 #define YUTANI_SHAPE_THRESHOLD_ANY 255 #define YUTANI_SHAPE_THRESHOLD_PASSTHROUGH 256 /* * YUTANI_CURSOR_TYPE * * Used with SHOW_MOUSE to set the cursor type for this window. * Note that modifications made to the cursor will only display * while it the current window is active and that cursor settings * are per-window, not per-application. * * HIDE: Disable the mouse cursor. Useful for games. * NORMAL: The normal arrow cursor. * DRAG: A 4-directional arrow. * RESIZE_VERTICAL: An up-down arrow / resize indicator. * RESIZE_HORIZONTAL: A left-right arrow / resize indicator. * RESIZE_UP_DOWN: A diagonal \-shaped arrow. * RESIZE_DOWN_UP: A diagonal /-shaped arrow. * * RESET: If the cursor was previously hidden, hide it again. * Otherwise, show the normal cursor. Allows for decorator * to set resize cursors without having to know if a window * had set the default mode to HIDE. */ #define YUTANI_CURSOR_TYPE_RESET -1 #define YUTANI_CURSOR_TYPE_HIDE 0 #define YUTANI_CURSOR_TYPE_NORMAL 1 #define YUTANI_CURSOR_TYPE_DRAG 2 #define YUTANI_CURSOR_TYPE_RESIZE_VERTICAL 3 #define YUTANI_CURSOR_TYPE_RESIZE_HORIZONTAL 4 #define YUTANI_CURSOR_TYPE_RESIZE_UP_DOWN 5 #define YUTANI_CURSOR_TYPE_RESIZE_DOWN_UP 6 #define YUTANI_CURSOR_TYPE_POINT 7 #define YUTANI_CURSOR_TYPE_IBEAM 8 /* * YUTANI_WINDOW_FLAG * * Flags for new windows describing how the window * should be created. */ #define YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS (1 << 0) #define YUTANI_WINDOW_FLAG_DISALLOW_DRAG (1 << 1) #define YUTANI_WINDOW_FLAG_DISALLOW_RESIZE (1 << 2) #define YUTANI_WINDOW_FLAG_ALT_ANIMATION (1 << 3) #define YUTANI_WINDOW_FLAG_DIALOG_ANIMATION (1 << 4) #define YUTANI_WINDOW_FLAG_NO_ANIMATION (1 << 5) #define YUTANI_WINDOW_FLAG_BLUR_BEHIND (1 << 8) #define YUTANI_WINDOW_FLAG_PARENT_WID (1 << 9) /* YUTANI_SPECIAL_REQUEST * * Special one-off single-shot request messages. */ #define YUTANI_SPECIAL_REQUEST_MAXIMIZE 1 #define YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE 2 #define YUTANI_SPECIAL_REQUEST_MINIMIZE 3 #define YUTANI_SPECIAL_REQUEST_CLIPBOARD 10 #define YUTANI_SPECIAL_REQUEST_RELOAD 20 /* * YUTANI_RESIZE * * Flags provided in resize offers describing the window state. */ #define YUTANI_RESIZE_NORMAL 0x00000000 #define YUTANI_RESIZE_TILED 0x0000000f #define YUTANI_RESIZE_TILE_LEFT 0x00000001 #define YUTANI_RESIZE_TILE_RIGHT 0x00000002 #define YUTANI_RESIZE_TILE_UP 0x00000004 #define YUTANI_RESIZE_TILE_DOWN 0x00000008 typedef struct { int x; int y; unsigned int width; unsigned int height; } yutani_damage_rect_t; extern yutani_msg_t * yutani_wait_for(yutani_t * y, uint32_t type); extern yutani_msg_t * yutani_poll(yutani_t * y); extern yutani_msg_t * yutani_poll_async(yutani_t * y); extern size_t yutani_query(yutani_t * y); extern int yutani_msg_send(yutani_t * y, yutani_msg_t * msg); extern yutani_t * yutani_context_create(FILE * socket); extern yutani_t * yutani_init(void); extern yutani_window_t * yutani_window_create(yutani_t * y, int width, int height); extern yutani_window_t * yutani_window_create_flags(yutani_t * y, int width, int height, uint32_t flags, ...); extern void yutani_flip(yutani_t * y, yutani_window_t * win); extern void yutani_window_move(yutani_t * yctx, yutani_window_t * window, int x, int y); extern void yutani_window_move_relative(yutani_t * yctx, yutani_window_t * window, yutani_window_t * base, int x, int y); extern void yutani_window_set_parent(yutani_t * yctx, yutani_window_t * window, yutani_window_t * parent); extern void yutani_close(yutani_t * y, yutani_window_t * win); extern void yutani_set_stack(yutani_t *, yutani_window_t *, int); extern void yutani_flip_region(yutani_t *, yutani_window_t * win, int32_t x, int32_t y, int32_t width, int32_t height); extern void yutani_window_resize(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height); extern void yutani_window_resize_offer(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height); extern void yutani_window_resize_accept(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height); extern void yutani_window_resize_done(yutani_t * yctx, yutani_window_t * window); extern void yutani_window_advertise(yutani_t * yctx, yutani_window_t * window, char * name); extern void yutani_window_advertise_icon(yutani_t * yctx, yutani_window_t * window, char * name, char * icon); extern void yutani_window_panel_size(yutani_t * yctx, yutani_wid_t wid, int32_t x, int32_t y, int32_t w, int32_t h); extern void yutani_subscribe_windows(yutani_t * y); extern void yutani_unsubscribe_windows(yutani_t * y); extern void yutani_query_windows(yutani_t * y); extern void yutani_session_end(yutani_t * y); extern void yutani_focus_window(yutani_t * y, yutani_wid_t wid); extern void yutani_key_bind(yutani_t * yctx, kbd_key_t key, kbd_mod_t mod, int response); extern void yutani_window_drag_start(yutani_t * yctx, yutani_window_t * window); extern void yutani_window_drag_start_wid(yutani_t * yctx, yutani_wid_t wid); extern void yutani_window_update_shape(yutani_t * yctx, yutani_window_t * window, int set_shape); extern void yutani_window_warp_mouse(yutani_t * yctx, yutani_window_t * window, int32_t x, int32_t y); extern void yutani_window_show_mouse(yutani_t * yctx, yutani_window_t * window, int32_t show_mouse); extern void yutani_window_resize_start(yutani_t * yctx, yutani_window_t * window, yutani_scale_direction_t direction); extern void yutani_special_request(yutani_t * yctx, yutani_window_t * window, uint32_t request); extern void yutani_special_request_wid(yutani_t * yctx, yutani_wid_t wid, uint32_t request); extern void yutani_set_clipboard(yutani_t * yctx, char * content); extern FILE * yutani_open_clipboard(yutani_t * yctx); extern gfx_context_t * init_graphics_yutani(yutani_window_t * window); extern gfx_context_t * init_graphics_yutani_double_buffer(yutani_window_t * window); extern void reinit_graphics_yutani(gfx_context_t * out, yutani_window_t * window); extern void release_graphics_yutani(gfx_context_t * gfx); extern void yutani_internal_refocus(yutani_t * yctx, yutani_window_t * window); _End_C_Header ================================================ FILE: base/usr/include/unistd.h ================================================ #pragma once #include <_cheader.h> #include #include _Begin_C_Header #define _POSIX_VERSION 200809L extern char **environ; extern pid_t getpid(void); extern pid_t getppid(void); extern int close(int fd); extern pid_t fork(void); extern int execl(const char *path, const char *arg, ...); extern int execlp(const char *file, const char *arg, ...); extern int execle(const char *path, const char *arg, ...); extern int execv(const char *path, char *const argv[]); extern int execvp(const char *file, char *const argv[]); extern int execvpe(const char *file, char *const argv[], char *const envp[]); extern int execve(const char *name, char * const argv[], char * const envp[]); extern void _exit(int status); extern int setuid(uid_t uid); extern int setgid(gid_t gid); extern uid_t getuid(void); extern uid_t geteuid(void); extern gid_t getgid(void); extern gid_t getegid(void); extern char * getcwd(char *buf, size_t size); extern int pipe(int pipefd[2]); extern int dup(int oldfd); extern int dup2(int oldfd, int newfd); extern pid_t tcgetpgrp(int fd); extern int tcsetpgrp(int fd, pid_t pgrp); extern ssize_t write(int fd, const void * buf, size_t count); extern ssize_t read(int fd, void * buf, size_t count); extern int symlink(const char *target, const char *linkpath); extern ssize_t readlink(const char *pathname, char *buf, size_t bufsiz); extern int chdir(const char *path); //extern int fchdir(int fd); extern int isatty(int fd); extern unsigned int sleep(unsigned int seconds); extern int usleep(useconds_t usec); extern off_t lseek(int fd, off_t offset, int whence); extern int access(const char * pathname, int mode); extern int getopt(int argc, char * const argv[], const char * optstring); extern char * optarg; extern int optind, opterr, optopt; extern int unlink(const char * pathname); /* Unimplemented stubs */ extern int rmdir(const char *pathname); /* TODO rm probably just works */ extern int chown(const char * pathname, uid_t owner, gid_t group); extern int fchown(int fd, uid_t owner, gid_t group); extern char * getlogin(void); extern char * ttyname(int fd); extern int ttyname_r(int fd, char * buf, size_t buflen); #define STDIN_FILENO 0 #define STDOUT_FILENO 1 #define STDERR_FILENO 2 #define SEEK_SET 0 #define SEEK_CUR 1 #define SEEK_END 2 #define F_OK 0 #define R_OK 4 #define W_OK 2 #define X_OK 1 extern int gethostname(char * name, size_t len); extern int sethostname(const char * name, size_t len); extern pid_t setsid(void); extern int setpgid(pid_t, pid_t); extern pid_t getpgid(pid_t); extern pid_t getpgrp(void); extern unsigned int alarm(unsigned int seconds); #ifndef intptr_t # if defined(__PTRDIFF_TYPE__) typedef signed __PTRDIFF_TYPE__ intptr_t; # else typedef signed long intptr_t; # endif #endif extern void *sbrk(intptr_t increment); extern void sync(void); extern int truncate(const char *, off_t); extern int ftruncate(int, off_t); #define _PC_PATH_MAX 1 extern long pathconf(const char *path, int name); extern int getgroups(int size, gid_t list[]); ssize_t pread(int fd, void *buf, size_t count, off_t offset); ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset); _End_C_Header ================================================ FILE: base/usr/include/utime.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header struct utimbuf { time_t actime; time_t modtime; }; extern int utime(const char *filename, const struct utimbuf *times); _End_C_Header ================================================ FILE: base/usr/include/va_list.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header typedef __builtin_va_list va_list; #define va_start(ap,last) __builtin_va_start(ap, last) #define va_end(ap) __builtin_va_end(ap) #define va_arg(ap,type) __builtin_va_arg(ap,type) #define va_copy(dest, src) __builtin_va_copy(dest,src) _End_C_Header ================================================ FILE: base/usr/include/wait.h ================================================ #pragma once #include <_cheader.h> _Begin_C_Header int waitpid(int pid, int *status, int options); int wait(int *status); _End_C_Header ================================================ FILE: base/usr/include/wchar.h ================================================ #pragma once #include <_cheader.h> #include _Begin_C_Header extern int wcwidth(wchar_t c); extern wchar_t * wcsncpy(wchar_t * dest, const wchar_t * src, size_t n); extern size_t wcslen(const wchar_t * s); extern int wcscmp(const wchar_t *s1, const wchar_t *s2); extern wchar_t * wcscat(wchar_t *dest, const wchar_t *src); extern wchar_t * wcstok(wchar_t * str, const wchar_t * delim, wchar_t ** saveptr); extern size_t wcsspn(const wchar_t * wcs, const wchar_t * accept); extern wchar_t *wcspbrk(const wchar_t *wcs, const wchar_t *accept); extern wchar_t * wcschr(const wchar_t *wcs, wchar_t wc); extern wchar_t * wcsrchr(const wchar_t *wcs, wchar_t wc); extern wchar_t * wcsncat(wchar_t *dest, const wchar_t * src, size_t n); extern int wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n); extern wchar_t * wcscpy(wchar_t * dest, const wchar_t * src); extern unsigned long int wcstoul(const wchar_t *nptr, wchar_t **endptr, int base); extern unsigned long long int wcstoull(const char *nptr, wchar_t **endptr, int base); extern long int wcstol(const wchar_t *nptr, wchar_t **endptr, int base); extern long long int wcstoll(const wchar_t *nptr, wchar_t **endptr, int base); typedef unsigned int wint_t; _End_C_Header ================================================ FILE: base/usr/share/bim/site/__init__.krk ================================================ import kuroko kuroko.module_paths.append('/lib/kuroko/') ================================================ FILE: base/usr/share/bim/syntax/__init__.krk ================================================ from bim import SyntaxState, bindHighlighter def bind(cls): bindHighlighter(cls) return cls class Highlighter(SyntaxState): def matchPrefix(prefix): let i = 0 while True: if i == len(prefix): return True if prefix[i] != self[i]: return False if not self[i]: return False i++ def paintSingleString(): self.paint(1, self.FLAG_STRING) while self[0]: if self[0] == '\\' and self[1] == "'": self.paint(2, self.FLAG_ESCAPE) else if self[0] == "'": self.paint(1, self.FLAG_STRING) return else if self[0] == '\\': self.paint(2, self.FLAG_ESCAPE) else: self.paint(1, self.FLAG_STRING) def paintSimpleString(): self.paint(1, self.FLAG_STRING) while self[0]: if self[0] == '\\' and self[1] == '"': self.paint(2, self.FLAG_ESCAPE) else if self[0] == '"': self.paint(1, self.FLAG_STRING) return else if self[0] == '\\': self.paint(2, self.FLAG_ESCAPE) else: self.paint(1, self.FLAG_STRING) @staticmethod def isalpha(c): c = c if isinstance(c,int) else ord(c) if c else -1 return (c >= ord('a') and c <= ord('z')) or (c >= ord('A') and c <= ord('Z')) @staticmethod def isalnum(c): c = c if isinstance(c,int) else ord(c) if c else -1 return (c >= ord('a') and c <= ord('z')) or (c >= ord('A') and c <= ord('Z')) or (c >= ord('0') and c <= ord('9')) ================================================ FILE: base/usr/share/bim/syntax/bash.krk ================================================ from syntax import Highlighter, bind class BashHighlighter(Highlighter): name = "bash" extensions = ('.sh','.bash','.bashrc') keywords = [ 'if','then','else','elif','fi','case','esac','for','coproc', 'select','while','until','do','done','in','function','time', 'exit','return','source','export','alias','complete','shopt', 'local','eval','echo','cd','pushd','popd','printf','sed', 'rm','mv' ] def popState(self, state): let newState = state // 100 return newState * 10 def pushState(self, state, newState): return state * 10 + newState def paintTick(self, outState): let last = None while self[0] != None: if self[0] == "'": self.paint(1, self.FLAG_STRING) return self.popState(outState) else if last == '\\': self.paint(1, self.FLAG_STRING) last = None else if self[0] != None: last = self[0] self.paint(1, self.FLAG_STRING) return outState def paintBracedVariable(self): while self[0] != None: if self[0] == '}': self.paint(1, self.FLAG_NUMERAL) return 0 self.paint(1, self.FLAG_NUMERAL) return 0 def specialVariable(self, c): return c == '@' or c == '?' def paintString(self, term, outState, color): let last = None while self[0] != None: if last != '\\' and self[0] == term: self.paint(1, color) return self.popState(outState) else if last == '\\': self.paint(1, color) last = None else if term != '`' and self[0] == '`': self.paint(1, self.FLAG_ESCAPE) outState = self.paintString('`', self.pushState(outState, 20), self.FLAG_ESCAPE) else if term != ')' and self[0] == '$' and self[1] == '(': self.paint(2, self.FLAG_TYPE) outState = self.paintString(')', self.pushState(outState, 30), self.FLAG_TYPE) else if self[0] == '$' and self[1] == '{': self.paint(2, self.FLAG_NUMERAL) self.paintBracedVariable() else if self[0] == '$': self.paint(1, self.FLAG_NUMERAL) if self.specialVariable(self[0]): self.paint(1, self.FLAG_NUMERAL) continue while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_NUMERAL) else if term != '"' and self[0] == '"': self.paint(1, self.FLAG_STRING) outState = self.paintString('"', self.pushState(outState, 40), self.FLAG_STRING) else if term != '"' and self[0] == "'": self.paint(1, self.FLAG_STRING) outState = self.paintTick(outState) else if self[0] != -1: last = self[0] self.paint(1, color) return outState def calculate(self): if self.state < 1: if self[0] == '#' and self[-1] != '\\': while self[0] != None: if self.commentBuzzwords(): continue self.paint(1, self.FLAG_COMMENT) return None else if self[0] == "'" and self[-1] != '\\': self.paint(1, self.FLAG_STRING) return self.paintTick(10) else if self[0] == '`' and self[-1] != '\\': self.paint(1, self.FLAG_ESCAPE) return self.paintString('`', 20, self.FLAG_ESCAPE) else if self[0] == '$' and self[1] == '(' and self[-1] != '\\': self.paint(2, self.FLAG_TYPE) return self.paintString(')', 30, self.FLAG_TYPE) else if self[0] == '"' and self[-1] != '\\': self.paint(1, self.FLAG_STRING) return self.paintString('"', 40, self.FLAG_STRING) else if self[0] == '$' and self[1] == '{' and self[-1] != '\\': self.paint(2, self.FLAG_NUMERAL) self.paintBracedVariable() return 0 else if self[0] == '$'and self[-1] != '\\': self.paint(1, self.FLAG_NUMERAL) if self.specialVariable(self[0]): self.paint(1, self.FLAG_NUMERAL) return 0 while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_NUMERAL) return 0 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self[0] == ';': self.paint(1, self.FLAG_KEYWORD) return 0 else if self.cKeywordQualifier(self[0]): for i = 0; self[i] != None; i++: if self[i] == ' ': break if self[i] == '=': self.paint(i, self.FLAG_TYPE) self.skip() return 0 for i = 0; self[i] != None; i++: if self[i] == '(': self.paint(i, self.FLAG_TYPE) return 0 if not self.cKeywordQualifier(self[i]) and self[i] != '-' and self[i] != ' ': break self.skip() return 0 else if self[0] != None: self.skip() return 0 else if self.state >= 10: let outState = self.state while self[0] != None: let s = (outState // 10) % 10 if s == 1: outState = self.paintString("'", outState, self.FLAG_STRING) else if s == 2: outState = self.paintString('`', outState, self.FLAG_ESCAPE) else if s == 3: outState = self.paintString(')', outState, self.FLAG_TYPE) else if s == 4: outState = self.paintString('"', outState, self.FLAG_STRING) else if not s: return None return outState return None bind(BashHighlighter) ================================================ FILE: base/usr/share/bim/syntax/biminfo.krk ================================================ from syntax import Highlighter, bind class BiminfoHighlighter(Highlighter): name = 'biminfo' extensions = ('.biminfo',) def calculate(self): if self.i == 0: if self[0] == '#': self.paint(-1, self.FLAG_COMMENT) else if self[0] == '>': self.paint(1, self.FLAG_KEYWORD) while self[0] != ' ': self.paint(1, self.FLAG_TYPE) self.skip() self.paint(-1, self.FLAG_NUMERAL) return None bind(BiminfoHighlighter) ================================================ FILE: base/usr/share/bim/syntax/c.krk ================================================ from syntax import Highlighter, bind from syntax.doxygen import tryDoxygenComment def makeit(): let FLAG_NONE = Highlighter.FLAG_NONE let FLAG_KEYWORD = Highlighter.FLAG_KEYWORD let FLAG_STRING = Highlighter.FLAG_STRING let FLAG_COMMENT = Highlighter.FLAG_COMMENT let FLAG_TYPE = Highlighter.FLAG_TYPE let FLAG_PRAGMA = Highlighter.FLAG_PRAGMA let FLAG_NUMERAL = Highlighter.FLAG_NUMERAL let FLAG_ERROR = Highlighter.FLAG_ERROR let FLAG_DIFFPLUS = Highlighter.FLAG_DIFFPLUS let FLAG_DIFFMINUS = Highlighter.FLAG_DIFFMINUS let FLAG_NOTICE = Highlighter.FLAG_NOTICE let FLAG_BOLD = Highlighter.FLAG_BOLD let FLAG_LINK = Highlighter.FLAG_LINK let FLAG_ESCAPE = Highlighter.FLAG_ESCAPE let FLAG_EXTRA = Highlighter.FLAG_EXTRA let FLAG_SPECIAL = Highlighter.FLAG_SPECIAL let FLAG_UNDERLINE = Highlighter.FLAG_UNDERLINE class CHighlighter(Highlighter): name = 'c' extensions = ('.c','.h','.cpp','.hpp','.c++','.h++','.cc','.hh') doxygenDocstrings = False keywords = [ "while","if","for","continue","return","break","switch","case","sizeof", "struct","union","typedef","do","default","else","goto", "alignas","alignof","offsetof","asm","__asm__", "public","private","class","using","namespace","virtual","override","protected", "template","typename","static_cast","throw" ] types = [ "static","int","char","short","float","double","void","unsigned","volatile","const", "register","long","inline","restrict","enum","auto","extern","bool","complex", # stdint stuff "uint8_t","uint16_t","uint32_t","uint64_t", "int8_t","int16_t","int32_t","int64_t", "ssize_t","size_t","uintptr_t","intptr_t", # Extra stuff "constexpr","FILE","__volatile__", # sys/types stuff "gid_t","uid_t","dev_t","ino_t","mode_t","caddr_t","off_t","time_t","pid_t", ] special = [ "NULL", "stdin","stdout","stderr", "STDIN_FILENO","STDOUT_FILENO","STDERR_FILENO" ] preprocessor_base_state = 5 def paintCString(self): let last = None while None not in self: if last != '\\' and '"' in self: self[1] = FLAG_STRING return 0 else if '\\' in self and not self[1]: self[1] = FLAG_ESCAPE return 4 else if '\\' in self and self[1] in 'abfnrtv?\\': self[2] = FLAG_ESCAPE last = None else if '\\' in self and self[1] in '01234567': self[2] = FLAG_ESCAPE if '01234567' in self: self[1] = FLAG_ESCAPE if '01234567' in self: self[1] = FLAG_ESCAPE last = None else if '%' in self: self[1] = FLAG_ESCAPE if '%' in self: self[1] = FLAG_ESCAPE else: while '-#*0+' in self: self[1] = FLAG_ESCAPE while self.isdigit(self[0]): self[1] = FLAG_ESCAPE if '.' in self: self[1] = FLAG_ESCAPE if '%' in self: self[1] = FLAG_ESCAPE else: while self.isdigit(self[0]): self[1] = FLAG_ESCAPE while 'lz' in self: self[1] = FLAG_ESCAPE if '"\\' in self: continue self[1] = FLAG_ESCAPE else if '\\' in self and self[1] == 'x': self[2] = FLAG_ESCAPE while self.isxdigit(self[0]): self[1] = FLAG_ESCAPE else if self.doxygenDocstrings and tryDoxygenComment(self, FLAG_STRING): continue else: last = self[0] self[1] = FLAG_STRING return 0 def paintCChar(self): self[1] = FLAG_NUMERAL let last = None while None not in self: if last != '\\' and "'" in self: self[1] = FLAG_NUMERAL return else if last == '\\' and '\\' in self: self[1] = FLAG_NUMERAL last = None else: last = self[0] self[1] = FLAG_NUMERAL def paintCComment(self): let last = None while None not in self: if self.commentBuzzwords(): continue if tryDoxygenComment(self): continue else if last == '*' and '/' in self: self[1] = FLAG_COMMENT return 0 else: last = self[0] self[1] = FLAG_COMMENT return 1 def paintCPragma(self): while None not in self: if '"' in self: self[1] = FLAG_STRING let result = self.paintCString() if result != 0: return result else if "'" in self: self.paintCChar() else if '\\' in self and self[1] == None: self[1] = FLAG_PRAGMA return 2 else if self.findKeywords(self.keywords, FLAG_KEYWORD, self.cKeywordQualifier): continue else if self.findKeywords(self.types, FLAG_TYPE, self.cKeywordQualifier): continue else if '/' in self and self[1] == '/': self.paintComment() return None else if '/' in self and self[1] == '*': if self.paintCComment() == 1: return 3 continue else: self[1] = FLAG_PRAGMA return 0 def paintCNumeral(self): if '0' in self and (self[1] == 'x' or self[1] == 'X'): self[2] = FLAG_NUMERAL while self.isxdigit(self[0]): self[1] = FLAG_NUMERAL else if '0' in self and self[1] == '.': self[2] = FLAG_NUMERAL while self.isdigit(self[0]): self[1] = FLAG_NUMERAL if 'f' in self: self[1] = FLAG_NUMERAL return 0 else if '0' in self: self[1] = FLAG_NUMERAL while '01234567' in self: self[1] = FLAG_NUMERAL else: while self.isdigit(self[0]): self[1] = FLAG_NUMERAL if '.' in self: self[1] = FLAG_NUMERAL while self.isdigit(self[0]): self[1] = FLAG_NUMERAL if 'f' in self: self[1] = FLAG_NUMERAL return 0 while 'uUlL' in self: self[1] = FLAG_NUMERAL return 0 def calculate(self): let cond = self % 0 if cond <= 0: while None not in self: if '#' in self: if any(self[-i-1] != ' ' and self[-i-1] != '\t' for i in range(self.i)): self.skip() continue self[1] = FLAG_PRAGMA while ' ' in self: self[1] = FLAG_PRAGMA if self.matchAndPaint("include", FLAG_PRAGMA, self.cKeywordQualifier): while ' ' in self: self[1] = FLAG_PRAGMA if '<' in self: self[1] = FLAG_STRING while '>' not in self and None not in self: self[1] = FLAG_STRING if None not in self: self[1] = FLAG_STRING else if self.matchAndPaint("if", FLAG_PRAGMA, self.cKeywordQualifier): if ' ' in self and self[1] == '0' and self[2] == None: self[2] = FLAG_COMMENT self.rewind(6) self[-1] = FLAG_COMMENT return self.preprocessor_base_state else if self.matchAndPaint("else", FLAG_PRAGMA, self.cKeywordQualifier): # Do nothing? return self.paintCPragma() else if '/' in self and self[1] == '/': self.paintComment() else if '/' in self and self[1] == '*': self[2] = FLAG_COMMENT return self.paintCComment() else if self.findKeywords(self.keywords, FLAG_KEYWORD, self.cKeywordQualifier): continue else if self.findKeywords(self.types, FLAG_TYPE, self.cKeywordQualifier): continue else if self.findKeywords(self.special, FLAG_NUMERAL, self.cKeywordQualifier): continue else if '"' in self: self[1] = FLAG_STRING return self.paintCString() else if "'" in self: self.paintCChar() else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): self.paintCNumeral() else: self.skip() else if cond == 1: return self.paintCComment() else if cond == 2: return self.paintCPragma() else if cond == 3: if self.paintCComment() == 1: return 3 return self.paintCPragma() else if cond == 4: return self.paintCString() else: while ' ' in self or '\t' in self: self[1] = FLAG_COMMENT if '#' in self: self[1] = FLAG_COMMENT while ' ' in self or '\t' in self: self[1] = FLAG_COMMENT if self.findKeywords(["if","ifdef","ifndef"], FLAG_COMMENT, self.cKeywordQualifier): self[-1] = FLAG_COMMENT return self.state + 1 else if self.findKeywords(["else","elif"], FLAG_COMMENT, self.cKeywordQualifier): self[-1] = FLAG_COMMENT if self.state == self.preprocessor_base_state: return 0 return self.state else if self.matchAndPaint("endif", FLAG_COMMENT, self.cKeywordQualifier): self[-1] = FLAG_COMMENT if self.state == self.preprocessor_base_state: return 0 return self.state - 1 else: self[-1] = FLAG_COMMENT return self.state else: self[-1] = FLAG_COMMENT return self.state return None return CHighlighter let CHighlighter = makeit() bind(CHighlighter) ================================================ FILE: base/usr/share/bim/syntax/conf.krk ================================================ from syntax import Highlighter, bind class ConfigHighlighter(Highlighter): name = "conf" extensions = ('.conf','.ini','.git/config','.cfg','.properties') spaces = True def calculate(self): if self.i == 0: if self[0] == ';': self.paintComment() else if self[0] == '#': self.paintComment() else if self[0] == '[': self.paint(1, self.FLAG_KEYWORD) while self[0] and self[0] != ']': self.paint(1, self.FLAG_KEYWORD) if self[0] == ']': self.paint(1, self.FLAG_KEYWORD) else: while self[0] and self[0] != '=': self.paint(1, self.FLAG_TYPE) return None bind(ConfigHighlighter) ================================================ FILE: base/usr/share/bim/syntax/css.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter class CSSHighlighter(Highlighter): name = 'css' extensions = ('.css', '.scss') elements = [ "a","abbr","address","area","article","aside","audio", "b","base","bdi","bdo","blockquote","body","br","button", "canvas","cite","code","col","colgroup","data","datalist", "dd","del","details","dfn","dialog","div","dl","dt","em", "embed","fieldset","figcaption","figure","footer","form", "h1","h2","h3","h4","h5","h6","head","header","hr","html", "i","iframe","img","input","ins","kbd","label","legend", "li","link","main","map","mark","meta","meter","nav", "noscript","object","ol","optgroup","option","output", "p","param","picture","pre","progress","q","rp","rt", "ruby","s","samp","script","section","select","small", "source","span","strong","style","sub","summary","sup", "svg","table","tbody","td","template","textarea","tfoot", "th","thead","time","title","tr","track","u","ul","var", "video","wbr","hgroup","*", ] properties = [ "align-content","align-items","align-self","all","animation", "animation-delay","animation-direction","animation-duration", "animation-fill-mode","animation-iteration-count","animation-name", "animation-play-state","animation-timing-function","backface-visibility", "background","background-attachment","background-blend-mode","background-clip", "background-color","background-image","background-origin","background-position", "background-repeat","background-size","border","border-bottom","border-bottom-color", "border-bottom-left-radius","border-bottom-right-radius","border-bottom-style", "border-bottom-width","border-collapse","border-color","border-image","border-image-outset", "border-image-repeat","border-image-slice","border-image-source","border-image-width", "border-left","border-left-color","border-left-style","border-left-width", "border-radius","border-right","border-right-color","border-right-style","border-right-width", "border-spacing","border-style","border-top","border-top-color","border-top-left-radius", "border-top-right-radius","border-top-style","border-top-width","border-width", "bottom","box-decoration-break","box-shadow","box-sizing","break-after", "break-before","break-inside","caption-side","caret-color","@charset", "clear","clip","color","column-count","column-fill","column-gap","column-rule","column-rule-color", "column-rule-style","column-rule-width","column-span","column-width","columns","content", "counter-increment","counter-reset","cursor","direction","display","empty-cells", "filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink", "flex-wrap","float","font","@font-face","font-family","font-feature-settings","@font-feature-values", "font-kerning","font-language-override","font-size","font-size-adjust","font-stretch","font-style", "font-synthesis","font-variant","font-variant-alternates","font-variant-caps","font-variant-east-asian", "font-variant-ligatures","font-variant-numeric","font-variant-position","font-weight", "grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column", "grid-column-end","grid-column-gap","grid-column-start","grid-gap","grid-row","grid-row-end", "grid-row-gap","grid-row-start","grid-template","grid-template-areas","grid-template-columns", "grid-template-rows","hanging-punctuation","height","hyphens","image-rendering","@import", "isolation","justify-content","@keyframes","left","letter-spacing","line-break","line-height", "list-style","list-style-image","list-style-position","list-style-type","margin","margin-bottom", "margin-left","margin-right","margin-top","max-height","max-width","@media","min-height", "min-width","mix-blend-mode","object-fit","object-position","opacity","order","orphans", "outline","outline-color","outline-offset","outline-style","outline-width","overflow", "overflow-wrap","overflow-x","overflow-y","padding","padding-bottom","padding-left","padding-right", "padding-top","page-break-after","page-break-before","page-break-inside","perspective", "perspective-origin","pointer-events","position","quotes","resize","right","scroll-behavior", "tab-size","table-layout","text-align","text-align-last","text-combine-upright","text-decoration", "text-decoration-color","text-decoration-line","text-decoration-style","text-indent","text-justify", "text-orientation","text-overflow","text-shadow","text-transform","text-underline-position", "top","transform","transform-origin","transform-style","transition","transition-delay", "transition-duration","transition-property","transition-timing-function","unicode-bidi", "user-select","vertical-align","visibility","white-space","widows","width","word-break", "word-spacing","word-wrap","writing-mode","font-decoration", ] values = [ "inline","block","inline-block","none","oblique", "transparent","thin","dotted","sans-serif", "rgb","rgba","bold","italic","underline","context-box", "monospace","serif","sans-serif","pre-wrap", "relative","baseline","hidden","solid","inherit","normal", "button","pointer","border-box","default","textfield", "collapse","top","bottom","avoid","table-header-group", "middle","absolute","rect","left","center","right", "ellipsis","nowrap","table","both","uppercase","lowercase","help", "static","table-cell","table-column","scroll","touch","auto", "not-allowed","inset","url","fixed","translate","alpha","fixed","device-width", "table-row", ] states = [ "focus","active","hover","link","visited","before","after", "left","right","root","empty","target","enabled","disabled","checked","invalid", "first-child","nth-child","not","last-child", ] def propertyQualifier(c): if isinstance(c,int): if c <= 0: return False c = chr(c) return self.isalnum(c) or c in '-@*!' suffixes = [ 'pt','px','pc','em','cm','mm', 'ex','in','vw','vh','ch','rem', 'vmin','vmax','s' ] def matchSuffixes(suffixes): for s in suffixes: if self.matchPrefix(s): self.paint(len(s), self.FLAG_NUMERAL) return def calculate(): if self.state < 1: if self[0] == '/' and self[1] == '*': if CHighlighter.paintCComment(self) == 1: return 1 else if self[0] == '"': self.paintSimpleString() return 0 else if self[-1] != '.' and self.findKeywords(self.elements, self.FLAG_KEYWORD, self.propertyQualifier): return 0 else if self[-1] != '.' and self.findKeywords(self.properties, self.FLAG_TYPE, self.propertyQualifier): return 0 else if self.matchPrefix('-moz-'): self.paint(5, self.FLAG_ESCAPE) while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE) else if self.matchPrefix('-webkit-'): self.paint(8, self.FLAG_ESCAPE) while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE) else if self.matchPrefix('-ms-'): self.paint(4, self.FLAG_ESCAPE) while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE) else if self.matchPrefix('-o-'): self.paint(3, self.FLAG_ESCAPE) while self[0] and self.propertyQualifier(self[0]): self.paint(1, self.FLAG_TYPE) else if self[0] == ':': self.skip() if self.findKeywords(self.states, self.FLAG_PRAGMA, self.propertyQualifier): return 0 while self[0] and self[0] != ';': if self.findKeywords(self.values, self.FLAG_NUMERAL, self.propertyQualifier): continue else if self[0] == '"': self.paintSimpleString() continue else if self[0] == "'": self.paintSingleString() continue else if self[0] == '{': self.skip() return 0 else if self[0] == '#': self.paint(1, self.FLAG_NUMERAL) while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) else if self.isdigit(self[0]): while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) if self[0] == '.': self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) if self[0] == '%': self.paint(1, self.FLAG_NUMERAL) else: self.matchSuffixes(self.suffixes) else if self.matchAndPaint("!important", self.FLAG_PRAGMA, self.propertyQualifier): continue else if self[0]: self.skip() return 0 else if not self[0]: return None else: self.skip() return 0 else if self.state == 1: if CHighlighter.paintCComment(self) == 1: return 1 return 0 return None bind(CSSHighlighter) ================================================ FILE: base/usr/share/bim/syntax/ctags.krk ================================================ from syntax import Highlighter, bind class CtagsHighlighter(Highlighter): name = 'ctags' extensions = ('tags',) def calculate(): if self.i == 0: if self[0] == '!': return self.paintComment() while self[0] and self[0] != '\t': self.paint(1, self.FLAG_TYPE) if self[0] == '\t': self.skip() while self[0] and self[0] != '\t': self.paint(1, self.FLAG_NUMERAL) if self[0] == '\t': self.skip() while self[0] and not (self[0] == ';' and self[1] == '"'): self.paint(1, self.FLAG_KEYWORD) return None bind(CtagsHighlighter) ================================================ FILE: base/usr/share/bim/syntax/diff.krk ================================================ from syntax import Highlighter, bind class DiffHighlighter(Highlighter): name = 'diff' extensions = ('.patch','.diff') mapping = { '+': Highlighter.FLAG_DIFFPLUS, '-': Highlighter.FLAG_DIFFMINUS, '@': Highlighter.FLAG_TYPE, ' ': Highlighter.FLAG_KEYWORD, } def calculate(): if self.i == 0: let flag = self.mapping[self[0]] if self[0] in self.mapping else 0 self.paint(-1, flag) return None bind(DiffHighlighter) ================================================ FILE: base/usr/share/bim/syntax/dirent.krk ================================================ ''' For highlighting bim's built-in directory browser. ''' from syntax import Highlighter, bind class DirentHighlighter(Highlighter): name = 'dirent' extensions = () def calculate(): if self.i == 0: if self[0] == '#': return self.paintComment() else if self[0] == 'd': self.paint(1, self.FLAG_COMMENT) self.paint(-1, self.FLAG_KEYWORD) else if self[0] == 'f': self.paint(1, self.FLAG_COMMENT) self.paint(-1, self.FLAG_NONE) return None bind(DirentHighlighter) ================================================ FILE: base/usr/share/bim/syntax/dlang.krk ================================================ from syntax import bind from syntax.c import CHighlighter @bind class DHighlighter(CHighlighter): name = 'dlang' extensions = ('.d',) keywords = CHighlighter.keywords + [ 'abstract', 'alias', 'align', 'assert', 'cast', 'dchar', 'debug', 'delegate', 'deprecated', 'export', # Why aren't these in the C highlighter for C++? 'try', 'catch', 'finally', 'foreach', 'foreach_reverse', 'function', 'interface', 'mixin', 'module', 'macro', 'with', 'scope','unittest','package','typeof','typeid', 'pragma','pure','override','import', ] types = CHighlighter.types + [ 'inout', 'nothrow', 'shared', 'synchronized', 'string', '__gshared', '__traits', '__vector', '__paramaters', 'ubyte','uint','ulong','ushort','real','ref','out','in', ] special = [ 'null','true','false', '__FILE__','__FILE_FULL_PATH__','__MODULE__','__LINE__', '__FUNCTION__','__PRETTY_FUNCTION__', ] ================================================ FILE: base/usr/share/bim/syntax/docker.krk ================================================ from syntax import Highlighter, bind from syntax.bash import BashHighlighter @bind class DockerfileHighlighter(BashHighlighter): name = "docker" extensions = ('Dockerfile',) spaces = True keywords = [ 'FROM','AS','MAINTAINER','RUN','CMD','COPY','EXPOSE','ADD', 'ENTRYPOINT','VOLUME','USER','WORKDIR','ONBUILD','LABEL','ARG', 'HEALTHCHECK','SHELL','STOPSIGNAL', ] def calculate(self): if self.state < 1 and self.i == 0: if self[0] == '#': self.paintComment() else if self.matchAndPaint('RUN', self.FLAG_KEYWORD, self.cKeywordQualifier): let out = super().calculate() if out == None and self[-1] == '\\': return 1 return out else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else: self.skip(1) else: if self.state == 1: self << 0 let out = super().calculate() if out == None and self[-1] == '\\': return 1 return out return None ================================================ FILE: base/usr/share/bim/syntax/doxygen.krk ================================================ def _make_dox(): from syntax import Highlighter let doxygen_keywords_at = [ "@author","@brief","@class","@short","@retval", "@since","@return","@returns","@throws","@bug", "@version","@deprecated","@attention","@note", "@protected","@copyright","@date","@bug","@pre", "@details", ] let doxygen_word_commands = { '@param': Highlighter.FLAG_TYPE, '@exception': Highlighter.FLAG_PRAGMA, '@def': Highlighter.FLAG_TYPE, '@see': Highlighter.FLAG_LINK, '@p': Highlighter.FLAG_TYPE, '@c': Highlighter.FLAG_NONE, '@file': Highlighter.FLAG_LINK, '@memberof': Highlighter.FLAG_TYPE, '@extends': Highlighter.FLAG_TYPE, '@mainpage': Highlighter.FLAG_STRING, '@b': Highlighter.FLAG_BOLD, '@section': Highlighter.FLAG_BOLD, '@subsection': Highlighter.FLAG_BOLD, '@package': Highlighter.FLAG_TYPE, '@ref': Highlighter.FLAG_LINK, '@typedef': Highlighter.FLAG_TYPE, '@struct': Highlighter.FLAG_TYPE, } let doxygen_block_commands = { '@warning': Highlighter.FLAG_NOTICE, } let doxygen_emphasis_commands = [ '@a', '@e', '@em', ] def doxygen_qualifier(c): if isinstance(c,int): if c > 0: c = chr(c) else: return False return Highlighter.isalnum(c) or c in '_@' def tryDoxygenComment(b,defaultflag=b.FLAG_COMMENT): if b[0] == '@': if not b.findKeywords(doxygen_keywords_at, b.FLAG_ESCAPE, doxygen_qualifier): for keyword, flag in doxygen_word_commands.items(): if b.matchAndPaint(keyword, b.FLAG_ESCAPE, doxygen_qualifier): while b[0] == ' ': b.skip() while b[0] and b[0] != ' ': if b[0] == '.' and b[1] in (None,' '): return True b.paint(1, flag) return True for keyword, flag in doxygen_block_commands.items(): if b.matchAndPaint(keyword, b.FLAG_ESCAPE, doxygen_qualifier): while b[0] == ' ': b.skip() while b[0]: b.paint(1, flag) return True if b.findKeywords(doxygen_emphasis_commands, b.FLAG_ESCAPE, doxygen_qualifier): while b[0] == ' ': b.skip() while b[0] and b[0] != ' ': b.paint(1, defaultflag | b.FLAG_UNDERLINE) return True b.paint(1, defaultflag) return True return False return tryDoxygenComment let tryDoxygenComment = _make_dox() ================================================ FILE: base/usr/share/bim/syntax/esh.krk ================================================ ''' Shell highlighter for ToaruOS's 'experimental shell'. ''' from syntax import Highlighter, bind class EshHighlighter(Highlighter): name = 'esh' extensions = ('.eshrc','.yutanirc') keywords = [ "cd","exit","export","help","history","if","empty?", "equals?","return","export-cmd","source","exec","not","while", "then","else","echo", ] def variableQualifier(c): c = c if isinstance(c,int) else ord(c) if c else -1 return self.isalnum(c) or c == ord('_') def keywordQualifier(c): c = c if isinstance(c,int) else ord(c) if c else -1 return self.isalnum(c) or c > 0 and chr(c) in '?_-' def paintVariable(): if self[0] == '{': self.paint(1, self.FLAG_TYPE) while self[0] != '}' and self[0]: self.paint(1, self.FLAG_TYPE) if self[0] == '}': self.paint(1, self.FLAG_TYPE) else: if self[0] in '?$#': self.paint(1, self.FLAG_TYPE) else: while self.variableQualifier(self[0]): self.paint(1, self.FLAG_TYPE) def paintString(): let last = None while self[0]: if last != '\\' and self[0] == '"': self.paint(1, self.FLAG_STRING) return 0 else if self[0] == '$': self.paint(1, self.FLAG_TYPE) self.paintVariable() last = None else if self[0]: last = self[0] self.paint(1, self.FLAG_STRING) return 2 def paintSingle(): let last = None while self[0]: if last != '\\' and self[0] == "'": self.paint(1, self.FLAG_STRING) return 0 else if self[0]: last = self[0] self.paint(1, self.FLAG_STRING) return 1 def calculate(): if self.state == 1: return self.paintSingle() else if self.state == 2: return self.paintString() if self[0] == '#': return self.paintComment() else if self[0] == '$': self.paint(1, self.FLAG_TYPE) self.paintVariable() return 0 else if self[0] == "'": self.paint(1, self.FLAG_STRING) return self.paintSingle() else if self[0] == '"': self.paint(1, self.FLAG_STRING) return self.paintString() else if self.matchAndPaint('export', self.FLAG_KEYWORD, self.keywordQualifier): while self[0] == ' ': self.skip() while self.keywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) return 0 else if self.matchAndPaint('export-cmd', self.FLAG_KEYWORD, self.keywordQualifier): while self[0] == ' ': self.skip() while self.keywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) return 0 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.keywordQualifier): return 0 else if self.isdigit(self[0]): while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) return 0 else if self[0]: self.skip() return 0 return None try: import os if os.uname['sysname'] == 'toaru': EshHighlighter.extensions = ('.eshrc','.yutanirc','.sh') bind(EshHighlighter) ================================================ FILE: base/usr/share/bim/syntax/gas.krk ================================================ from syntax import Highlighter, bind @bind class GasHighlighter(Highlighter): name = 'gas' extensions = ('.S',) spaces = True directives = ['.' + x for x in [ 'abort','align','balignw','balignl','bundle_align_mode','bundle_lock','bundle_unlock', 'bss','cfi_startproc','cfi_sections','cfi_endproc','cfi_personality','cfi_lsda', 'cfi_def_cfa','cfi_def_cfa_register','cfi_def_cfa_offset','cfi_adjust_cfa_offset', 'cfi_offset','cfi_rel_offset','cfi_register','cfi_restore','cfi_undefined', 'cfi_same_value','cfi_remember_state','cfi_return_column','cfi_signal_frame', 'cfi_window_save','cfi_escape','cfi_val_encoded_addr','data','def','desc', 'dim','eject','else','elseif','endef','endif','endr','qeu','equiv','eqv','err', 'error','exitm','extern','fail','file','fill','global','globl','gnu_attribute', 'hidden','ident','if','incbin','include','internal','irp','irpc','lcomm','lflags', 'line','linkonce','list','ln','loc','loc_mark_labels','local','mri','nolist','org', 'p2alignw','p2align1','popsection','previous','print','protected','psize','purgem', 'pushsection','reloc','rept','sbttl','scl','section','set','single','size','skip', 'sleb128','stabd','stabn','stabs','struct','subsection','symver','tag','text','title', 'type','uleb128','val','version','vtable_entry','vtable_inherit','warning','weak', 'weakref', 'code16','code32','code64', ]] types = ['.' + x for x in [ 'byte','hword','word','int','long','double','short','float','quad','octa', 'string','string8','string16','ascii','asciz','comm','space' ]] registers = ['%' + x for x in [ 'r8','r9','r10','r11','r12','r13','r14','r15', 'rax','rbx','rcx','rdx','rdi','rsi','rsp','rbp', 'eax','ebx','ecx','edx','ax','bx','cx','dx','ah','al','bh','bl','ch','cl','dh','dl', 'edi','esi','esp','ebp','di','si','sp','bp','sph','spl','bph','bpl', 'cs','ds','es','fs','gs','ss','ip','eip','rip','eflags', 'cr0','cr1','cr2','cr3','cr4', ]] instructions = [ 'aaa', 'aad','aadb','aadw','aadl','aadq', 'aam','aamb','aamw','aaml','aamq', 'aas', 'adc','adcb','adcw','adcl','adcq', 'add','addb','addw','addl','addq', 'and','andb','andw','andl','andq', 'arpl', 'bb0_reset', 'bb1_reset', 'bound','boundb','boundw','boundl','boundq', 'bsf', 'bsr', 'bswap', 'bt','btb','btw','btl','btq', 'btc','btcb','btcw','btcl','btcq', 'btr','btrb','btrw','btrl','btrq', 'bts','btsb','btsw','btsl','btsq', 'call','callb','callw','calll','callq', 'cbw', 'cdq', 'cdqe', 'clc', 'cld', 'clgi', 'cli', 'clts', 'cmc', 'cmp','cmpb','cmpw','cmpl','cmpq', 'cmpsb', 'cmpsd', 'cmpsq', 'cmpsw', 'cmpxchg', 'cmpxchg486', 'cmpxchg8b','cmpxchg8bb','cmpxchg8bw','cmpxchg8bl','cmpxchg8bq', 'cmpxchg16b','cmpxchg16bb','cmpxchg16bw','cmpxchg16bl','cmpxchg16bq', 'cpuid', 'cpu_read', 'cpu_write', 'cqo', 'cwd', 'cwde', 'daa', 'das', 'dec', 'div', 'dmint', 'emms', 'enter','enterb','enterw','enterl','enterq', 'equ', 'f2xm1', 'fabs', 'fadd', 'faddp', 'fbld','fbldb','fbldw','fbldl','fbldq', 'fbstp','fbstpb','fbstpw','fbstpl','fbstpq', 'fchs', 'fclex', 'fcmovb', 'fcmovbe', 'fcmove', 'fcmovnb', 'fcmovnbe', 'fcmovne', 'fcmovnu', 'fcmovu', 'fcom', 'fcomi', 'fcomip', 'fcomp', 'fcompp', 'fcos', 'fdecstp', 'fdisi', 'fdiv', 'fdivp', 'fdivr', 'fdivrp', 'femms', 'feni', 'ffree', 'ffreep', 'fiadd','fiaddb','fiaddw','fiaddl','fiaddq', 'ficom','ficomb','ficomw','ficoml','ficomq', 'ficomp','ficompb','ficompw','ficompl','ficompq', 'fidiv','fidivb','fidivw','fidivl','fidivq', 'fidivr','fidivrb','fidivrw','fidivrl','fidivrq', 'fild','fildb','fildw','fildl','fildq', 'fimul','fimulb','fimulw','fimull','fimulq', 'fincstp', 'finit', 'fist','fistb','fistw','fistl','fistq', 'fistp','fistpb','fistpw','fistpl','fistpq', 'fisttp','fisttpb','fisttpw','fisttpl','fisttpq', 'fisub','fisubb','fisubw','fisubl','fisubq', 'fisubr','fisubrb','fisubrw','fisubrl','fisubrq', 'fld', 'fld1', 'fldcw','fldcwb','fldcww','fldcwl','fldcwq', 'fldenv','fldenvb','fldenvw','fldenvl','fldenvq', 'fldl2e', 'fldl2t', 'fldlg2', 'fldln2', 'fldpi', 'fldz', 'fmul', 'fmulp', 'fnclex', 'fndisi', 'fneni', 'fninit', 'fnop', 'fnsave','fnsaveb','fnsavew','fnsavel','fnsaveq', 'fnstcw','fnstcwb','fnstcww','fnstcwl','fnstcwq', 'fnstenv','fnstenvb','fnstenvw','fnstenvl','fnstenvq', 'fnstsw', 'fpatan', 'fprem', 'fprem1', 'fptan', 'frndint', 'frstor','frstorb','frstorw','frstorl','frstorq', 'fsave','fsaveb','fsavew','fsavel','fsaveq', 'fscale', 'fsetpm', 'fsin', 'fsincos', 'fsqrt', 'fst', 'fstcw','fstcwb','fstcww','fstcwl','fstcwq', 'fstenv','fstenvb','fstenvw','fstenvl','fstenvq', 'fstp', 'fstsw', 'fsub', 'fsubp', 'fsubr', 'fsubrp', 'ftst', 'fucom', 'fucomi', 'fucomip', 'fucomp', 'fucompp', 'fxam', 'fxch', 'fxtract', 'fyl2x', 'fyl2xp1', 'hlt', 'ibts', 'icebp', 'idiv', 'imul','imulb','imulw','imull','imulq', 'in', 'inc','incb','incw','incl','incq', 'incbin', 'insb', 'insd', 'insw', 'int','intb','intw','intl','intq', 'int01', 'int1', 'int03', 'int3', 'into', 'invd', 'invlpg','invlpgb','invlpgw','invlpgl','invlpgq', 'invlpga', 'iret', 'iretd', 'iretq', 'iretw', 'jcxz','jcxzb','jcxzw','jcxzl','jcxzq', 'jecxz','jecxzb','jecxzw','jecxzl','jecxzq', 'jrcxz','jrcxzb','jrcxzw','jrcxzl','jrcxzq', 'jmp','jmpb','jmpw','jmpl','jmpq', 'jmpe', 'lahf', 'lar', 'lds','ldsb','ldsw','ldsl','ldsq', 'lea','leab','leaw','leal','leaq', 'leave', 'les','lesb','lesw','lesl','lesq', 'lfence', 'lfs','lfsb','lfsw','lfsl','lfsq', 'lgdt','lgdtb','lgdtw','lgdtl','lgdtq', 'lgs','lgsb','lgsw','lgsl','lgsq', 'lidt','lidtb','lidtw','lidtl','lidtq', 'lldt', 'lmsw', 'loadall', 'loadall286', 'lodsb', 'lodsd', 'lodsq', 'lodsw', 'loop','loopb','loopw','loopl','loopq', 'loope','loopeb','loopew','loopel','loopeq', 'loopne','loopneb','loopnew','loopnel','loopneq', 'loopnz','loopnzb','loopnzw','loopnzl','loopnzq', 'loopz','loopzb','loopzw','loopzl','loopzq', 'lsl', 'lss','lssb','lssw','lssl','lssq', 'ltr', 'mfence', 'monitor', 'mov','movb','movw','movl','movq', 'movd', 'movq', 'movsb', 'movsd', 'movsq', 'movsw', 'movsx', 'movsxd', 'movsx', 'movzx', 'movzbl', 'mul', 'mwait', 'neg', 'nop', 'not', 'or','orb','orw','orl','orq', 'out', 'outsb', 'outsd', 'outsw', 'packssdw','packssdwb','packssdww','packssdwl','packssdwq', 'packsswb','packsswbb','packsswbw','packsswbl','packsswbq', 'packuswb','packuswbb','packuswbw','packuswbl','packuswbq', 'paddb','paddbb','paddbw','paddbl','paddbq', 'paddd','padddb','padddw','padddl','padddq', 'paddsb','paddsbb','paddsbw','paddsbl','paddsbq', 'paddsiw','paddsiwb','paddsiww','paddsiwl','paddsiwq', 'paddsw','paddswb','paddsww','paddswl','paddswq', 'paddusb','paddusbb','paddusbw','paddusbl','paddusbq', 'paddusw','padduswb','paddusww','padduswl','padduswq', 'paddw','paddwb','paddww','paddwl','paddwq', 'pand','pandb','pandw','pandl','pandq', 'pandn','pandnb','pandnw','pandnl','pandnq', 'pause', 'paveb','pavebb','pavebw','pavebl','pavebq', 'pavgusb','pavgusbb','pavgusbw','pavgusbl','pavgusbq', 'pcmpeqb','pcmpeqbb','pcmpeqbw','pcmpeqbl','pcmpeqbq', 'pcmpeqd','pcmpeqdb','pcmpeqdw','pcmpeqdl','pcmpeqdq', 'pcmpeqw','pcmpeqwb','pcmpeqww','pcmpeqwl','pcmpeqwq', 'pcmpgtb','pcmpgtbb','pcmpgtbw','pcmpgtbl','pcmpgtbq', 'pcmpgtd','pcmpgtdb','pcmpgtdw','pcmpgtdl','pcmpgtdq', 'pcmpgtw','pcmpgtwb','pcmpgtww','pcmpgtwl','pcmpgtwq', 'pdistib','pdistibb','pdistibw','pdistibl','pdistibq', 'pf2id','pf2idb','pf2idw','pf2idl','pf2idq', 'pfacc','pfaccb','pfaccw','pfaccl','pfaccq', 'pfadd','pfaddb','pfaddw','pfaddl','pfaddq', 'pfcmpeq','pfcmpeqb','pfcmpeqw','pfcmpeql','pfcmpeqq', 'pfcmpge','pfcmpgeb','pfcmpgew','pfcmpgel','pfcmpgeq', 'pfcmpgt','pfcmpgtb','pfcmpgtw','pfcmpgtl','pfcmpgtq', 'pfmax','pfmaxb','pfmaxw','pfmaxl','pfmaxq', 'pfmin','pfminb','pfminw','pfminl','pfminq', 'pfmul','pfmulb','pfmulw','pfmull','pfmulq', 'pfrcp','pfrcpb','pfrcpw','pfrcpl','pfrcpq', 'pfrcpit1','pfrcpit1b','pfrcpit1w','pfrcpit1l','pfrcpit1q', 'pfrcpit2','pfrcpit2b','pfrcpit2w','pfrcpit2l','pfrcpit2q', 'pfrsqit1','pfrsqit1b','pfrsqit1w','pfrsqit1l','pfrsqit1q', 'pfrsqrt','pfrsqrtb','pfrsqrtw','pfrsqrtl','pfrsqrtq', 'pfsub','pfsubb','pfsubw','pfsubl','pfsubq', 'pfsubr','pfsubrb','pfsubrw','pfsubrl','pfsubrq', 'pi2fd','pi2fdb','pi2fdw','pi2fdl','pi2fdq', 'pmachriw','pmachriwb','pmachriww','pmachriwl','pmachriwq', 'pmaddwd','pmaddwdb','pmaddwdw','pmaddwdl','pmaddwdq', 'pmagw','pmagwb','pmagww','pmagwl','pmagwq', 'pmulhriw','pmulhriwb','pmulhriww','pmulhriwl','pmulhriwq', 'pmulhrwa','pmulhrwab','pmulhrwaw','pmulhrwal','pmulhrwaq', 'pmulhrwc','pmulhrwcb','pmulhrwcw','pmulhrwcl','pmulhrwcq', 'pmulhw','pmulhwb','pmulhww','pmulhwl','pmulhwq', 'pmullw','pmullwb','pmullww','pmullwl','pmullwq', 'pmvgezb','pmvgezbb','pmvgezbw','pmvgezbl','pmvgezbq', 'pmvlzb','pmvlzbb','pmvlzbw','pmvlzbl','pmvlzbq', 'pmvnzb','pmvnzbb','pmvnzbw','pmvnzbl','pmvnzbq', 'pmvzb','pmvzbb','pmvzbw','pmvzbl','pmvzbq', 'pop','popb','popw','popl','popq', 'popa', 'popal', 'popaw', 'popf', 'popfd','popfl', 'popfq', 'popfw', 'por','porb','porw','porl','porq', 'prefetch','prefetchb','prefetchw','prefetchl','prefetchq', 'prefetchw','prefetchwb','prefetchww','prefetchwl','prefetchwq', 'pslld','pslldb','pslldw','pslldl','pslldq', 'psllq','psllqb','psllqw','psllql','psllqq', 'psllw','psllwb','psllww','psllwl','psllwq', 'psrad','psradb','psradw','psradl','psradq', 'psraw','psrawb','psraww','psrawl','psrawq', 'psrld','psrldb','psrldw','psrldl','psrldq', 'psrlq','psrlqb','psrlqw','psrlql','psrlqq', 'psrlw','psrlwb','psrlww','psrlwl','psrlwq', 'psubb','psubbb','psubbw','psubbl','psubbq', 'psubd','psubdb','psubdw','psubdl','psubdq', 'psubsb','psubsbb','psubsbw','psubsbl','psubsbq', 'psubsiw','psubsiwb','psubsiww','psubsiwl','psubsiwq', 'psubsw','psubswb','psubsww','psubswl','psubswq', 'psubusb','psubusbb','psubusbw','psubusbl','psubusbq', 'psubusw','psubuswb','psubusww','psubuswl','psubuswq', 'psubw','psubwb','psubww','psubwl','psubwq', 'punpckhbw','punpckhbwb','punpckhbww','punpckhbwl','punpckhbwq', 'punpckhdq','punpckhdqb','punpckhdqw','punpckhdql','punpckhdqq', 'punpckhwd','punpckhwdb','punpckhwdw','punpckhwdl','punpckhwdq', 'punpcklbw','punpcklbwb','punpcklbww','punpcklbwl','punpcklbwq', 'punpckldq','punpckldqb','punpckldqw','punpckldql','punpckldqq', 'punpcklwd','punpcklwdb','punpcklwdw','punpcklwdl','punpcklwdq', 'push','pushb','pushw','pushl','pushq', 'pusha', 'pushal', 'pushaw', 'pushf', 'pushfd', 'pushfq', 'pushfw', 'pxor','pxorb','pxorw','pxorl','pxorq', 'rcl','rclb','rclw','rcll','rclq', 'rcr','rcrb','rcrw','rcrl','rcrq', 'rdshr', 'rdmsr', 'rdpmc', 'rdtsc', 'rdtscp', 'ret','retb','retw','retl','retq', 'retf','retfb','retfw','retfl','retfq', 'retn','retnb','retnw','retnl','retnq', 'rol','rolb','rolw','roll','rolq', 'ror','rorb','rorw','rorl','rorq', 'rdm', 'rsdc','rsdcb','rsdcw','rsdcl','rsdcq', 'rsldt','rsldtb','rsldtw','rsldtl','rsldtq', 'rsm', 'rsts','rstsb','rstsw','rstsl','rstsq', 'sahf', 'sal','salb','salw','sall','salq', 'salc', 'sar','sarb','sarw','sarl','sarq', 'sbb','sbbb','sbbw','sbbl','sbbq', 'scasb', 'scasd', 'scasq', 'scasw', 'sfence', 'sgdt','sgdtb','sgdtw','sgdtl','sgdtq', 'shl','shlb','shlw','shll','shlq', 'shld', 'shr','shrb','shrw','shrl','shrq', 'shrd', 'sidt','sidtb','sidtw','sidtl','sidtq', 'sldt', 'skinit', 'smi', 'smint', 'smintold', 'smsw', 'stc', 'std', 'stgi', 'sti', 'stosb', 'stosd','stosl', 'stosq', 'stosw', 'str', 'sub','subb','subw','subl','subq', 'svdc','svdcb','svdcw','svdcl','svdcq', 'svldt','svldtb','svldtw','svldtl','svldtq', 'svts','svtsb','svtsw','svtsl','svtsq', 'swapgs', 'syscall', 'sysenter', 'sysexit', 'sysret', 'test','testb','testw','testl','testq', 'ud0', 'ud1', 'ud2b', 'ud2', 'ud2a', 'umov', 'verr', 'verw', 'fwait', 'wbinvd', 'wrshr', 'wrmsr', 'xadd', 'xbts', 'xchg', 'xlatb', 'xlat', 'xor','xorb','xorw','xorl','xorq', 'cmovcc', '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', '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', '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', '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', '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', 'ljmp', 'rep', 'stos', 'addr32', ] def dqualifier(self, c): return self.cKeywordQualifier(c) or c == '.' or c == ord('.') def rqualifier(self, c): return self.cKeywordQualifier(c) or c == '%' or c == ord('%') def paintCComment(self): let last = None while self[0] != None: if self.commentBuzzwords(): continue else if last == '*' and self[0] == '/': self.paint(1, self.FLAG_COMMENT) return 0 else: last = self[0] self.paint(1, self.FLAG_COMMENT) return 1 def paintCNumeral(self): if self[0] == '0' and (self[1] == 'x' or self[1] == 'X'): self.paint(2, self.FLAG_NUMERAL) while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) else if self[0] == '0' and self[1] == '.': self.paint(2, self.FLAG_NUMERAL) while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) if self[0] == 'f': self.paint(1, self.FLAG_NUMERAL) return 0 else if self[0] == '0': self.paint(1, self.FLAG_NUMERAL) while self[0] in '01234567': self.paint(1, self.FLAG_NUMERAL) else: while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) if self[0] == '.': self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) if self[0] == 'f': self.paint(1, self.FLAG_NUMERAL) return 0 while self[0] in 'uUlL': self.paint(1, self.FLAG_NUMERAL) return 0 def calculate(self): if self.state <= 0: if self[0] == '/' and self[1] == '/': while self[0]: self.paint(1, self.FLAG_COMMENT) else if self[0] == '/' and self[1] == '*': self.paint(2, self.FLAG_COMMENT) return self.paintCComment() else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): self.paintCNumeral() return 0 else if self[0] == '.': if self.findKeywords(self.directives, self.FLAG_KEYWORD, self.dqualifier): return 0 else if self.findKeywords(self.types, self.FLAG_PRAGMA, self.dqualifier): return 0 else: self.skip() return 0 else if self[0] == '%': if self.findKeywords(self.registers, self.FLAG_ESCAPE, self.rqualifier): return 0 else: self.skip() return 0 else if not self.cKeywordQualifier(self[-1]) and self.isalpha(self[0]): if self.findKeywords(self.instructions, self.FLAG_TYPE, self.cKeywordQualifier): return 0 self.skip() return 0 else if self[0] == '"': self.paintSimpleString() return 0 else if self[0] == "'": self.paintSingleString() return 0 else if self[0] != None: self.skip() return 0 else if self.state == 1: return self.paintCComment() return None ================================================ FILE: base/usr/share/bim/syntax/git.krk ================================================ from syntax import Highlighter, bind class GitcommitHighlighter(Highlighter): name = 'gitcommit' extensions = ('COMMIT_EDITMSG',) def calculate(): if self.i == 0 and self[0] == '#': return self.paintComment() else if self.lineno == 0: while self[0] and self.i < 50: self.paint(1, self.FLAG_KEYWORD) self.paint(-1, self.FLAG_DIFFMINUS) else if self.lineno == 1: self.paint(-1, self.FLAG_DIFFMINUS) else if self[0]: self.paint(-1, self.FLAG_NONE) return None bind(GitcommitHighlighter) class GitrebaseHighlighter(Highlighter): name = 'gitrebase' extensions = ('git-rebase-todo',) commands = [ "p","r","e","s","f","x","d", "pick","reword","edit","squash","fixup", "exec","drop" ] def calculate(): if self.i == 0 and self[0] == '#': return self.paintComment() else if self.i == 0 and self.findKeywords(self.commands, self.FLAG_KEYWORD, self.cKeywordQualifier): while self[0] == ' ': self.skip() while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) return None return None bind(GitrebaseHighlighter) ================================================ FILE: base/usr/share/bim/syntax/graphql.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter class GraphqlHighlighter(Highlighter): name = 'graphql' extensions = ('.graphql','.gql','.graphqls') keywords = [ 'repeatable','on','implements','extend', 'enum','scalar','type','union','input','interface','subscription', 'query','mutation','fragment','directive','schema', ] constants = [ 'true','false','null', '__schema','__type','__typename', ] primitives = [ ] def paintTriple(): while self[0]: if self[0] == "'": self.paint(1, self.FLAG_STRING) if self[0] == "'" and self[1] == "'": self.paint(2, self.FLAG_STRING) return 0 else: self.paint(1, self.FLAG_STRING) return 2 def paintPyTriple(quote): while self[0]: if self[0] == quote: self.paint(1, self.FLAG_STRING) if self[0] == quote and self[1] == quote: self.paint(2, self.FLAG_STRING) return 0 else: self.paint(1, self.FLAG_STRING) return 1 if quote == '"' else 2 def calculate(): if self.state <= 0: if self[0] == '#': self.paintComment() else if self[0] == '"': if self[1] == '"' and self[2] == '"': self.paint(3, self.FLAG_STRING) return self.paintPyTriple('"') self.paintSimpleString() return 0 else if self[0] == "'": self.paintSingleString() return 0 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self.findKeywords(self.primitives, self.FLAG_TYPE, self.cKeywordQualifier): return 0 else if self.findKeywords(self.constants, self.FLAG_NUMERAL, self.cKeywordQualifier): return 0 else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): CHighlighter.paintCNumeral(self) return 0 else if self[0] == '@': self.paint(1, self.FLAG_TYPE) while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) return 0 else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) return 0 else if self[0]: self.skip() return 0 return None else if self.state == 1: return self.paintPyTriple('"') return None bind(GraphqlHighlighter) ================================================ FILE: base/usr/share/bim/syntax/groovy.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter class GroovyHighlighter(Highlighter): name = 'groovy' extensions = ('.groovy','.jenkinsfile','.gradle') keywords = [ "as","assert","break","case", "catch","class","const","continue", "def","default","do","else","enum", "extends","finally","for", "goto","if","implements","import", "in","instanceof","interface","new", "package","return","super", "switch","throw","throws", "trait","try","while","final", ] constants = [ 'true','false','null','this', ] primitives = [ 'byte','char','short','int','long','BigInteger' ] def paintTriple(): while self[0]: if self[0] == "'": self.paint(1, self.FLAG_STRING) if self[0] == "'" and self[1] == "'": self.paint(2, self.FLAG_STRING) return 0 else: self.paint(1, self.FLAG_STRING) return 2 def calculate(): if self.state <= 0: if self.lineno == 0 and self.i == 0 and self[0] == '#': self.paintComment() return None else if self[0] == '/' and self[1] == '/': self.paintComment() return None else if self[0] == '/' and self[1] == '*': if CHighlighter.paintCComment(self) == 1: return 1 else if self[0] == '"': self.paintSimpleString() return 0 else if self[0] == "'": self.paintSingleString() return 0 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self.findKeywords(self.primitives, self.FLAG_TYPE, self.cKeywordQualifier): return 0 else if self.findKeywords(self.constants, self.FLAG_NUMERAL, self.cKeywordQualifier): return 0 else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): CHighlighter.paintCNumeral(self) return 0 else if self[0] == '@': self.paint(1, self.FLAG_TYPE) while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) return 0 else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) return 0 else if self[0]: self.skip() return 0 return None else if self.state == 1: if CHighlighter.paintCComment(self) == 1: return 1 return 0 else if self.state == 2: return self.paintTriple() return None bind(GroovyHighlighter) ================================================ FILE: base/usr/share/bim/syntax/hosts.krk ================================================ from syntax import Highlighter, bind class HostsHighlighter(Highlighter): name = 'hosts' extensions = ('hosts',) def calculate(): if self.i == 0: if self[0] == '#': return self.paintComment() while self[0] != '\t' and self[0] != ' ' and self[0]: self.paint(1, self.FLAG_NUMERAL) while self[0]: self.paint(1, self.FLAG_TYPE) return None bind(HostsHighlighter) ================================================ FILE: base/usr/share/bim/syntax/issue.krk ================================================ from syntax import Highlighter, bind class EtcIssueHighlighter(Highlighter): name = "issue" extensions = ('/etc/issue',) spaces = True e_types = [ ('bold', Highlighter.FLAG_BOLD), ('reset', Highlighter.FLAG_NONE), ('blue', Highlighter.FLAG_KEYWORD), ('red', Highlighter.FLAG_DIFFMINUS), ('black', Highlighter.FLAG_COMMENT), ('brown', Highlighter.FLAG_TYPE), ('cyan', Highlighter.FLAG_KEYWORD), ('darkgray', Highlighter.FLAG_COMMENT), ('gray', Highlighter.FLAG_COMMENT), ('green', Highlighter.FLAG_DIFFPLUS), ('lightblue', Highlighter.FLAG_KEYWORD), ('lightcyan', Highlighter.FLAG_KEYWORD), ('lightgray', Highlighter.FLAG_COMMENT), ('lightgreen', Highlighter.FLAG_DIFFPLUS), ('lightmagenta', Highlighter.FLAG_NUMERAL), ('lightred', Highlighter.FLAG_DIFFMINUS), ('magenta', Highlighter.FLAG_NUMERAL), ('reverse', Highlighter.FLAG_UNDERLINE), ('yellow', Highlighter.FLAG_NOTICE), ] def calculate(self): if self[0] == '\\': if self[1] in ('\\','n','s','r','m','v','l','d','t'): self.paint(2, self.FLAG_ESCAPE) return 0 else if not self[1]: self.paint(1, self.FLAG_ESCAPE) return None else if self[1] == 'e': if self[2] == '{': self.paint(3, self.FLAG_ESCAPE) for tname, tcolor in self.e_types: if self.matchAndPaint(tname, tcolor, self.cKeywordQualifier): break if not self[0] == '}': self.paint(1, self.FLAG_ERROR) else: self.paint(1, self.FLAG_ESCAPE) return 0 else: self.paint(2, self.FLAG_ESCAPE) return 0 else if self[1] == '4': if self[2] == '{': self.paint(3, self.FLAG_ESCAPE) while self[0] and self[0] != '}': self.paint(1, self.FLAG_KEYWORD) if self[0] == '}': self.paint(1, self.FLAG_ESCAPE) return 0 else: self.paint(2, self.FLAG_ESCAPE) return 0 else: self.skip() return 0 else if self[0]: self.skip() return 0 else: return None bind(EtcIssueHighlighter) ================================================ FILE: base/usr/share/bim/syntax/java.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter class JavaHighlighter(Highlighter): name = 'java' extensions = ('.java',) keywords = [ "assert","break","case","catch","class","continue", "default","do","else","enum","exports","extends","finally", "for","if","implements","instanceof","interface","module","native", "new","requires","return","throws", "strictfp","super","switch","synchronized","this","throw","try","while", ] types = [ "var","boolean","void","short","long","int","double","float","enum","char", "private","protected","public","static","final","transient","volatile","abstract", ] special = [ "true","false","import","package","null", ] at_comments = [ "@author","@see","@since","@return","@throws", "@version","@exception","@deprecated", ] brace_comments = [ "{@docRoot","{@inheritDoc","{@link","{@linkplain", "{@value","{@code","{@literal","{@serial", "{@serialData","{@serialField", ] def atKeywordQualifier(c): if isinstance(c,int) and c > 0: c = chr(c) else if isinstance(c,int) and c <= 0: return False return self.isalnum(c) or c in '_@' def braceKeywordQualifier(c): if isinstance(c,int) and c > 0: c = chr(c) else if isinstance(c,int) and c <= 0: return False return self.isalnum(c) or c in '{@_' def keywordQualifier(c): return self.cKeywordQualifier(c) def paintJavaComment(): let last while self[0]: if self.commentBuzzwords(): continue if self[0] == '@': if not self.findKeywords(self.at_comments, self.FLAG_ESCAPE, self.atKeywordQualifier): if self.matchAndPaint('@param', self.FLAG_ESCAPE, self.atKeywordQualifier): while self[0] == ' ': self.skip() while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) else: self.paint(1, self.FLAG_COMMENT) else if self[0] == '{': if self.findKeywords(self.brace_comments, self.FLAG_ESCAPE, self.braceKeywordQualifier): while self[0] != '}' and self[0]: self.paint(1, self.FLAG_ESCAPE) if self[0] == '}': self.paint(1, self.FLAG_ESCAPE) else: self.paint(1, self.FLAG_COMMENT) else if self[0] == '<': let isTag = False for i = 1; self[i]; i++: if self[i] == '>': isTag = True break if not self.isalnum(self[i]) and self[i] != '/': isTag = 0 break if isTag: self.paint(1, self.FLAG_TYPE) while self[0] and self[0] != '>': if self[0] == '/': self.paint(1, self.FLAG_TYPE) else: self.paint(1, self.FLAG_KEYWORD) if self[0] == '>': self.paint(1, self.FLAG_TYPE) else: self.paint(1, self.FLAG_COMMENT) else if last == '*' and self[0] == '/': self.paint(1, self.FLAG_COMMENT) return 0 else: last = self[0] self.paint(1, self.FLAG_COMMENT) return 1 def calculate(): if self.state <= 0: while self[0] is not None: if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): CHighlighter.paintCNumeral(self) else if self[0] == '/' and self[1] == '/': self.paintComment() else if self[0] == '/' and self[1] == '*': if self.paintJavaComment() == 1: return 1 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.keywordQualifier): pass else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): pass else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): pass else if self[0] == '"': self.paintSimpleString() else if self[0] == "'": CHighlighter.paintCChar(self) else if self[0] == '@': self.paint(1, self.FLAG_PRAGMA) while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_PRAGMA) else if self[0]: self.skip() else if self.state == 1: while self[0] is not None: if self.paintJavaComment() == 1: return 1 return None bind(JavaHighlighter) class KotlinHighlighter(JavaHighlighter): name = 'kotlin' extensions = ('.kt',) keywords = [ "as","as?","break","class","continue","do","else","false","for", "fun","if","in","!in","interface","is","!is","null","object", "package","return","super","this","throw","true","try","typealias", "typeof","val","var","when","while", "by","catch","constructor","delegate","dynamic","field","file", "finally","get","import","init","param","property","receiver", "set","setparam","where", "actual","abstract","annotation","companion","const", "crossinline","data","enum","expect","external","final", "infix","inner","internal","lateinit","noinline","open", "operator","out","override","private","protected","public", "reified","sealed","suspend","tailrec","vararg", "field","it","inline", ] types = [ "Byte","Short","Int","Long", "Float","Double","String", ] specials = [] def keywordQualifier(c): if isinstance(c,int) and c > 0: c = chr(c) else if isinstance(c,int) and c <= 0: return False return self.isalnum(c) or c in '?!_' def paintTriple(quote): while self[0]: if self[0] == quote: self.paint(1, self.FLAG_STRING) if self[0] == quote and self[1] == quote: self.paint(2, self.FLAG_STRING) return 0 else: self.paint(1, self.FLAG_STRING) return 2 def calculate(): if self.state <= 0: while self[0] is not None: if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): CHighlighter.paintCNumeral(self) else if self[0] == '/' and self[1] == '/': self.paintComment() else if self[0] == '/' and self[1] == '*': if self.paintJavaComment() == 1: return 1 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.keywordQualifier): pass else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): pass else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): pass else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHJIKLMNOPQRSTUVWXYZ': while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) else if self[0] == '"': if self[1] == '"' and self[2] == '"': self.paint(3, self.FLAG_STRING) return self.paintTriple('"') self.paintSimpleString() else if self[0] == "'": CHighlighter.paintCChar(self) else if self[0] == '@': self.paint(1, self.FLAG_PRAGMA) while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_PRAGMA) else if self[0]: self.skip() else if self.state == 1: while self[0] is not None: if self.paintJavaComment() == 1: return 1 else if self.state == 2: return self.paintTriple('"') return None bind(KotlinHighlighter) ================================================ FILE: base/usr/share/bim/syntax/javascript.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter def makeit(): let FLAG_NONE = Highlighter.FLAG_NONE let FLAG_KEYWORD = Highlighter.FLAG_KEYWORD let FLAG_STRING = Highlighter.FLAG_STRING let FLAG_COMMENT = Highlighter.FLAG_COMMENT let FLAG_TYPE = Highlighter.FLAG_TYPE let FLAG_PRAGMA = Highlighter.FLAG_PRAGMA let FLAG_NUMERAL = Highlighter.FLAG_NUMERAL let FLAG_ERROR = Highlighter.FLAG_ERROR let FLAG_DIFFPLUS = Highlighter.FLAG_DIFFPLUS let FLAG_DIFFMINUS = Highlighter.FLAG_DIFFMINUS let FLAG_NOTICE = Highlighter.FLAG_NOTICE let FLAG_BOLD = Highlighter.FLAG_BOLD let FLAG_LINK = Highlighter.FLAG_LINK let FLAG_ESCAPE = Highlighter.FLAG_ESCAPE let FLAG_EXTRA = Highlighter.FLAG_EXTRA let FLAG_SPECIAL = Highlighter.FLAG_SPECIAL let FLAG_UNDERLINE = Highlighter.FLAG_UNDERLINE class JavascriptHighlighter(Highlighter): name = 'javascript' extensions = ('.js','.jsx','.ts','.tsx') keywords = [ "abstract","arguments","from", "await","break","case","catch","class","const", "continue","debugger","default","delete","do","else","enum","eval", "export","extends","final","finally","for","function","goto", "if","implements","import","in","instanceof","interface","let","long", "native","new","package","private","protected","public","return", "static","super","switch","synchronized","this","throw","throws", "transient","true","try","typeof","volatile","while","with","yield", ] types = [ "int","float","double","short","var","void","byte","char","boolean", ] special = ['true','false','null'] def paintJSFormatString(): while self[0]: if '\\' in self and self[1] == '`': self[2] = FLAG_ESCAPE else if '`' in self: self[1] = FLAG_STRING return 0 else if '\\' in self: self[2] = FLAG_ESCAPE else if '$' in self and self[1] == '{': self[2] = FLAG_NUMERAL while self[0] and self[0] != '}': self[1] = FLAG_NUMERAL self[1] = FLAG_NUMERAL else: self[1] = FLAG_STRING return 1 def calculate(): if self.state < 1: while None not in self: if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): CHighlighter.paintCNumeral(self) else if not self.cKeywordQualifier(self[-1]) and self[0] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ': while self[0] and self.cKeywordQualifier(self[0]): self[1] = FLAG_TYPE else if '/' in self and self[1] == '/': self.paintComment() else if '/' in self and self[1] == '*': if CHighlighter.paintCComment(self) == 1: return 1 else if self.findKeywords(self.keywords, FLAG_KEYWORD, self.cKeywordQualifier): continue else if self.findKeywords(self.types, FLAG_TYPE, self.cKeywordQualifier): continue else if self.findKeywords(self.special, FLAG_NUMERAL, self.cKeywordQualifier): continue else if '=' in self and self[1] == '>': self[2] = FLAG_PRAGMA else if ':' in self and self.cKeywordQualifier(self[-1]): let start = self.i while self[-1] and self.cKeywordQualifier(self[-1]): self.rewind(1) while self[0] and self.i != start: self[1] = FLAG_TYPE self[1] = FLAG_PRAGMA else if '<' in self: self[1] = FLAG_TYPE while self[0] and ('/' in self or self.cKeywordQualifier(self[0])): self[1] = FLAG_TYPE else if '>' in self: self[1] = FLAG_TYPE else if '"' in self: self.paintSimpleString() else if "'" in self: self.paintSingleString() else if '`' in self: self[1] = FLAG_STRING if self.paintJSFormatString(): return 2 else if self[0]: self.skip() else if self.state == 1: if CHighlighter.paintCComment(self) == 1: return 1 return 0 else if self.state == 2: if self.paintJSFormatString(): return 2 return 0 return None return JavascriptHighlighter let JavascriptHighlighter = makeit() bind(JavascriptHighlighter) ================================================ FILE: base/usr/share/bim/syntax/json.krk ================================================ from syntax import Highlighter, bind class JsonHighlighter(Highlighter): name = 'json' extensions = ('.json',) keywords = ['true','false','null'] def calculate(): while self[0]: if self[0] == '"': let start = self.i self.paintSimpleString() let end = self.i while self[0] == ' ': self.skip() if self[0] == ':': self.rewind(end-start) self.paint(1, self.FLAG_ESCAPE) while self.i < end-1: self.paint(1, self.FLAG_KEYWORD) if self[0] == '"': self.paint(1, self.FLAG_ESCAPE) return 0 else if self[0] == '-' or self.isdigit(self[0]): if self[0] == '-': self.paint(1, self.FLAG_NUMERAL) if self[0] == '0': self.paint(1, self.FLAG_NUMERAL) else: while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) if self[0] == '.': self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) if self[0] == 'e' or self[0] == 'E': self.paint(1, self.FLAG_NUMERAL) if self[0] == '+' or self[0] == '-': self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) else if self.findKeywords(self.keywords, self.FLAG_NUMERAL, self.cKeywordQualifier): continue else: self.skip() return 0 return None bind(JsonHighlighter) ================================================ FILE: base/usr/share/bim/syntax/krk.krk ================================================ import os from bim import getCommands from syntax import Highlighter, bind from syntax.doxygen import tryDoxygenComment @bind class KrkHighlighter(Highlighter): name = 'krk' extensions = ('.krk',) spaces = True doxygenDocstrings = False enableChecking = False checkKrkCode = None keywords = [ 'and','class','def','else','for','if','in','import','let','not', 'or','return','while','try','except','raise','continue','break','as','from', 'elif', 'lambda', 'pass', 'with', 'is', 'del', 'assert', 'yield', 'finally', 'async', 'await', ] types = [ 'self','super','len','str','int','float','dir','repr','list','dict','range', 'object','isinstance','type','print','tuple','bool','any','all', 'hex','ord','chr','bytes','set','getattr','setattr','input','zip','enumerate', 'property','staticmethod','classmethod','filter','min','max','id','map','bin', 'sum','sorted','issubclass','hasattr','delattr', 'NotImplemented', 'abs', ] special = [ 'True', 'False', 'None' ] exceptions = [ 'Exception', 'TypeError', 'ArgumentError', 'IndexError', 'KeyError', 'AttributeError', 'NameError', 'ImportError', 'IOError', 'ValueError', 'KeyboardInterrupt', 'ZeroDivisionError', 'NotImplementedError', 'SyntaxError', 'AssertionError', ] def paintString(self, strType, isTriple, isFormat=False): while self[0] != None: if self[0] == '\\' and self[1] == strType: self.paint(2, self.FLAG_ESCAPE) else if self[0] == strType: self.paint(1, self.FLAG_STRING) if isTriple: if self[0] == strType and self[1] == strType: self.paint(2, self.FLAG_STRING) return 0 else: return 0 else if self[0] == '\\': if self[1] == 'x': self.paint(4, self.FLAG_ESCAPE) else if self[1] == 'u': self.paint(6, self.FLAG_ESCAPE) else if self[1] == 'U': self.paint(10, self.FLAG_ESCAPE) else if self[1] == None and not isTriple: self.paint(1, self.FLAG_ESCAPE) return 3 if strType == '"' else (14 if isFormat else 4) else if self[1] in '\\\'"abfnrtv[': self.paint(2, self.FLAG_ESCAPE) else: self.paint(1, self.FLAG_STRING) else if isFormat and self[0] == '{': if self[1] == '}': self.paint(-1, self.FLAG_ERROR) return None self.paint(1, self.FLAG_ESCAPE) let x = 0 while self[0]: if self[0] == '{': x++ else if self[0] == '}': if x == 0: self.paint(1, self.FLAG_ESCAPE) break x-- else if self[0] == strType and not isTriple: self.paint(-1, self.FLAG_ERROR) return None else if self.findKeywords(self.keywords, self.FLAG_ESCAPE, self.cKeywordQualifier): continue else if self[-1] != '.' and self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): continue else if self.findKeywords(self.exceptions, self.FLAG_PRAGMA, self.cKeywordQualifier): continue self.paint(1, self.FLAG_NUMERAL) else if self.doxygenDocstrings and isTriple and tryDoxygenComment(self, self.FLAG_STRING): continue else: self.paint(1, self.FLAG_STRING) if isTriple: return int(strType == "'") + (11 if isFormat else 1) return 0 def paintNumeral(self): if self[0] == '0' and (self[1] == 'x' or self[1] == 'X'): self.paint(2, self.FLAG_NUMERAL) while self.isxdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) else if self[0] == '0' and (self[1] == 'o' or self[1] == 'O'): self.paint(2, self.FLAG_NUMERAL) while self[0] in '01234567': self.paint(1, self.FLAG_NUMERAL) else if self[0] == '0' and (self[1] == 'b' or self[1] == 'B'): self.paint(2, self.FLAG_NUMERAL) while self[0] == '0' or self[0] == '1': self.paint(1, self.FLAG_NUMERAL) else: while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) if self[0] == '.' and self.isdigit(self[1]): self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]): self.paint(1, self.FLAG_NUMERAL) return 0 def paintDoxyComment(self): while self[0] is not None: if tryDoxygenComment(self): continue else if self.commentBuzzwords(): continue else: self.paint(1, self.FLAG_COMMENT) def calculate(self): if self.enableChecking and self.i == 0: self.checkKrkCode() if self.state <= 0: if self[0] == '#': self.paintDoxyComment() else if self[0] == '@': self.paint(1, self.FLAG_TYPE) while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_TYPE) return 0 else if self[0] == '"' or self[0] == "'": let strType = self[0] if self[-1] == 'f': self.rewind(1) self.paint(1, self.FLAG_KEYWORD) if self[1] == strType and self[2] == strType: self.paint(3, self.FLAG_STRING) return self.paintString(strType,True,self[-4]=='f') self.paint(1, self.FLAG_STRING) return self.paintString(strType,False,self[-2]=='f') else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self[-1] != '.' and self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): return 0 else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): return 0 else if self.findKeywords(self.exceptions, self.FLAG_PRAGMA, self.cKeywordQualifier): return 0 else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): self.paintNumeral() return 0 else if self[0] != None: self.skip() return 0 else if self.state >= 1: return self.paintString("'\""[self.state % 2], (self.state % 10) <= 2, self.state > 10) return None @bind class BimcmdHighlighter(KrkHighlighter): name = 'bimcmd' extensions = ('.bim3rc',) file_commands = ['e','tabnew','split','w','runscript','rundir'] def calculate(self): if self.state <= 0 and self[-1] != '.': if self.i == 0: if self.findKeywords(self.file_commands, self.FLAG_KEYWORD, self.cKeywordQualifier): if self[0] == ' ': while self[0] == ' ': self.skip() let filePath = self[0:] if filePath: if filePath.startswith('~/'): filePath = filePath.replace('~',os.environ.get('HOME','~'),1) try: os.stat(filePath) self.paint(-1, self.FLAG_DIFFPLUS) except: self.paint(-1, self.FLAG_DIFFMINUS) return -1 if self.findKeywords(getCommands(), self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 return super().calculate() ================================================ FILE: base/usr/share/bim/syntax/latex.krk ================================================ from syntax import Highlighter, bind class LatexHighlighter(Highlighter): name = "latex" extensions = ('.tex',) spaces = True def calculate(self): while self[0]: if self[0] == '%': self.paintComment() return None else if self[0] == '\\': if self.isalpha(self[1]): self.paint(2, self.FLAG_KEYWORD) while self.isalpha(self[0]): self.paint(1, self.FLAG_KEYWORD) else: self.skip() else: self.skip() return None bind(LatexHighlighter) ================================================ FILE: base/usr/share/bim/syntax/ld.krk ================================================ from syntax import Highlighter, bind class LinkerScriptHighlighter(Highlighter): name = "ld" extensions = ('.ld',) spaces = False keywords = [ 'INCLUDE','INPUT','GROUP','AS_NEEDED','OUTPUT','SEARCH_DIR', 'STARTUP','TARGET','REGION_ALIAS','ASSERT', 'EXTERN','FORCE_COMMON_ALLOCATION','INHIBIT_COMMON_ALLOCATION', 'FORCE_GROUP_ALLOCATION','INSERT','AFTER','BEFORE', 'NOCROSSREFS','NOCROSSREFS_TO','OUTPUT_ARCH','LD_FEATURE', 'HIDDEN','PROVIDE','PROVIDE_HIDDEN','SECTIONS','EXCLUDE_FILE', 'SORT_BY_NAME','SORT_BY_ALIGNMENT','KEEP','ENTRY','BLOCK','ALIGN', 'COMMON','DISCARD' # Special: # 'OUTPUT_FORMAT', ] types = [ 'BYTE','LONG','SHORT','QUAD' ] bfd_names = [ 'elf64-x86-64','elf32-i386', ] def paintCComment(self): let last = None while self[0] != None: if self.commentBuzzwords(): continue else if last == '*' and self[0] == '/': self.paint(1, self.FLAG_COMMENT) return 0 else: last = self[0] self.paint(1, self.FLAG_COMMENT) return 1 def whitespaceThen(self, c): while self[0] == ' ': self.skip() return self[0] == c def bfdName(self, c): return self.cKeywordQualifier(c) or c == '-' def calculate(self): let cond = self.state if cond <= 0: while self[0] is not None: if self[0] == '/' and self[1] == '*': self.paint(2, self.FLAG_COMMENT) return self.paintCComment() else if self.matchAndPaint("OUTPUT_FORMAT", self.FLAG_KEYWORD, self.cKeywordQualifier): # Expect an argument if not self.whitespaceThen('('): self.paint(-1, self.FLAG_ERROR) return None self.skip(1) # A BFD name if not self.findKeywords(self.bfd_names, self.FLAG_TYPE, self.bfdName): while self[0] is not None and self[0] != ',' and self[0] != ')': self.skip() if not (self.whitespaceThen(',') or self.whitespaceThen(')')): self.paint(-1, self.FLAG_ERROR) return None if self[0] == ')': self.skip() continue else: self.skip() # comma if not self.findKeywords(self.bfd_names, self.FLAG_TYPE, self.bfdName): while self[0] is not None and self[0] != ',' and self[0] != ')': self.skip() if not self.whitespaceThen(','): self.paint(-1, self.FLAG_ERROR) return None self.skip() # comma if not self.findKeywords(self.bfd_names, self.FLAG_TYPE, self.bfdName): while self[0] is not None and self[0] != ',' and self[0] != ')': self.skip() if not self.whitespaceThen(')'): self.paint(-1, self.FLAG_ERROR) return None self.skip() # ')' else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): return 0 else if self[0] == '.' and self.cKeywordQualifier(self[1]): self.paint(2,self.FLAG_TYPE) while self.cKeywordQualifier(self[0]) or self[0] == '-': self.paint(1,self.FLAG_TYPE) else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): if self[0] == '0' and self[1] == 'x': self.paint(2,self.FLAG_NUMERAL) while self[0] in '0123456789abcdefABCDEF': self.paint(1,self.FLAG_NUMERAL) else if self[0] == '0': self.paint(1,self.FLAG_NUMERAL) while self[0] in '01234567': self.paint(1,self.FLAG_NUMERAL) else: self.paint(1,self.FLAG_NUMERAL) while self.isdigit(self[0]): self.paint(1,self.FLAG_NUMERAL) if self[0] in 'kKM': self.paint(1, self.FLAG_NUMERAL) else if self[0] is not None: self.skip() else if cond == 1: return self.paintCComment() return None bind(LinkerScriptHighlighter) ================================================ FILE: base/usr/share/bim/syntax/lisp.krk ================================================ from syntax import Highlighter, bind class LispHighlighter(Highlighter): name = 'lisp' extensions = ('.lisp','.lsp','.cl') parens = [ Highlighter.FLAG_DIFFPLUS, Highlighter.FLAG_TYPE, Highlighter.FLAG_PRAGMA, Highlighter.FLAG_KEYWORD, ] def parenFromState(i): return self.parens[i % len(self.parens)] def calculate(): if self.state == -1: self.state = 0 while self[0]: if self[0] == ';': self.paintComment() else if self[0] == '(': self.paint(1, self.parenFromState(self.state)) self << self.state + 1 while self[0] and self[0] not in ' ()': self.paint(1, self.FLAG_KEYWORD) else if self[0] == ')': if self.state == 0: self.paint(1, self.FLAG_ERROR) else: self << self.state - 1 self.paint(1, self.parenFromState(self.state)) else if self[0] == ':': while self[0] and self[0] not in ' ()': self.paint(1, self.FLAG_PRAGMA) else if self[0] == '"': self.paintSimpleString() else if self[0]: self.skip() if self.state == 0: return None if not self[0]: return self.state return None bind(LispHighlighter) ================================================ FILE: base/usr/share/bim/syntax/lua.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter class LuaHighlighter(Highlighter): name = 'lua' extensions = ('.lua',) keywords = [ 'and','break','do','else','elseif', 'end','for','function','if', 'in','local','not','or', 'repeat','return','then','until','while' ] constants = [ 'false','nil','true' ] def calculate(): if self.state <= 0: if self[0] == '-' and self[1] == '-': self.paintComment() return None else if self[0] == '"': self.paintSimpleString() return 0 else if self[0] == "'": self.paintSingleString() return 0 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self.findKeywords(self.constants, self.FLAG_NUMERAL, self.cKeywordQualifier): return 0 else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): CHighlighter.paintCNumeral(self) return 0 else if self[0]: self.skip() return 0 return None return None bind(LuaHighlighter) ================================================ FILE: base/usr/share/bim/syntax/make.krk ================================================ from syntax import Highlighter, bind class GnumakeHighlighter(Highlighter): name = 'make' extensions = ('Makefile','makefile','GNUmakefile','.mak','.mk') commands = [ "define","endef","undefine","ifdef","ifndef","ifeq","ifneq","else","endif", "include","sinclude","override","export","unexport","private","vpath", "-include", ] functions = [ "subst","patsubst","findstring","filter","filter-out", "sort","word","words","wordlist","firstword","lastword", "dir","notdir","suffix","basename","addsuffix","addprefix", "join","wildcard","realpath","abspath","error","warning", "shell","origin","flavor","foreach","if","or","and", "call","eval","file","value", ] targets = [ "all", # Not really special, but highlight it 'cause I feel like it. ".PHONY", ".SUFFIXES", ".DEFAULT", ".PRECIOUS", ".INTERMEDIATE", ".SECONDARY", ".SECONDEXPANSION", ".DELETE_ON_ERROR", ".IGNORE", ".LOW_RESOLUTION_TIME", ".SILENT", ".EXPORT_ALL_VARIABLES", ".NOTPARALLEL", ".ONESHELL", ".POSIX", ] def commandQualifier(c): if isinstance(c,int): c = chr(c) if c > 0 else '\x00' return self.isalnum(c) or c in '_-.' def makeCloseParen(): self.paint(2, self.FLAG_TYPE) self.findKeywords(self.functions, self.FLAG_KEYWORD, self.cKeywordQualifier) let i = 1 while self[0]: if self[0] == '(': i++ if self[-1] == '$': self.paint(1, self.FLAG_TYPE) self.findKeywords(self.functions, self.FLAG_KEYWORD, self.cKeywordQualifier) continue else if self[0] == ')': i-- if i == 0: self.paint(1, self.FLAG_TYPE) return 0 else if self[0] == '"': self.paintSimpleString() self.paint(1, self.FLAG_TYPE) return 0 def makeCloseBrace(): self.paint(2, self.FLAG_TYPE) while self[0]: if self[0] == '}': self.paint(1, self.FLAG_TYPE) return 0 self.paint(1, self.FLAG_TYPE) return 0 def makeVar(): if self[1] == '(': self.makeCloseParen() else if self[1] == '{': self.makeCloseBrace() else: self.paint(2, self.FLAG_TYPE) def paintString(): let strType = self[0] self.paint(1, self.FLAG_STRING) while self[0]: if self[0] == strType and self[-1] != '\\': self.paint(1, self.FLAG_STRING) return else if self[0] == '\\' and (self[1] == strType or strType == '"'): self.paint(2, self.FLAG_ESCAPE) else if self[0] == '$': self.makeVar() else: self.paint(1, self.FLAG_STRING) def variableOrComment(flag, next): while self[0]: if self[0] == '$': self.makeVar() else if self[0] == '#': self.paintComment() else if self[0] == "'": self.paintString() else if self[0] == '"': self.paintString() else if self[0] == '\\' and not self[1]: return next else: self.paint(1, flag) return 0 def calculate(): if self.state == 2: return self.variableOrComment(self.FLAG_NUMERAL, 2) else if self.state == 3: return self.variableOrComment(self.FLAG_NONE, 3) if self.i == 0 and self[0] == '\t': self.skip() if self[0] == '@': self.paint(1, self.FLAG_KEYWORD) return self.variableOrComment(self.FLAG_NUMERAL, 2) else: while self[0] == ' ': self.skip() let whatisit for i = 0; self[i]; i++: if self[i] == ':' and self[i+1] != '=': whatisit = 1 break else if self[i] == '=': whatisit = 2 break else if self[i] == '#': break if not whatisit: while self[0]: if self[0] == '#': self.paintComment() else if self.findKeywords(self.commands, self.FLAG_KEYWORD, self.commandQualifier): continue else if self[0] == '$': self.variableOrComment(self.FLAG_NONE, 3) else: self.skip() else if whatisit == 1: # rule while self[0]: if self[0] == '#': self.paintComment() else if self[0] == ':': self.paint(1, self.FLAG_TYPE) self.variableOrComment(self.FLAG_NONE, 3) else if self.findKeywords(self.targets, self.FLAG_KEYWORD, self.commandQualifier): continue else: self.paint(1, self.FLAG_TYPE) else if whatisit == 2: # variable self.matchAndPaint('export', self.FLAG_KEYWORD, self.cKeywordQualifier) while self[0] and self[0] not in '+=:?': self.paint(1, self.FLAG_TYPE) while self[0] and self[0] != '=': self.skip() return self.variableOrComment(self.FLAG_NONE, 3) return None bind(GnumakeHighlighter) ================================================ FILE: base/usr/share/bim/syntax/man.krk ================================================ from syntax import Highlighter, bind class ManpageHighlighter(Highlighter): name = 'man' extensions = ('.1','.2','.3','.4','.5','.6','.7','.8') def calculate(): while self[0]: if self.i == 0 and self[0] == '.': if self[1] == 'S' and self[2] == 'H' and self[3] == ' ': self.paint(3, self.FLAG_KEYWORD) self.paint(-1, self.FLAG_STRING) else if self[1] == 'B' and self[2] == ' ': self.paint(2, self.FLAG_KEYWORD) self.paint(-1, self.FLAG_BOLD) else if self.isalpha(self[1]): self.paint(1, self.FLAG_KEYWORD) while self[0] and self.isalpha(self[0]): self.paint(1, self.FLAG_KEYWORD) else if self[1] == '\\' and self[2] == '"': self.paint(1, self.FLAG_COMMENT) else: self.skip() else if self[0] == '\\': if self[1] == 'f': self.paint(2, self.FLAG_NUMERAL) self.paint(1, self.FLAG_PRAGMA) else: self.paint(2, self.FLAG_ESCAPE) else: self.skip() return None bind(ManpageHighlighter) ================================================ FILE: base/usr/share/bim/syntax/markdown.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter from syntax.py import PythonHighlighter from syntax.krk import KrkHighlighter from syntax.java import JavaHighlighter from syntax.json import JsonHighlighter from syntax.xml import XMLHighlighter, HTMLHighlighter from syntax.diff import DiffHighlighter from syntax.bash import BashHighlighter from syntax.make import GnumakeHighlighter class MarkdownHighlighter(Highlighter): name = 'markdown' extensions = ('.md',) nestables = [ ('c', 100, CHighlighter), ('py', 200, PythonHighlighter), ('java', 300, JavaHighlighter), ('json', 400, JsonHighlighter), ('xml', 500, XMLHighlighter), ('html', 600, HTMLHighlighter), ('make', 700, GnumakeHighlighter), ('diff', 800, DiffHighlighter), ('bash', 900, BashHighlighter), ('krk', 1000, KrkHighlighter), ] def nest(name, state, highlighter): self << (0 if self.state < 1 else self.state - state) let sub = highlighter(self) while True: let next = sub.calculate() if next is None: sub << -1 break sub << next if sub.state != 0: break if not sub.state or sub.state == -1: return state return sub.state + state def calculate(): if self.state < 1: if self.i == 0 and self[0] == '#': while self[0] == '#': self.paint(1, self.FLAG_KEYWORD) self.paint(-1, self.FLAG_BOLD) return None else if self.i == 0: while self[0] == ' ': self.skip() if self[0] == '`' and self[1] == '`' and self[2] == '`': self.paint(3, self.FLAG_STRING) for k,s,h in self.nestables: if self.matchPrefix(k): self.paint(len(k), self.FLAG_NUMERAL) return s return 1 if self[0] == '`': self.paint(1, self.FLAG_STRING) while self[0]: if self[0] == '`': self.paint(1, self.FLAG_STRING) return 0 self.paint(1, self.FLAG_STRING) return 0 else if self[0] == '[': self.skip() while self[0] and self[0] != ']': self.paint(1, self.FLAG_LINK) if self[0] == ']': self.skip() if self[0] == '(': self.skip() while self[0] and self[0] != ')': self.paint(1, self.FLAG_NUMERAL) return 0 else if self[0] == '*' and self[1] == '*' and self.isalnum(self[2]) and not self.isalnum(self[-1]): self.paint(2, self.FLAG_BOLD) while self[0] and not (self[0] == '*' and self[1] == '*'): self.paint(1, self.FLAG_BOLD) if self[0] == '*' and self[1] == '*': self.paint(2, self.FLAG_BOLD) return 0 else if self[0] == '*' and self.isalnum(self[1]) and not self.isalnum(self[-1]): self.paint(1, self.FLAG_COMMENT) # uh while self[0] and self[0] != '*': self.paint(1, self.FLAG_COMMENT) if self[0] == '*': self.paint(1, self.FLAG_COMMENT) return 0 else if self[0] == '_' and self.isalnum(self[1]) and not self.isalnum(self[-1]): self.paint(1, self.FLAG_COMMENT) while self[0] and self[0] != '_': self.paint(1, self.FLAG_COMMENT) if self[0] == '_': self.paint(1, self.FLAG_COMMENT) return 0 else if self[0]: self.skip() return 0 return None else if self.state >= 1: if self.i == 0: for j=0;self[j];j++: if self[j] == '`' and self[j+1] == '`' and self[j+2] == '`': self.paint(j, self.FLAG_NONE) self.paint(3, self.FLAG_STRING) return None if self.state == 1: self.paint(-1, self.FLAG_STRING) return 1 else: for k,state,highlighter in self.nestables: if self.state < state + 99: return self.nest(k, state, highlighter) return None bind(MarkdownHighlighter) ================================================ FILE: base/usr/share/bim/syntax/protobuf.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter class ProtobufHighlighter(Highlighter): name = 'protobuf' extensions = ('.proto',) keywords = [ "syntax","import","option","package","message","group","oneof", "optional","required","repeated","default","extend","extensions","to","max","reserved", "service","rpc","returns","stream", ] types = [ "int32","int64","uint32","uint64","sint32","sint64", "fixed32","fixed64","sfixed32","sfixed64", "float","double","bool","string","bytes", "enum", ] special = ['true','false'] def calculate(): if self.state < 1: if self[0] == '/' and self[1] == '/': self.paintComment() else if self[0] == '/' and self[1] == '*': if CHighlighter.paintCComment(self) == 1: return 1 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): return 0 else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): return 0 else if self[0] == '"': self.paintSimpleString() return 0 else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): CHighlighter.paintCNumeral(self) return 0 else if self[0]: self.skip() return 0 return None else: if CHighlighter.paintCComment(self) == 1: return 1 return 0 bind(ProtobufHighlighter) ================================================ FILE: base/usr/share/bim/syntax/py.krk ================================================ from syntax import Highlighter, bind class PythonHighlighter(Highlighter): name = 'python' extensions = ('.py',) spaces = True keywords = [ "class","def","return","del","if","else","elif","for","while","continue", "break","assert","as","and","or","except","finally","from","global", "import","in","is","lambda","with","nonlocal","not","pass","raise","try","yield", "async","await", ] types = [ "abs","all","any","ascii","bin","bool","breakpoint","bytes", "bytearray","callable","compile","complex","delattr","chr", "dict","dir","divmod","enumerate","eval","exec","filter","float", "format","frozenset","getattr","globals","hasattr","hash","help", "hex","id","input","int","isinstance","issubclass","iter","len", "list","locals","map","max","memoryview","min","next","object", "oct","open","ord","pow","print","property","range","repr","reverse", "round","set","setattr","slice","sorted","staticmethod","str","sum", "super","tuple","type","vars","zip", 'self', ] special = ['True','False','None'] def paintPyTriple(quote): while self[0]: if self[0] == quote: self.paint(1, self.FLAG_STRING) if self[0] == quote and self[1] == quote: self.paint(2, self.FLAG_STRING) return 0 else: self.paint(1, self.FLAG_STRING) return 1 if quote == '"' else 2 def paintPyString(quote): self.paint(1, self.FLAG_STRING) while self[0]: if self[0] == '\\' and self[1] == quote: self.paint(2, self.FLAG_ESCAPE) else if self[0] == quote: self.paint(1, self.FLAG_STRING) return 0 else if self[0] == '\\': self.paint(2, self.FLAG_ESCAPE) else: self.paint(1, self.FLAG_STRING) return 0 def paintPyNumeral(): if self[0] == '0' and self[1] == 'x' or self[1] == 'X': self.paint(2, self.FLAG_NUMERAL) while self.isxdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) else if self[0] == '0' and self[1] == '.': self.paint(2, self.FLAG_NUMERAL) while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) if (self[0] == '+' or self[0] == '-') and (self[1] == 'e' or self[1] == 'E'): self.paint(2, self.FLAG_NUMERAL) while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) else if self[0] == 'e' or self[0] == 'E': self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) if self[0] == 'j': self.paint(1, self.FLAG_NUMERAL) return 0 else: while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) if self[0] == '.': self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) if (self[0] == '+' or self[0] == '-') and (self[1] == 'e' or self[1] == 'E'): self.paint(2, self.FLAG_NUMERAL) while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) else if self[0] == 'e' or self[0] == 'E': self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) if self[0] == 'j': self.paint(1, self.FLAG_NUMERAL) return 0 while self[0] == 'l' or self[0] == 'L': self.paint(1, self.FLAG_NUMERAL) return 0 def paintPyFString(quote): self.paint(1, self.FLAG_STRING) while self[0]: if self[0] == '\\' and self[1] == quote: self.paint(2, self.FLAG_ESCAPE) else if self[0] == quote: self.paint(1, self.FLAG_STRING) return else if self[0] == '\\': self.paint(2, self.FLAG_ESCAPE) else if self[0] == '{': self.paint(1, self.FLAG_NUMERAL) if self[0] == '}': self.rewind(1) self.paint(2, self.FLAG_ERROR) else: while self[0] and self[0] != '}': self.paint(1, self.FLAG_NUMERAL) self.paint(1, self.FLAG_NUMERAL) else: self.paint(1, self.FLAG_STRING) def calculate(): if self.state < 1: if self[0] == '#': self.paintComment() else if self.i == 0 and self.matchAndPaint('import', self.FLAG_PRAGMA, self.cKeywordQualifier): return 0 else if self[0] == '@': self.paint(1, self.FLAG_PRAGMA) while self.cKeywordQualifier(self[0]): self.paint(1, self.FLAG_PRAGMA) return 0 else if self[0] == '"': if self[1] == '"' and self[2] == '"': self.paint(3, self.FLAG_STRING) return self.paintPyTriple('"') else if self[-1] == 'f': self.rewind(1) self.paint(1, self.FLAG_TYPE) self.paintPyFString('"') return 0 else: self.paintPyString('"') return 0 else if self[0] == "'": if self[1] == "'" and self[2] == "'": self.paint(3, self.FLAG_STRING) return self.paintPyTriple("'") else if self[-1] == 'f': self.rewind(1) self.paint(1, self.FLAG_TYPE) self.paintPyFString("'") return 0 else: self.paintPyString("'") return 0 else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): return 0 else if self.findKeywords(self.special, self.FLAG_NUMERAL, self.cKeywordQualifier): return 0 else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): self.paintPyNumeral() return 0 else if self[0]: self.skip() return 0 else if self.state == 1: return self.paintPyTriple('"') else if self.state == 2: return self.paintPyTriple("'") return None bind(PythonHighlighter) ================================================ FILE: base/usr/share/bim/syntax/rust.krk ================================================ from syntax import Highlighter, bind from syntax.c import CHighlighter @bind class RustHighlighter(Highlighter): name = 'rust' extensions = ('.rs',) keywords = [ "as","break","const","continue","crate","else","enum","extern", "false","fn","for","if","impl","in","let","loop","match","mod", "move","mut","pub","ref","return","Self","self","static","struct", "super","trait","true","type","unsafe","use","where","while", ] types = [ "bool","char","str", "i8","i16","i32","i64", "u8","u16","u32","u64", "isize","usize", "f32","f64", ] def paintRustComment(): while self[0]: if self.commentBuzzwords(): continue else if self[0] == '*' and self[1] == '/': self.paint(2, self.FLAG_COMMENT) self << self.state - 1 if self.state == 0: return 0 else if self[0] == '/' and self[1] == '*': self << self.state + 1 self.paint(2, self.FLAG_COMMENT) else: self.paint(1, self.FLAG_COMMENT) return self.state def paintRustNumeral(): if self[0] == '0' and self[1] == 'b': self.paint(2, self.FLAG_NUMERAL) while self[0] == '0' or self[0] == '1' or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) else if self[0] == '0' and self[1] == 'o': self.paint(2, self.FLAG_NUMERAL) while (ord(self[0]) >= ord('0') and ord(self[0]) < ord('7')) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) else if self[0] == '0' and self[1] == 'x': self.paint(2, self.FLAG_NUMERAL) while self.isxdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) else if self[0] == '0' and self[1] == '.': self.paint(2, self.FLAG_NUMERAL) while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) else: while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) if self[0] == '.': self.paint(1, self.FLAG_NUMERAL) while self.isdigit(self[0]) or self[0] == '_': self.paint(1, self.FLAG_NUMERAL) return 0 def calculate(): if self.state == -1 or self.state == 0: if self[0] == '/' and self[1] == '/': self.paintComment() else if self[0] == '/' and self[1] == '*': self.paint(2, self.FLAG_COMMENT) self.state = 1 return self.paintRustComment() else if self.findKeywords(self.keywords, self.FLAG_KEYWORD, self.cKeywordQualifier): return 0 else if self.findKeywords(self.types, self.FLAG_TYPE, self.cKeywordQualifier): return 0 else if self[0] == '\"': self.paintSimpleString() return 0 else if self[0] == '\'' and self[1] == 's' and self[2] == 't': self.paint(1, self.FLAG_TYPE) return 0 else if self[0] == '\'': CHighlighter.paintCChar(self) return 0 else if not self.cKeywordQualifier(self[-1]) and self.isdigit(self[0]): self.paintRustNumeral() return 0 else if self[0]: self.skip() return 0 else: return self.paintRustComment() ================================================ FILE: base/usr/share/bim/syntax/xml.krk ================================================ from syntax import Highlighter, bind from syntax.css import CSSHighlighter class XMLHighlighter(Highlighter): name = 'xml' extensions = ('.xml','.iml') def paintXmlComment(): while self[0]: if self[0] == '-' and self[1] == '-' and self[2] == '>': self.paint(3, self.FLAG_COMMENT) return 0 else: if self.commentBuzzwords(): continue self.paint(1, self.FLAG_COMMENT) return 4 def paintTagString(): if self[0] == '"': self.paint(1, self.FLAG_STRING) return 2 else: self.paintSimpleString() if not self[0] and self[-1] != '"': return 3 return None def paintInsideTag(): while self[0]: if self[0] == '>': self.paint(1, self.FLAG_TYPE) return 0 else if self[0] == '"': self.paintSimpleString() if not self[0] and self[-1] != '"': return 3 else: self.paint(1, self.FLAG_TYPE) return 2 def paintTag(): while self[0]: if self[0] == '/': self.paint(1, self.FLAG_TYPE) if self[0] == '?': self.paint(1, self.FLAG_TYPE) if self[0] == ' ' or self[0] == '\t': self.skip() if self.isalnum(self[0]): while self.isalnum(self[0]) or self[0] == '-': self.paint(1, self.FLAG_KEYWORD) if not self[0]: return 2 return self.paintInsideTag() else: self.paint(1, self.FLAG_TYPE) return None def calculate(): if self.state < 1: while self[0] and self[0] != '<': self.skip() if not self[0]: return None if self[0] == '<' and self[1] == '!' and self[2] == '-' and self[3] == '-': self.paint(4, self.FLAG_COMMENT) return self.paintXmlComment() self.paint(1, self.FLAG_TYPE) return self.paintTag() else if self.state == 1: return self.paintTag() else if self.state == 2: return self.paintInsideTag() else if self.state == 3: return self.paintTagString() else if self.state == 4: return self.paintXmlComment() return None bind(XMLHighlighter) ''' General proof of concept for an HTML highlighter that does nested CSS. Could be extended to do nested JS, probably. Doesn't actually do either right now, but demonstrates how it could be done. ''' class HTMLHighlighter(XMLHighlighter): name = 'html' extensions = ('.html','.htm') def paintTag(): if self.matchAndPaint('style', self.FLAG_KEYWORD, self.cKeywordQualifier): return self.paintInsideStyleTag() return super().paintTag() def paintInsideStyleTag(): let r = self.paintInsideTag() if r == 2: return 5 else: return self.paintCss() def paintCss(): while self[0] and not (self[0] == '<' and self[1] == '/' and self[2] == 's'): self << 0 let css = CSSHighlighter(self) let result = css.calculate() if result == 0: self.__init__(css) continue else if result == 1: return 7 else if result == None: return 6 if (self[0] == '<' and self[1] == '/' and self[2] == 's'): return self.paintTag() return 6 def calculate(): if self.state < 5: return super().calculate() else if self.state == 5: return self.paintInsideStyleTag() else if self.state == 6: return self.paintCss() else if self.state == 7: self << 1 let css = CSSHighlighter(self) if css.calculate() == 1: return 7 self.__init__(css) return 6 bind(HTMLHighlighter) ================================================ FILE: base/usr/share/bim/themes/__init__.krk ================================================ ================================================ FILE: base/usr/share/bim/themes/ansi.krk ================================================ from bim import defineTheme @defineTheme def ansi(): setcolor("text-fg","@7") setcolor("text-bg","@0") setcolor("alternate-fg","@5") setcolor("alternate-bg","@9") setcolor("number-fg","@3") setcolor("number-bg","@9") setcolor("status-fg","@7") setcolor("status-bg","@4") setcolor("status-alt","@4") setcolor("tabbar-bg","@4") setcolor("tab-bg","@4") setcolor("keyword","@4") setcolor("string","@2") setcolor("comment","@5") setcolor("type","@3") setcolor("pragma","@1") setcolor("numeral","@1") setcolor("error-fg","@7") setcolor("error-bg","@1") setcolor("search-fg","@0") setcolor("search-bg","@3") setcolor("select-bg","@7") setcolor("select-fg","@0") setcolor("red","@1") setcolor("green","@2") setcolor("bold","@7") setcolor("link","@4") setcolor("escape","@2") if checkprop("can_bright") == 0: # TODO These should return True/False, not 0 for success setcolor("text-fg","@17") setcolor("text-bg","@9") setcolor("alternate-fg","@10") setcolor("status-fg","@17") setcolor("status-alt","@14") setcolor("keyword","@14") setcolor("comment","@10") setcolor("error-fg","@17") setcolor("search-bg","@13") setcolor("select-bg","@17") setcolor("link","@14") setcolor("escape","@12") ================================================ FILE: base/usr/share/bim/themes/citylights.krk ================================================ from bim import defineTheme from themes import ansi @defineTheme def citylights(): if checkprop('can_24bit'): colorscheme("ansi") return setcolor("text-fg","2;151;178;198") setcolor("text-bg","2;29;37;44") setcolor("alternate-fg","2;45;55;65") setcolor("alternate-bg","2;33;42;50") setcolor("number-fg","2;71;89;103") setcolor("number-bg","2;37;47;56") setcolor("status-fg","2;151;178;198") setcolor("status-bg","2;53;67;78") setcolor("status-alt","2;116;144;166") setcolor("tabbar-bg","2;37;47;56") setcolor("tab-bg","2;29;37;44") setcolor("keyword","2;94;196;255") setcolor("string","2;83;154;252") setcolor("comment","2;107;133;153;3") setcolor("type","2;139;212;156") setcolor("pragma","2;0;139;148") setcolor("numeral","2;207;118;132") setcolor("error-fg","5;15") setcolor("error-bg","5;196") setcolor("search-fg","5;234") setcolor("search-bg","5;226") setcolor("select-fg","2;29;37;44") setcolor("select-bg","2;151;178;198") setcolor("red","2;222;53;53") setcolor("green","2;55;167;0") setcolor("bold","2;151;178;198;1") setcolor("link","2;94;196;255") setcolor("escape","2;133;182;249") ================================================ FILE: base/usr/share/bim/themes/light.krk ================================================ from bim import defineTheme from themes import ansi # A light color scheme # Based on selenized by Jan Warchoł @defineTheme def light(): if checkprop('can_24bit'): colorscheme("ansi") return setcolor("text-fg","2;57;76;82") setcolor("text-bg","2;250;242;218") setcolor("alternate-fg","2;144;153;149") setcolor("alternate-bg","2;236;227;204") setcolor("number-fg","2;57;76;82") setcolor("number-bg","2;212;204;181") setcolor("status-fg","2;82;102;109") setcolor("status-bg","2;212;204;181") setcolor("status-alt","2;129;61;19") setcolor("tab-bg","2;212;204;181") setcolor("tabbar-bg","2;236;227;204") setcolor("error-fg","5;15") setcolor("error-bg","5;196") setcolor("search-fg","5;234") setcolor("search-bg","5;226") setcolor("keyword","2;0;114;212;1") setcolor("string","2;72;145;0") setcolor("comment","2;144;153;149;3") setcolor("type","2;193;92;29") setcolor("pragma","2;210;33;45;1") setcolor("numeral","2;202;72;152;1") setcolor("select-fg","2;213;205;182") setcolor("select-bg","2;144;153;149") setcolor("red","2;222;53;53") setcolor("green","2;55;167;0") setcolor("bold","2;57;76;82;1") setcolor("link","2;0;114;212") setcolor("escape","2;0;156;143") ================================================ FILE: base/usr/share/bim/themes/solarized.krk ================================================ from bim import defineTheme from themes import ansi @defineTheme def solarized_dark(): if checkprop('can_24bit'): return ansi() setcolor("text-fg","2;147;161;161") setcolor("text-bg","2;0;43;54") setcolor("alternate-fg","2;147;161;161") setcolor("alternate-bg","2;7;54;66") setcolor("number-fg","2;131;148;149") setcolor("number-bg","2;7;54;66") setcolor("status-fg","2;131;148;150") setcolor("status-bg","2;7;54;66") setcolor("status-alt","2;133;153;0") setcolor("tabbar-bg","2;7;54;66") setcolor("tab-bg","2;131;148;150") setcolor("keyword","2;133;153;0") setcolor("string","2;42;161;152") setcolor("comment","2;101;123;131") setcolor("type","2;181;137;0") setcolor("pragma","2;203;75;22") setcolor("numeral","2;220;50;47") setcolor("error-fg","5;15") setcolor("error-bg","5;196") setcolor("search-fg","5;234") setcolor("search-bg","5;226") setcolor("select-fg","2;0;43;54") setcolor("select-bg","2;147;161;161") setcolor("red","2;222;53;53") setcolor("green","2;55;167;0") setcolor("bold","2;147;161;161;1") setcolor("link","2;42;161;152") setcolor("escape","2;133;153;0") ================================================ FILE: base/usr/share/bim/themes/strawberry.krk ================================================ ''' Based on 'strawberry' vim theme: @ref https://github.com/haystackandroid/strawberry ''' from bim import defineTheme from themes import ansi @defineTheme def strawberry(): if checkprop('can_24bit'): colorscheme("ansi") return setcolor("text-fg", "2;117;97;107") setcolor("text-bg", "2;255;240;247") setcolor("alternate-fg", "2;158;139;149") setcolor("alternate-bg", "2;252;238;244") setcolor("number-bg", "2;240;221;230") setcolor("number-fg", "2;158;139;149") setcolor("status-fg", "2;255;240;247") setcolor("status-bg", "2;224;162;177") setcolor("status-alt", "2;255;255;255;1") setcolor("tabbar-bg", "2;224;162;177") setcolor("tab-bg", "2;240;221;230") setcolor("keyword", "2;33;158;33;1") setcolor("string", "2;70;141;212") setcolor("comment", "2;158;139;149;3") setcolor("type", "2;212;106;132;1") setcolor("pragma", "2;162;111;191;1") setcolor("numeral", "2;70;141;212") setcolor("error-fg", "5;15") setcolor("error-bg", "5;196") setcolor("search-fg", "5;234") setcolor("search-bg", "2;224;162;177") setcolor("select-fg", "2;0;43;54") setcolor("select-bg", "2;147;161;161") setcolor("red", "2;222;53;53") setcolor("green", "2;55;167;0") setcolor("bold", "2;117;97;107;1") setcolor("link", "2;51;162;230;4") setcolor("escape", "2;113;203;173") ================================================ FILE: base/usr/share/bim/themes/sunsmoke.krk ================================================ from bim import defineTheme from themes import ansi @defineTheme def sunsmoke256(): if checkprop('can_256color'): colorscheme("ansi") return setcolor("text-fg","5;188") setcolor("text-bg","5;234") setcolor("alternate-fg","5;244") setcolor("alternate-bg","5;236") setcolor("number-fg","5;101") setcolor("number-bg","5;232") setcolor("status-fg","5;188") setcolor("status-bg","5;59") setcolor("status-alt","5;244") setcolor("tabbar-bg","5;59") setcolor("tab-bg","5;59") setcolor("keyword","5;74") setcolor("string","5;71") setcolor("comment","5;102") setcolor("type","5;221") setcolor("pragma","5;160") setcolor("numeral","5;161") setcolor("error-fg","5;15") setcolor("error-bg","5;196") setcolor("search-fg","5;234") setcolor("search-bg","5;226") setcolor("select-fg","5;17") setcolor("select-bg","5;109") setcolor("red","@1") setcolor("green","@2") setcolor("bold","5;188;1") setcolor("link","5;74;4") setcolor("escape","5;79") @defineTheme def sunsmoke(): if checkprop('can_24bit'): colorscheme("sunsmoke256") return setcolor("text-fg", "2;230;230;230") setcolor("text-bg", "2;31;31;31") setcolor("alternate-fg", "2;122;122;122") setcolor("alternate-bg", "2;46;43;46") setcolor("number-fg", "2;150;139;57") setcolor("number-bg", "2;0;0;0") setcolor("status-fg", "2;230;230;230") setcolor("status-bg", "2;71;64;58") setcolor("status-alt", "2;122;122;122") setcolor("tabbar-bg", "2;71;64;58") setcolor("tab-bg", "2;71;64;58") setcolor("keyword", "2;51;162;230") setcolor("string", "2;72;176;72") setcolor("comment", "2;158;153;129;3") setcolor("type", "2;230;206;110") setcolor("pragma", "2;194;70;54") setcolor("numeral", "2;230;43;127") setcolor("error-fg", "5;15") setcolor("error-bg", "5;196") setcolor("search-fg", "5;234") setcolor("search-bg", "5;226") setcolor("select-fg", "2;0;43;54") setcolor("select-bg", "2;147;161;161") setcolor("red", "2;222;53;53") setcolor("green", "2;55;167;0") setcolor("bold", "2;230;230;230;1") setcolor("link", "2;51;162;230;4") setcolor("escape", "2;113;203;173") ================================================ FILE: base/usr/share/bim/themes/tiff.krk ================================================ from bim import defineTheme from themes import ansi @defineTheme def tiff(): if checkprop('can_24bit'): colorscheme("ansi") return setcolor("text-fg", "2;255;255;255") setcolor("text-bg", "2;23;0;30") setcolor("alternate-fg", "2;36;22;67") setcolor("alternate-bg", "2;20;0;27") setcolor("number-fg", "2;156;152;178") setcolor("number-bg", "2;23;0;30") setcolor("status-fg", "2;255;255;255") setcolor("status-bg", "2;30;100;190") setcolor("status-alt", "2;230;230;230") setcolor("tabbar-bg", "2;32;0;44") setcolor("tab-bg", "2;53;28;62") setcolor("keyword", "2;254;222;93") setcolor("string", "2;105;206;96") setcolor("comment", "2;130;130;130;3") setcolor("type", "2;97;175;239") setcolor("pragma", "2;105;128;247") setcolor("numeral", "2;213;128;71") setcolor("error-fg", "5;15") setcolor("error-bg", "5;196") setcolor("search-fg", "5;234") setcolor("search-bg", "5;226") setcolor("select-fg", "2;0;43;54") setcolor("select-bg", "2;147;161;161") setcolor("red", "2;213;128;71") setcolor("green", "2;55;167;0") setcolor("bold", "2;230;230;230;1") setcolor("link", "2;51;162;230;4") setcolor("escape", "2;113;203;173") ================================================ FILE: base/usr/share/bim/themes/wombat.krk ================================================ from bim import defineTheme from themes import ansi # Wombat 256-color theme @defineTheme def wombat(): if checkprop('can_256color'): colorscheme("ansi") return setcolor("text-fg","5;230") setcolor("text-bg","5;235") setcolor("alternate-fg","5;244") setcolor("alternate-bg","5;236") setcolor("number-bg","5;232") setcolor("number-fg","5;101") setcolor("status-fg","5;230") setcolor("status-bg","5;238") setcolor("status-alt","5;186") setcolor("tabbar-bg","5;230") setcolor("tab-bg","5;248") setcolor("keyword","5;117") setcolor("string","5;113") setcolor("comment","5;102") setcolor("type","5;186") setcolor("pragma","5;173") setcolor("numeral","5;173") setcolor("error-fg","5;15") setcolor("error-bg","5;196") setcolor("search-fg","5;234") setcolor("search-bg","5;226") setcolor("select-fg","5;235") setcolor("select-bg","5;230") setcolor("red","@1") setcolor("green","@2") setcolor("bold","5;230;1") setcolor("link","5;117") setcolor("escape","5;194") if not checkprop('can_italic'): setcolor('comment','5;102;3') ================================================ FILE: base/usr/share/help/0_index.trt ================================================

Welcome!

Welcome to the Help Browser. This is a simple rich text document viewer. It is incomplete. To view the documentation for an application, select the "Contents" entry from the "Help" menu, when available. ================================================ FILE: base/usr/share/help/calculator.trt ================================================

Calculator

A simple calculator powered by Kuroko.
Use the keyboard or the provided buttons to enter calculations as Kuroko expressions.
Single-line statements such as imports are also supported. For more math functionality, import math. ================================================ FILE: base/usr/share/help/file-browser.trt ================================================

File Browser

Provides a graphical representation of the file system.
Double click to navigate through directories or open files.
A right-click context menu provides additional options. ================================================ FILE: base/usr/share/help/help-browser.trt ================================================

Help Browser

Displays HTML-escape markup files describing applications.
This application is incomplete. A more featureful HTML rendering engine will eventually replace it. ================================================ FILE: base/usr/share/help/package-manager.trt ================================================

Package Manager

Graphical interface to the msk command-line utility.
Double click packages to install them. Dependencies will be installed automatically. ================================================ FILE: base/usr/share/help/terminal.trt ================================================

Terminal

Terminal emulator providing extensive ANSI escape sequence support.
Supports most xterm-compatible escape squences, as well as 24-bit color.
Text display supports anti-aliased and bitmap fonts. ================================================ FILE: boot/README.md ================================================ # ToaruOS Bootloader This is version 4.0 of the ToaruOS Live CD bootloader. The 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. The 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. While 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. The 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). While 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. ================================================ FILE: boot/boot.S ================================================ .code16 main: ljmp $0x0,$entry entry: /* Set up initial segments */ xor %ax, %ax mov %ax, %ds mov %ax, %ss /* Don't lose dl */ mov %dl, boot_disk /* Initialize stack to just below us */ mov $0x7c00, %ax mov %ax, %sp /* Prepare to switch to unreal mode */ cli push %ds push %es /* Enable A20 */ in $0x92, %al or $2, %al out %al, $0x92 /* Switch to unreal mode */ lgdtw gdtr mov %cr0, %eax or $1, %al mov %eax, %cr0 jmp pmode pmode: mov $0x10, %bx mov %bx, %ds mov %bx, %es and $0xfe, %al mov %eax, %cr0 jmp unrealmode unrealmode: pop %es pop %ds /* Clear the screen */ mov $0, %al movl $3840, %ecx movl $0xb8000, %edi addr32 rep stosb /* Check if we can actually go to long mode on this */ mov $0x80000001, %eax cpuid and $0x20000000, %edx jnz can_long movl $str_Need_long, %esi call print_string jmp _oh_no can_long: /* Spot check memory */ movl $0x12345678, %eax movl $0x5000000, %ebx movl %eax, (%ebx) movl (%ebx), %edx cmp %edx, %eax jz good_memory movl $str_More_mem, %esi call print_string _oh_no: jmp _oh_no good_memory: /* Ask for drive params */ mov $0x48, %ah mov boot_disk, %dl mov $drive_params, %si int $0x13 .extern _bss_start boot_from_cd: /* Collect information on lower memory. */ mov $0x500, %ax mov %ax, %es clc int $0x12 mov %ax, lower_mem /* Collect information on upper memory. */ mov $0x0, %di call do_e820 jc hang /* Get video mode info */ mov $0, %ax mov %ax, %es mov $vbe_cont_info, %di mov $0x4F00, %ax int $0x10 /* Actually switch to protected mode. */ mov %cr0, %eax or $1, %eax mov %eax, %cr0 mov $0x10, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs mov %ax, %ss cli .global bios_main ljmp $0x08,$bios_main hang: jmp hang do_e820: xor %ebx, %ebx xor %bp, %bp mov $0x534D4150, %edx mov $0xe820, %eax movl $0x1,%es:20(%di) mov $24, %ecx int $0x15 jb do_e820.failed mov $0x534D4150, %edx cmp %edx, %eax jne do_e820.failed test %ebx, %ebx je do_e820.failed jmp do_e820.jmpin do_e820.e820lp: mov $0xe820, %eax movl $0x1,%es:20(%di) mov $24, %ecx int $0x15 jb do_e820.e820f mov $0x534D4150, %edx do_e820.jmpin: jcxz do_e820.skipent cmp $20, %cl jbe do_e820.notext testb $0x1, %es:20(%di) je do_e820.skipent do_e820.notext: mov %es:8(%di), %ecx or %es:12(%di), %ecx jz do_e820.skipent inc %bp add $24, %di do_e820.skipent: test %ebx, %ebx jne do_e820.e820lp do_e820.e820f: mov %bp, mmap_ent clc ret do_e820.failed: stc ret print_string: movl $0xb8000, %edi print_string.loop: movb (%esi), %ah cmp $0, %ah je print_string.exit movb %ah, (%edi) inc %edi movb $7, (%edi) inc %esi inc %edi jmp print_string.loop print_string.exit: ret pm_stack: .quad 0 .global do_bios_call do_bios_call: .code32 /* Standard function entry point stuff */ push %ebp mov %esp, %ebp push %eax push %ebx push %ecx push %edx push %esi push %edi /* Save stack because bios might mess it up? */ movl %esp, %eax movl %eax, (pm_stack) /* Prepare intermediary mode */ mov $0x20, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs mov %ax, %ss /* Enable intermediary mode */ ljmp $0x18,$do_bios_call.0 do_bios_call.0: .code16 /* Disable protected mode */ mov %cr0, %eax and $~1, %eax mov %eax, %cr0 /* Jump to deactivate protected mode */ ljmp $0x0,$do_bios_call.1 do_bios_call.1: /* Set up real mode segments */ xor %ax, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs mov %ax, %ss /* Enable interrupts while BIOS is active */ sti /* Function switch */ movl 32(%esp), %ebx /* 1: Read disk */ mov $0x01, %ax cmp %bx, %ax je do_bios_call.read_disk /* 2: Query mode index */ mov $0x02, %ax cmp %bx, %ax je do_bios_call.query_mode /* 3: Set mode */ mov $0x03, %ax cmp %bx, %ax je do_bios_call.set_mode mov $0x04, %ax cmp %bx, %ax je do_bios_call.test_key mov $0x05, %ax cmp %bx, %ax je do_bios_call.set_font /* Else: Bad call, jump to loop. */ jmp do_bios_call.done do_bios_call.read_disk: mov $0x42, %ah /* Extended read */ mov boot_disk, %dl /* Using our boot disk */ mov $dap, %si /* From the DAP below */ int $0x13 jmp do_bios_call.done do_bios_call.query_mode: movl 36(%esp), %ecx mov $0x0, %ax mov %ax, %es mov $vbe_info, %edi mov $0x4F01, %ax int $0x10 jmp do_bios_call.done do_bios_call.set_mode: movl 36(%esp), %ebx mov $0x4F02, %ax int $0x10 jmp do_bios_call.done do_bios_call.test_key: movl 36(%esp), %ebx xor %ax, %ax mov %bl, %ah int $0x16 movl %eax, 20(%esp) jmp do_bios_call.done do_bios_call.set_font: movl 36(%esp), %ebp /* address of font data into ebp */ mov $0x1100, %ax /* mode = load user-defined font */ mov $17, %bh /* 17 bytes (rows) per character */ mov $0, %bl /* font block 0 */ mov $0, %dx /* starting from char 0 */ mov $0x100, %cx /* write 256 glyphs */ int $0x10 jmp do_bios_call.done do_bios_call.done: /* Disable interrupts again */ cli /* Restore data segment, gdt */ xor %ax,%ax mov %ax, %ds lgdtw gdtr /* Enable protected mode */ mov %cr0, %eax or $1, %eax mov %eax, %cr0 /* Jump to activate protected mode */ ljmp $0x08,$do_bios_call.2 do_bios_call.2: .code32 /* Restore protected mode data segments */ mov $0x10, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs mov %ax, %ss /* Restore stack */ movl (pm_stack), %eax movl %eax, %esp /* Pop callee-saved registers we may messed with */ pop %edi pop %esi pop %edx pop %ecx pop %ebx pop %eax pop %ebp /* Return */ ret .align 8 gdtr: .word gdt_end - gdt_base - 1 .long gdt_base gdt_base: .quad 0 .word 0xFFFF .word 0 .byte 0 .byte 0x9a .byte 0xcf .byte 0 .word 0xffff .word 0 .byte 0 .byte 0x92 .byte 0xcf .byte 0 .word 0xffff .word 0 .byte 0 .byte 0x9e .byte 0 .byte 0 .word 0xffff .word 0 .byte 0 .byte 0x92 .byte 0 .byte 0 gdt_end: .global boot_disk boot_disk: .byte 0 .global mmap_ent mmap_ent: .byte 0 .byte 0 .global lower_mem lower_mem: .byte 0 .byte 0 .align 4 .global dap dap: .byte 16 .byte 0 /* always 0 */ .global dap_sectors dap_sectors: .word 1 .global dap_buffer dap_buffer: .long 0x0 .global dap_lba_low dap_lba_low: .long 0 .global dap_lba_high dap_lba_high: .long 0 .align 4 drive_params: .word 0x1A .word 0 /* flags */ .long 0 /* cylinders */ .long 0 /* heads */ .long 0 /* sectors */ .quad 0 /* total sectors */ .global drive_params_bps drive_params_bps: .word 0 /* bytes per sector */ .align 4 .global vbe_info vbe_info: .word 0 /* attributes */ .word 0 /* old shit (window a/b) */ .word 0 /* Granulatory of banks, don't care. */ .word 0 /* Window size, don't care. */ .long 0 /* Segments... */ .long 0 /* old bank switching thing */ .global vbe_info_pitch vbe_info_pitch: .word 0 /* PITCH */ .global vbe_info_width vbe_info_width: .word 0 /* WIDTH */ .global vbe_info_height vbe_info_height: .word 0 /* HEIGHT */ .word 0 /* w, y */ .byte 0 /* planes */ .global vbe_info_bpp vbe_info_bpp: .byte 0 /* bits per pixel */ .byte 0 /* banks */ .byte 0 /* Memory model */ .byte 0 /* bank size */ .byte 0 /* pages */ .byte 0 /* reserved */ .byte 0 /* RED mask */ .byte 0 /* RED offset */ .byte 0 /* GREEN mask */ .byte 0 /* GREEN offset */ .byte 0 /* BLUE maask */ .byte 0 /* BLUE offset */ .byte 0 /* ALPHA mask */ .byte 0 /* ALPHA offset */ .byte 0 /* Color attributes */ .global vbe_info_fbaddr vbe_info_fbaddr: .long 0 /* Framebuffer address */ .long 0 /* Extra memory offset */ .word 0 /* Extra memory size */ .zero 206 /* Other crap */ .align 4 vbe_cont_info: .ascii "VBE2" .word 0x200 .long 0 .long 0 /* caps */ .global vbe_cont_info_mode_off vbe_cont_info_mode_off: .word 0 /* MODES */ vbe_cont_info_mode_seg: .word 0 .zero 494 str_Need_long: .asciz "ToaruOS 2.0 requires a 64-bit processor." str_More_mem: .asciz "ToaruOS 2.0 needs at least 128MiB of RAM, and 1GiB is recommended." .global disk_space disk_space: .zero 2048 ================================================ FILE: boot/config.c ================================================ /** * @brief Shared bootloader configuration. * * Sets up menus that present the boot options for both the EFI * and BIOS loaders. If you want to tweak ToaruOS's bootloader * to boot some other Multiboot1-compliant OS, start here. * * This is also the place to add new default startup configs, * add toggles for command line options, and so on. * * @copyright * 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-2021 K. Lange */ #include #include #include "options.h" #include "util.h" #include "menu.h" #include "text.h" #include "multiboot.h" #include "editor.h" /* Basic text strings */ #define BASE_VERSION "ToaruOS Bootloader v5.0" char * VERSION_TEXT = BASE_VERSION #ifdef EFI_PLATFORM " (EFI)"; #else " (BIOS)"; #endif char * HELP_TEXT = " to boot, to edit, or select a menu option with \030/\031/\032/\033."; char * HELP_TEXT_OPT = " to toggle, or select another option with \030/\031/\032/\033."; char * COPYRIGHT_TEXT = "ToaruOS is free software under the NCSA license."; char * LINK_TEXT = "https://toaruos.org - https://github.com/klange/toaruos"; /* Boot command line strings */ #define DEFAULT_ROOT_CMDLINE "root=/dev/ram0 " #define DEFAULT_GRAPHICAL_CMDLINE "start=live-session " #define DEFAULT_SINGLE_CMDLINE "start=\"terminal -F\" " #define DEFAULT_TEXT_CMDLINE "start=--vga vid=text " #define DEFAULT_VID_CMDLINE "vid=auto " #define MIGRATE_CMDLINE "migrate " #define DEFAULT_HEADLESS_CMDLINE "start=--headless " char * kernel_path = "KERNEL."; char * ramdisk_path = "RAMDISK.IGZ"; char cmdline[1024] = {0}; /* Names of the available boot modes. */ struct bootmode boot_mode_names[] = { {1, "normal", "Normal Boot"}, {2, "video", "Configure Video Output"}, {3, "single", "Single-User Graphical Terminal"}, {4, "headless", "Headless"}, #ifndef EFI_PLATFORM {5, "vga", "VGA Text Mode"}, #endif }; int base_sel = 0; int kmain() { BOOT_SET(); BOOT_OPTION(_debug, 0, "Debug output", "Enable debug output in the bootloader and enable the", "serial debug log in the operating system itself."); BOOT_OPTION(_smp, 1, "Enable SMP", "SMP support may not be completely stable and can be", "disabled with this option if desired."); BOOT_OPTION(_vbox, 1, "VirtualBox Guest Additions", "Enable integration with VirtualBox, including", "automatic mode setting and absolute mouse pointer."); BOOT_OPTION(_vboxrects, 0, "VirtualBox Seamless support", "(Requires Guest Additions) Enables support for the", "Seamless Desktop mode in VirtualBox."); BOOT_OPTION(_vboxpointer, 1, "VirtualBox Pointer", "(Requires Guest Additions) Enables support for the", "VirtualBox hardware pointer mapping."); BOOT_OPTION(_vmware, 1, "VMWare driver", "Enable the VMware / QEMU absolute mouse pointer,", "and optional guest scaling."); BOOT_OPTION(_vmwareres, 1, "VMware guest size", "(Requires VMware driver) Enables support for", "automatically setting display size in VMware"); BOOT_OPTION(_qemubug, 0, "QEMU PS/2 workaround", "Work around a bug in QEMU's PS/2 controller", "prior to 6.0.50."); BOOT_OPTION(_migrate, 1, "Writable root", "Migrates the ramdisk from tarball to an in-memory", "temporary filesystem at boot. Needed for packages."); BOOT_OPTION(_lfbwc, 1, "WC framebuffer", "Enables write-combining PAT configuration for", "framebuffers. Toggle if graphics are slow."); BOOT_OPTION(_kaslr, 1, "KASLR (experimental)", "Enables rudimentary randomization of the kernel", "load address within a small range."); while (1) { /* Loop over rendering the menu */ show_menu(); if (boot_mode == 2) { extern int video_menu(void); video_menu(); boot_edit = 0; memset(cmdline, 0, 1024); continue; } /* Build our command line. */ strcat(cmdline, DEFAULT_ROOT_CMDLINE); if (_migrate) { strcat(cmdline, MIGRATE_CMDLINE); } char * _video_command_line = DEFAULT_VID_CMDLINE; if (boot_mode == 1) { strcat(cmdline, DEFAULT_GRAPHICAL_CMDLINE); strcat(cmdline, _video_command_line); } else if (boot_mode == 3) { strcat(cmdline, DEFAULT_SINGLE_CMDLINE); strcat(cmdline, _video_command_line); } else if (boot_mode == 4) { strcat(cmdline, DEFAULT_HEADLESS_CMDLINE); } else if (boot_mode == 5) { strcat(cmdline, DEFAULT_TEXT_CMDLINE); } if (_debug) { txt_debug = 1; strcat(cmdline, "debug "); } if (!_vbox) { strcat(cmdline, "novbox "); } if (_vbox && !_vboxrects) { strcat(cmdline, "novboxseamless "); } if (_vbox && !_vboxpointer) { strcat(cmdline, "novboxpointer "); } if (_vmware && !_vmwareres) { strcat(cmdline, "novmwareresset "); } if (!_smp) { strcat(cmdline, "nosmp "); } if (_qemubug) { strcat(cmdline, "sharedps2 "); } if (_lfbwc) { strcat(cmdline, "lfbwc "); } extern int disable_kaslr; disable_kaslr = !_kaslr; if (!boot_edit) break; if (boot_editor()) break; boot_edit = 0; memset(cmdline, 0, 1024); } boot(); while (1) {} return 0; } ================================================ FILE: boot/editor.c ================================================ /** * @brief Command line editor. * * Very rudimentary command line editor so options can be * tweaked. Has a couple of nice features like being * able to move the cursor. Not intended to be all that * robust, and needs to work in EFI and BIOS. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include "options.h" #include "text.h" #include "util.h" #include "kbd.h" int boot_edit = 0; static uint16_t attribute_cache[80*25] = {0}; static void draw_text(int cursor, int len) { int i = 0; for (int _my_y = 0; _my_y < 25; ++_my_y) { for (int _my_x = 0; _my_x < 80; ++_my_x) { int ch = (i < len) ? cmdline[i] : ' '; int attr = (i == cursor) ? 0x70 : 0x07; uint16_t combined = (attr << 8) | (ch & 0xFF); if (attribute_cache[i] != combined) { move_cursor(_my_x, _my_y); set_attr(attr); print_((char[]){ch,'\0'}); attribute_cache[i] = combined; } i++; } } } int boot_editor(void) { int len = strlen(cmdline); int cursor = len; int data = 0; memset(attribute_cache, 0, sizeof(attribute_cache)); while (1) { draw_text(cursor, len); int status; do { status = read_key(&data); } while (status == 1); if (status == 0) { /* Handle a few special characters */ if (data == '\n') { return 1; } else if (data == 27) { return 0; } else if (data == '\b') { if (!cursor) continue; if (cursor == len) { cmdline[len-1] = '\0'; cursor--; len--; } else { cmdline[cursor-1] = '\0'; strcat(cmdline,&cmdline[cursor]); cursor--; len--; } } else { if (len > 1022) continue; /* Move everything from the cursor onward forward */ if (cursor < len) { int x = len + 1; while (x > cursor) { cmdline[x] = cmdline[x-1]; x--; } } cmdline[cursor] = data; len++; cursor++; } } else if (status == 2) { /* Left */ if (cursor) cursor--; } else if (status == 3) { /* Right */ if (cursor < len) cursor++; } else if (status == 4) { /* Shift-left: Word left */ while (cursor && cmdline[cursor] == ' ') cursor--; while (cursor && cmdline[cursor] != ' ') cursor--; } else if (status == 5) { /* Shift-right: Word right */ while (cursor < len && cmdline[cursor] == ' ') cursor++; while (cursor < len && cmdline[cursor] != ' ') cursor++; } } } ================================================ FILE: boot/editor.h ================================================ #pragma once extern int boot_edit; int boot_editor(void); ================================================ FILE: boot/elf.h ================================================ #pragma once #define ELFMAG0 0x7f #define ELFMAG1 'E' #define ELFMAG2 'L' #define ELFMAG3 'F' #define EI_NIDENT 16 typedef uint32_t Elf32_Word; typedef uint32_t Elf32_Addr; typedef uint32_t Elf32_Off; typedef uint32_t Elf32_Sword; typedef uint16_t Elf32_Half; /* * ELF Header */ typedef struct { unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; } Elf32_Header; /* * e_type */ #define ET_NONE 0 /* No file type */ #define ET_REL 1 /* Relocatable file */ #define ET_EXEC 2 /* Executable file */ #define ET_DYN 3 /* Shared object file */ #define ET_CORE 4 /* Core file */ #define ET_LOPROC 0xff0 /* [Processor Specific] */ #define ET_HIPROC 0xfff /* [Processor Specific] */ /* * Machine types */ #define EM_NONE 0 #define EM_386 3 #define EV_NONE 0 #define EV_CURRENT 1 /** Program Header */ typedef struct { Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_align; } Elf32_Phdr; /* p_type values */ #define PT_NULL 0 /* Unused, skip me */ #define PT_LOAD 1 /* Loadable segment */ #define PT_DYNAMIC 2 /* Dynamic linking information */ #define PT_INTERP 3 /* Interpreter (null-terminated string, pathname) */ #define PT_NOTE 4 /* Auxillary information */ #define PT_SHLIB 5 /* Reserved. */ #define PT_PHDR 6 /* Oh, it's me. Hello! Back-reference to the header table itself */ #define PT_LOPROC 0x70000000 #define PT_HIPROC 0x7FFFFFFF /** Section Header */ typedef struct { Elf32_Word sh_name; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_info; Elf32_Word sh_addralign; Elf32_Word sh_entsize; } Elf32_Shdr; typedef struct { uint32_t id; uint32_t ptr; } Elf32_auxv; typedef struct { Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx; } Elf32_Sym; typedef struct { Elf32_Addr r_offset; Elf32_Word r_info; } Elf32_Rel; typedef struct { Elf32_Sword d_tag; union { Elf32_Word d_val; Elf32_Addr d_ptr; Elf32_Off d_off; } d_un; } Elf32_Dyn; /* sh_type values */ #define SHT_NONE 0 #define SHT_PROGBITS 1 #define SHT_SYMTAB 2 #define SHT_STRTAB 3 #define SHT_NOBITS 8 #define SHT_REL 9 #define ELF32_R_SYM(i) ((i) >> 8) #define ELF32_R_TYPE(i) ((unsigned char)(i)) #define ELF32_R_INFO(s,t) (((s) << 8) + (unsigned char)(t)) #define ELF32_ST_BIND(i) ((i) >> 4) #define ELF32_ST_TYPE(i) ((i) & 0xf) #define ELF32_ST_INFO(b,t) (((b) << 4) + ((t) & 0xf)) #define STB_LOCAL 0 #define STB_GLOBAL 1 #define STB_WEAK 2 #define STB_NUM 3 #define STB_LOPROC 13 #define STB_HIPROC 15 #define STT_NOTYPE 0 #define STT_OBJECT 1 #define STT_FUNC 2 #define STT_SECTION 3 #define STT_FILE 4 #define STT_COMMON 5 #define STT_TLS 6 #define STT_NUM 7 #define STT_LOPROC 13 #define STT_HIPROC 15 ================================================ FILE: boot/iso9660.h ================================================ #pragma once #include typedef struct { char year[4]; char month[2]; char day[2]; char hour[2]; char minute[2]; char second[2]; char hundredths[2]; int8_t timezone; } __attribute__((packed)) iso_9660_datetime_t; typedef struct { uint8_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; int8_t timezone; } __attribute__((packed)) iso_9660_rec_date_t; typedef struct { uint8_t length; uint8_t ext_length; uint32_t extent_start_LSB; uint32_t extent_start_MSB; uint32_t extent_length_LSB; uint32_t extent_length_MSB; iso_9660_rec_date_t record_date; uint8_t flags; uint8_t interleave_units; uint8_t interleave_gap; uint16_t volume_seq_LSB; uint16_t volume_seq_MSB; uint8_t name_len; char name[]; } __attribute__((packed)) iso_9660_directory_entry_t; typedef struct { uint8_t type; /* 0x01 */ char id[5]; /* CD001 */ uint8_t version; uint8_t _unused0; char system_id[32]; char volume_id[32]; uint8_t _unused1[8]; uint32_t volume_space_LSB; uint32_t volume_space_MSB; uint8_t _unused2[32]; uint16_t volume_set_LSB; uint16_t volume_set_MSB; uint16_t volume_seq_LSB; uint16_t volume_seq_MSB; uint16_t logical_block_size_LSB; uint16_t logical_block_size_MSB; uint32_t path_table_size_LSB; uint32_t path_table_size_MSB; uint32_t path_table_LSB; uint32_t optional_path_table_LSB; uint32_t path_table_MSB; uint32_t optional_path_table_MSB; /* iso_9660_directory_entry_t */ char root[34]; char volume_set_id[128]; char volume_publisher[128]; char data_preparer[128]; char application_id[128]; char copyright_file[38]; char abstract_file[36]; char bibliographic_file[37]; iso_9660_datetime_t creation; iso_9660_datetime_t modification; iso_9660_datetime_t expiration; iso_9660_datetime_t effective; uint8_t file_structure_version; uint8_t _unused_3; char application_use[]; } __attribute__((packed)) iso_9660_volume_descriptor_t; #define ISO_SECTOR_SIZE 2048 #define FLAG_HIDDEN 0x01 #define FLAG_DIRECTORY 0x02 #define FLAG_ASSOCIATED 0x04 #define FLAG_EXTENDED 0x08 #define FLAG_PERMISSIONS 0x10 #define FLAG_CONTINUES 0x80 extern char root_data[ISO_SECTOR_SIZE]; extern iso_9660_volume_descriptor_t * root; extern iso_9660_directory_entry_t * dir_entry; int navigate(char * name); ================================================ FILE: boot/kbd.c ================================================ /** * @brief Keyboard reading functions. * * Abstracts away the differences between our EFI and BIOS * environments to provide consistent scancode feedback for * the menus and command line editor. * * For EFI, we use the WaitForKey and ReadKeyStroke interfaces. * * For BIOS, we have a bad PS/2 driver, which should be fine if * you're booting with BIOS? * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include "kbd.h" #include "util.h" #include "text.h" #ifdef EFI_PLATFORM #include extern EFI_SYSTEM_TABLE *ST; #define KBD_SCAN_DOWN 0x50 #define KBD_SCAN_UP 0x48 #define KBD_SCAN_LEFT 0x4B #define KBD_SCAN_RIGHT 0x4D #define KBD_SCAN_ENTER 0x1C #define KBD_SCAN_1 2 #define KBD_SCAN_9 10 int read_scancode(int timeout) { EFI_INPUT_KEY Key; unsigned long int index; if (timeout) { EFI_EVENT events[] = {ST->ConIn->WaitForKey, 0}; uefi_call_wrapper(ST->BootServices->CreateEvent, 5, EVT_TIMER, 0, NULL, NULL, &events[1]); uefi_call_wrapper(ST->BootServices->SetTimer, 3, events[1], TimerRelative, 10000000UL); uefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 2, events, &index); } else { uefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index); } EFI_STATUS result = uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &Key); if (result == EFI_NOT_READY) return -1; switch (Key.ScanCode) { case 0: switch (Key.UnicodeChar) { case L'\r': return KBD_SCAN_ENTER; case L'1': case L'2': case L'3': case L'4': case L'5': case L'6': case L'7': case L'8': case L'9': return Key.UnicodeChar - L'1' + KBD_SCAN_1; case L'e': return 0x12; default: return 0xFF; } break; case 0x01: return KBD_SCAN_UP; case 0x02: return KBD_SCAN_DOWN; case 0x03: return KBD_SCAN_RIGHT; case 0x04: return KBD_SCAN_LEFT; default: return 0xFF; } } int read_key(int * c) { EFI_INPUT_KEY Key; unsigned long int index; uefi_call_wrapper(ST->BootServices->WaitForEvent, 3, 1, &ST->ConIn->WaitForKey, &index); uefi_call_wrapper(ST->ConIn->ReadKeyStroke, 2, ST->ConIn, &Key); if (Key.ScanCode == 0) { *c = Key.UnicodeChar; if (*c == '\r') *c = '\n'; return 0; } switch (Key.ScanCode) { case 0x03: return 3; case 0x04: return 2; case 0x09: return 4; case 0x0a: return 5; case 0x17: *c = 27; return 0; } return 1; } #else int read_cmos_seconds(void) { outportb(0x70,0); return inportb(0x71); } static char kbd_us[128] = { 0, 27, '1','2','3','4','5','6','7','8','9','0', '-','=','\b', '\t', 'q','w','e','r','t','y','u','i','o','p','[',']','\n', 0, 'a','s','d','f','g','h','j','k','l',';','\'', '`', 0, '\\','z','x','c','v','b','n','m',',','.','/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; static char kbd_us_l2[128] = { 0, 27, '!','@','#','$','%','^','&','*','(',')', '_','+','\b', '\t', 'Q','W','E','R','T','Y','U','I','O','P','{','}','\n', 0, 'A','S','D','F','G','H','J','K','L',':','"', '~', 0, '|','Z','X','C','V','B','N','M','<','>','?', 0, '*', 0, '\037', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; extern int do_bios_call(unsigned int function, unsigned int arg1); int read_key(int * c) { static int shift_state = 0; int sc = read_scancode(0); int shift = do_bios_call(4,2) & 0x3; if (sc == 0x4B) return shift ? 4 : 2; if (sc == 0x4D) return shift ? 5 : 3; if (!(sc & 0x80)) { *c = shift ? kbd_us_l2[sc] : kbd_us[sc]; return *c == 0; } return 1; } int kbd_status(void) { int result = do_bios_call(4,0x11); return (result & 0xFF) == 0; } int read_scancode(int timeout) { if (timeout) { int start_s = read_cmos_seconds(); while (kbd_status()) { int now_s = read_cmos_seconds(); if (now_s != start_s) return -1; } } int result = do_bios_call(4,0); return (result >> 8) & 0xFF; } #endif ================================================ FILE: boot/kbd.h ================================================ #pragma once #define KBD_SCAN_DOWN 0x50 #define KBD_SCAN_UP 0x48 #define KBD_SCAN_LEFT 0x4B #define KBD_SCAN_RIGHT 0x4D #define KBD_SCAN_ENTER 0x1C #define KBD_SCAN_1 2 #define KBD_SCAN_9 10 int read_scancode(int); int read_key(int * c); ================================================ FILE: boot/link.ld ================================================ OUTPUT_FORMAT("binary") /*ENTRY(start) */ phys = 0x7c00; SECTIONS { . = 0x7c00; .text : { code = .; *(.text) } .rodata BLOCK(1) : ALIGN(1) { *(.rodata) } .data BLOCK(1) : ALIGN(1) { data = .; *(.data) } .bss BLOCK(1) : ALIGN(1) { PROVIDE(_bss_start = .); bss = .; *(COMMON) *(.bss) *(.stack) PROVIDE(_bss_end = .); } end = .; /DISCARD/ : { *(.comment) *(.eh_frame) *(.note.gnu.build-id) } } ================================================ FILE: boot/mbr.S ================================================ .code16 main: /* fix up code seg */ ljmp $0x0,$entry entry: /* init data segments */ xor %ax, %ax mov %ax, %ds mov %ax, %ss /* save boot disk */ mov %dl, boot_disk /* set up stack */ mov $0x7b00, %ax mov %ax, %sp /* figure out sector size */ mov $0x48, %ah mov boot_disk, %dl mov $drive_params, %si int $0x13 /* figure out first sector of stage 2 */ mov $0, %edx mov $BOOT_FILE_OFFSET, %eax mov (drive_params_bps), %ecx div %ecx mov %eax, dap_lba_low mov $BOOT_FILE_SIZE, %eax div %ecx inc %eax mov %ax, dap_sectors movl $0x7e00, dap_buffer mov $0x42, %ah /* Extended read */ mov boot_disk, %dl /* Using our boot disk */ mov $dap, %si /* From the DAP below */ int $0x13 mov $0, %ax mov %ax, %es mov %ax, %ds /* Now move the rest of our code somewhere low */ mov $mover, %esi mov $0x7b00, %edi mov $(mover_end-mover), %ecx rep movsb mov $0x7b00, %eax jmp *%eax mover: mov $0x7e00, %esi mov $0x7c00, %edi mov $BOOT_FILE_SIZE, %ecx rep movsb mov $0x7c00, %eax jmp *%eax mover_end: boot_disk: .byte 0 .align 4 .global dap dap: .byte 16 .byte 0 /* always 0 */ .global dap_sectors dap_sectors: .word 1 .global dap_buffer dap_buffer: .long 0x0 .global dap_lba_low dap_lba_low: .long 0 .global dap_lba_high dap_lba_high: .long 0 .align 4 drive_params: .word 0x1A .word 0 /* flags */ .long 0 /* cylinders */ .long 0 /* heads */ .long 0 /* sectors */ .quad 0 /* total sectors */ drive_params_bps: .word 0 /* bytes per sector */ .org 510 .byte 0x55 .byte 0xaa ================================================ FILE: boot/menu.c ================================================ /** * @brief Present configuration menus. * * Handles display and user interaction for the config menu. * * @copyright * 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-2021 K. Lange */ #include "options.h" #include "text.h" #include "util.h" #include "kbd.h" #include "qemu.h" #include "editor.h" extern void draw_logo(int); struct option boot_options[20] = {{0}}; int sel_max = 0; int sel = 0; int boot_mode = 0; void toggle(int ndx, int value, char *str) { set_attr(sel == ndx ? 0x70 : 0x07); if (value) { print_(" [X] "); } else { print_(" [ ] "); } print_(str); if (x < 40) { while (x < 39) { print_(" "); } x = 40; } else { print_("\n"); } } void show_menu(void) { if (detect_qemu()) return; static int timeout_shown = 0; int s; int timeout = 4; char timeout_msg[] = "Normal boot will commence in 0 seconds."; char * timeout_val = strchr(timeout_msg,'0'); /* Determine number of options */ sel_max = 0; while (boot_options[sel_max].value) { sel_max++; } sel_max += base_sel + 1; clear_(); if (!timeout_shown) { timeout_shown = 1; draw_logo(10); while (1) { move_cursor(0,15); *timeout_val = timeout + '0'; set_attr(0x08); print_banner("Press to boot now, to edit command line,"); print_banner("or use \030/\031/\032/\033 to select a menu option."); print_banner(timeout_msg); s = read_scancode(1); if (timeout && s == -1) { timeout--; if (!timeout) { boot_mode = boot_mode_names[sel].index; return; } continue; } clear_(); goto _key_read; } } do { move_cursor(0,0); set_attr(0x1f); print_banner(VERSION_TEXT); set_attr(0x07); print_("\n"); for (int i = 0; i < base_sel + 1; ++i) { set_attr(sel == i ? 0x70 : 0x07); print_(" "); char tmp[] = {'0' + (i + 1), '.', ' ', '\0'}; print_(tmp); print_(boot_mode_names[i].title); print_("\n"); } // put a gap set_attr(0x07); print_("\n"); for (int i = 0; i < sel_max - base_sel - 1; ++i) { toggle(base_sel + 1 + i, *boot_options[i].value, boot_options[i].title); } set_attr(0x07); move_cursor(0,17); print_banner(sel <= base_sel ? HELP_TEXT : HELP_TEXT_OPT); print_("\n"); if (sel > base_sel) { print_banner(boot_options[sel - base_sel - 1].description_1); print_banner(boot_options[sel - base_sel - 1].description_2); print_("\n"); } else { print_banner(COPYRIGHT_TEXT); print_("\n"); print_banner(LINK_TEXT); } read_again: timeout = 0; s = read_scancode(0); _key_read: if (s == 0x50) { /* DOWN */ if (sel > base_sel && sel < sel_max - 1) { sel = (sel + 2) % sel_max; } else { sel = (sel + 1) % sel_max; } } else if (s == 0x48) { /* UP */ if (sel > base_sel + 1) { sel = (sel_max + sel - 2) % sel_max; } else { sel = (sel_max + sel - 1) % sel_max; } } else if (s == 0x4B) { /* LEFT */ if (sel > base_sel) { if ((sel - base_sel) % 2) { sel = (sel + 1) % sel_max; } else { sel -= 1; } } } else if (s == 0x4D) { /* RIGHT */ if (sel > base_sel) { if ((sel - base_sel) % 2) { sel = (sel + 1) % sel_max; } else { sel -= 1; } } } else if (s == 0x1c) { if (sel <= base_sel) { boot_mode = boot_mode_names[sel].index; break; } else { int index = sel - base_sel - 1; *boot_options[index].value = !*boot_options[index].value; } } else if (s == 0x12) { /* e */ if (sel <= base_sel) { boot_edit = 1; boot_mode = boot_mode_names[sel].index; break; } } else if (s >= 2 && s <= 10) { int i = s - 2; if (i <= base_sel) { boot_mode = boot_mode_names[i].index; break; } #ifndef EFI_PLATFORM } else if (s == 0x2f) { /* v */ void bios_toggle_mode(void); bios_toggle_mode(); #endif } else if (!timeout) { goto read_again; } } while (1); } ================================================ FILE: boot/menu.h ================================================ #pragma once extern int boot_mode; void show_menu(void); ================================================ FILE: boot/multiboot.c ================================================ /** * @brief Main bootloader logic. * * Does all the heavy lifting after configuration options have * been selected by the user. Loads the kernel and ramdisk, * sets up multiboot structures, and jumps to the kernel. * * @copyright * 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-2021 K. Lange */ #include #include #include "multiboot.h" #include "text.h" #include "util.h" #include "menu.h" #include "elf.h" #include "options.h" #include "iso9660.h" #include "kbd.h" extern void draw_logo(int); char * kernel_load_start = 0; mboot_mod_t modules_mboot[1] = { {0,0,0,1} }; static struct multiboot multiboot_header = { /* flags; */ MULTIBOOT_FLAG_CMDLINE | MULTIBOOT_FLAG_MODS | MULTIBOOT_FLAG_MEM | MULTIBOOT_FLAG_MMAP | MULTIBOOT_FLAG_LOADER, /* mem_lower; */ 0x100000, /* mem_upper; */ 0x640000, /* boot_device; */ 0, /* cmdline; */ 0, /* mods_count; */ 1, /* mods_addr; */ 0, /* num; */ 0, /* size; */ 0, /* addr; */ 0, /* shndx; */ 0, /* mmap_length; */ 0, /* mmap_addr; */ 0, /* drives_length; */ 0, /* drives_addr; */ 0, /* config_table; */ 0, /* boot_loader_name; */ 0, /* apm_table; */ 0, /* vbe_control_info; */ 0, /* vbe_mode_info; */ 0, /* vbe_mode; */ 0, /* vbe_interface_seg; */ 0, /* vbe_interface_off; */ 0, /* vbe_interface_len; */ 0, }; static uintptr_t ramdisk_off = 0; static uintptr_t ramdisk_len = 0; uintptr_t final_offset = 0; uintptr_t _xmain = 0; int disable_kaslr = 0; static inline uint64_t read_tsc(void) { uint32_t lo, hi; asm volatile ( "rdtsc" : "=a"(lo), "=d"(hi) ); return ((uint64_t)hi << 32) | (uint64_t)lo; } static int load_aout(uint32_t * hdr) { uintptr_t base_offset = (uintptr_t)hdr - (uintptr_t)kernel_load_start; uintptr_t hdr_offset = hdr[3] - base_offset; uint32_t rando = 0; size_t xtra = 0; if (!disable_kaslr) { asm volatile ( "rdtsc" : "=a"(rando), "=d"((uint32_t){0}) ); xtra = (rando & 0xFF) << 12; } memcpy((void*)(uintptr_t)hdr[4] + xtra, kernel_load_start + (hdr[4] - hdr_offset), (hdr[5] - hdr[4])); memset((void*)(uintptr_t)hdr[5] + xtra, 0, (hdr[6] - hdr[5])); _xmain = (uintptr_t)hdr[7] + xtra; if (hdr[6] + xtra > final_offset) final_offset = hdr[6] + xtra; final_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0); return 1; } static int load_elf32(Elf32_Header * header) { if (header->e_ident[0] != ELFMAG0 || header->e_ident[1] != ELFMAG1 || header->e_ident[2] != ELFMAG2 || header->e_ident[3] != ELFMAG3) { print_("Not a valid ELF32.\n"); return 0; } uintptr_t entry = (uintptr_t)header->e_entry; for (uintptr_t x = 0; x < (uint32_t)header->e_phentsize * header->e_phnum; x += header->e_phentsize) { Elf32_Phdr * phdr = (Elf32_Phdr *)(kernel_load_start + header->e_phoff + x); if (phdr->p_type == PT_LOAD) { memcpy((uint8_t*)(uintptr_t)phdr->p_vaddr, kernel_load_start + phdr->p_offset, phdr->p_filesz); uintptr_t r = phdr->p_filesz; while (r < phdr->p_memsz) { *(char *)(phdr->p_vaddr + r) = 0; r++; } if (phdr->p_vaddr + r > final_offset) final_offset = phdr->p_vaddr + r; } } _xmain = entry; /* Round final offset */ final_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0); print("Loaded with end at 0x"); print_hex(final_offset); print("\n"); return 1; } static int load_kernel(void) { clear(); /* Check for Multiboot header */ for (uintptr_t x = 0; x < 8192; x += 4) { uint32_t * check = (uint32_t *)(kernel_load_start + x); if (*check == 0x1BADB002) { if (check[1] & (1 << 16)) { return load_aout(check); } else { return load_elf32((void*)kernel_load_start); } } } return 0; } static void relocate_ramdisk(mboot_mod_t * mboot_mods) { char * dest = (char*)final_offset; char * src = (char*)ramdisk_off; for (size_t s = 0; s < ramdisk_len; ++s) { dest[s] = src[s]; } mboot_mods->mod_start = final_offset; mboot_mods->mod_end = final_offset + ramdisk_len; final_offset += ramdisk_len; final_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0); } #ifdef EFI_PLATFORM #include extern EFI_GRAPHICS_OUTPUT_PROTOCOL * GOP; /* EFI boot uses simple filesystem driver */ static EFI_GUID efi_simple_file_system_protocol_guid = {0x0964e5b22,0x6459,0x11d2,0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b}; static EFI_GUID efi_loaded_image_protocol_guid = {0x5B1B31A1,0x9562,0x11d2, {0x8E,0x3F,0x00,0xA0,0xC9,0x69,0x72,0x3B}}; extern EFI_SYSTEM_TABLE *ST; extern EFI_HANDLE ImageHandleIn; extern char do_the_nasty[]; static void finish_boot(void) { /* Set up multiboot header */ struct multiboot * finalHeader = (void*)(uintptr_t)final_offset; memcpy((void*)final_offset, &multiboot_header, sizeof(struct multiboot)); final_offset += sizeof(struct multiboot); finalHeader->flags |= MULTIBOOT_FLAG_FB; finalHeader->framebuffer_addr = GOP->Mode->FrameBufferBase; finalHeader->framebuffer_pitch = GOP->Mode->Info->PixelsPerScanLine * 4; finalHeader->framebuffer_width = GOP->Mode->Info->HorizontalResolution; finalHeader->framebuffer_height = GOP->Mode->Info->VerticalResolution; finalHeader->framebuffer_bpp = 32; /* Copy in command line */ memcpy((void*)final_offset, cmdline, strlen(cmdline)+1); finalHeader->cmdline = (uintptr_t)final_offset; final_offset += strlen(cmdline) + 1; /* Copy bootloader name */ memcpy((void*)final_offset, VERSION_TEXT, strlen(VERSION_TEXT)+1); finalHeader->boot_loader_name = (uintptr_t)final_offset; final_offset += strlen(VERSION_TEXT) + 1; /* Copy module pointers */ memcpy((void*)final_offset, modules_mboot, sizeof(modules_mboot)); finalHeader->mods_addr = (uintptr_t)final_offset; final_offset += sizeof(modules_mboot); /* Realign for memory map */ final_offset = (final_offset & ~(0xFFF)) + ((final_offset & 0xFFF) ? 0x1000 : 0); /* Write memory map */ mboot_memmap_t * mmap = (void*)final_offset; memset((void*)final_offset, 0x00, 1024); finalHeader->mmap_addr = (uint32_t)(uintptr_t)mmap; { EFI_STATUS e; UINTN mapSize = 0, mapKey, descriptorSize; UINT32 descriptorVersion; e = uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, NULL, &mapKey, &descriptorSize, NULL); EFI_MEMORY_DESCRIPTOR * efi_memory = (void*)(final_offset); final_offset += mapSize; while ((uintptr_t)final_offset & 0x3ff) final_offset++; e = uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, efi_memory, &mapKey, &descriptorSize, NULL); if (EFI_ERROR(e)) { print_("EFI error.\n"); while (1) {}; } uint64_t upper_mem = 0; int descriptors = mapSize / descriptorSize; for (int i = 0; i < descriptors; ++i) { EFI_MEMORY_DESCRIPTOR * d = efi_memory; mmap->size = sizeof(uint64_t) * 2 + sizeof(uint32_t); mmap->base_addr = d->PhysicalStart; mmap->length = d->NumberOfPages * 4096; switch (d->Type) { case EfiConventionalMemory: case EfiLoaderCode: case EfiLoaderData: case EfiBootServicesCode: case EfiBootServicesData: case EfiRuntimeServicesCode: case EfiRuntimeServicesData: mmap->type = 1; break; case EfiReservedMemoryType: case EfiUnusableMemory: case EfiMemoryMappedIO: case EfiMemoryMappedIOPortSpace: case EfiPalCode: case EfiACPIMemoryNVS: case EfiACPIReclaimMemory: default: mmap->type = 2; break; } if (mmap->type == 1 && mmap->base_addr >= 0x100000) { upper_mem += mmap->length; } mmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uint32_t)); efi_memory = (EFI_MEMORY_DESCRIPTOR *)((char *)efi_memory + descriptorSize); } finalHeader->mmap_length = (uintptr_t)mmap - finalHeader->mmap_addr; finalHeader->mem_lower = 1024; finalHeader->mem_upper = upper_mem / 1024; } relocate_ramdisk((void*)(uintptr_t)finalHeader->mods_addr); { EFI_STATUS e; UINTN mapSize = 0, mapKey, descriptorSize; UINT32 descriptorVersion; uefi_call_wrapper(ST->BootServices->GetMemoryMap, 5, &mapSize, NULL, &mapKey, &descriptorSize, NULL); e = uefi_call_wrapper(ST->BootServices->ExitBootServices, 2, ImageHandleIn, mapKey); if (e != EFI_SUCCESS) { print_("Exit services failed. \n"); print_hex_(e); while (1) {}; } } /* Jump to entry with register arguments */ __asm__ __volatile__ ( "jmp %0" :: "r"(_xmain), "a"(MULTIBOOT_EAX_MAGIC), "b"(finalHeader)); __builtin_unreachable(); } void boot(void) { UINTN count; EFI_HANDLE * handles; EFI_LOADED_IMAGE * loaded_image; EFI_FILE_IO_INTERFACE *efi_simple_filesystem; EFI_FILE *root; EFI_STATUS status; uefi_call_wrapper(ST->BootServices->SetWatchdogTimer, 4, 0, 0, 0, NULL); clear_(); draw_logo(0); for (unsigned int i = 0; i < ST->NumberOfTableEntries; ++i) { /* ACPI 1 table pointer. */ if (ST->ConfigurationTable[i].VendorGuid.Data1 == 0xeb9d2d30 && ST->ConfigurationTable[i].VendorGuid.Data2 == 0x2d88 && ST->ConfigurationTable[i].VendorGuid.Data3 == 0x11d3) { multiboot_header.config_table = (uintptr_t)ST->ConfigurationTable[i].VendorTable & 0xFFFFffff; break; } /* ACPI 2 table pointer. */ if (ST->ConfigurationTable[i].VendorGuid.Data1 == 0x8868e871 && ST->ConfigurationTable[i].VendorGuid.Data2 == 0xe4f1 && ST->ConfigurationTable[i].VendorGuid.Data3 == 0x11d3) { multiboot_header.config_table = (uintptr_t)ST->ConfigurationTable[i].VendorTable & 0xFFFFffff; break; } } status = uefi_call_wrapper(ST->BootServices->HandleProtocol, 3, ImageHandleIn, &efi_loaded_image_protocol_guid, (void **)&loaded_image); if (EFI_ERROR(status)) { print_("Could not obtain loaded_image_protocol\n"); while (1) {}; } print("Found loaded image...\n"); status = uefi_call_wrapper(ST->BootServices->HandleProtocol, 3, loaded_image->DeviceHandle, &efi_simple_file_system_protocol_guid, (void **)&efi_simple_filesystem); if (EFI_ERROR(status)) { print_("Could not obtain simple_file_system_protocol.\n"); while (1) {}; } status = uefi_call_wrapper(efi_simple_filesystem->OpenVolume, 2, efi_simple_filesystem, &root); if (EFI_ERROR(status)) { print_("Could not open volume.\n"); while (1) {}; } EFI_FILE * file; CHAR16 kernel_name[16] = {0}; { char * c = kernel_path; char * ascii = c; int i = 0; while (*ascii) { kernel_name[i] = *ascii; i++; ascii++; } if (kernel_name[i-1] == L'.') { kernel_name[i-1] = 0; } } /* Load kernel */ status = uefi_call_wrapper(root->Open, 5, root, &file, kernel_name, EFI_FILE_MODE_READ, 0); if (EFI_ERROR(status)) { print_("Error opening kernel.\n"); while (1) {}; } #define KERNEL_LOAD_START 0x4000000ULL kernel_load_start = (char*)KERNEL_LOAD_START; { EFI_PHYSICAL_ADDRESS addr = KERNEL_LOAD_START; EFI_ALLOCATE_TYPE type = AllocateAddress; EFI_MEMORY_TYPE memtype = EfiLoaderData; UINTN pages = 8192; EFI_STATUS status = 0; status = uefi_call_wrapper(ST->BootServices->AllocatePages, 4, type, memtype, pages, &addr); if (EFI_ERROR(status)) { print_("Could not allocate space to load boot payloads: "); print_hex_(status); print_(" "); print_hex_(addr); while (1) {}; } } unsigned int offset = 0; UINTN bytes = 134217728; status = uefi_call_wrapper(file->Read, 3, file, &bytes, (void *)KERNEL_LOAD_START); if (EFI_ERROR(status)) { print_("Error loading kernel.\n"); while (1) {}; } offset += bytes; while (offset % 4096) offset++; { char * c = ramdisk_path; CHAR16 name[16] = {0}; char * ascii = c; int i = 0; while (*ascii) { name[i] = *ascii; i++; ascii++; } if (name[i-1] == L'.') { name[i-1] == 0; } bytes = 134217728; status = uefi_call_wrapper(root->Open, 5, root, &file, name, EFI_FILE_MODE_READ, 0); if (!EFI_ERROR(status)) { status = uefi_call_wrapper(file->Read, 3, file, &bytes, (void*)(uintptr_t)(KERNEL_LOAD_START + offset)); if (!EFI_ERROR(status)) { ramdisk_off = KERNEL_LOAD_START + offset; ramdisk_len = bytes; } else { print_("Failed to read ramdisk\n"); } } else { print_("Error opening "); print_(c); print_("\n"); } } load_kernel(); finish_boot(); } #else struct mmap_entry { uint64_t base; uint64_t len; uint32_t type; uint32_t reserved; }; extern unsigned short mmap_ent; extern unsigned short lower_mem; static void finish_boot(void) { print("Setting up memory map...\n"); print_hex(mmap_ent); print("\n"); memset(kernel_load_start, 0x00, 1024); mboot_memmap_t * mmap = (void*)final_offset; multiboot_header.mmap_addr = (uintptr_t)mmap; multiboot_header.mods_addr = (uintptr_t)&modules_mboot; multiboot_header.boot_loader_name = (uintptr_t)VERSION_TEXT; struct mmap_entry * e820 = (void*)0x5000; uint64_t upper_mem = 0; for (int i = 0; i < mmap_ent; ++i) { print("entry "); print_hex(i); print(" "); print_hex((uint32_t)(e820[i].base >> 32ULL)); print_hex((uint32_t)e820[i].base); print(" "); print_hex((uint32_t)(e820[i].len >> 32ULL)); print_hex((uint32_t)e820[i].len); print(" "); print_hex(e820[i].type); print("\n"); mmap->size = sizeof(uint64_t) * 2 + sizeof(uintptr_t); mmap->base_addr = e820[i].base; mmap->length = e820[i].len; mmap->type = e820[i].type; if (mmap->type == 1 && mmap->base_addr >= 0x100000) { upper_mem += mmap->length; } mmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uintptr_t)); } multiboot_header.mmap_length = (uintptr_t)mmap - multiboot_header.mmap_addr; print("lower "); print_hex(lower_mem); print("KB\n"); multiboot_header.mem_lower = 1024; print("upper "); print_hex(upper_mem >> 32); print_hex(upper_mem); print("\n"); multiboot_header.mem_upper = upper_mem / 1024; print("Jumping to kernel...\n"); uint32_t foo[3]; foo[0] = MULTIBOOT_EAX_MAGIC; foo[1] = (unsigned int)&multiboot_header;; foo[2] = _xmain; __asm__ __volatile__ ( "mov %%cr0,%%eax\n" /* Disable paging */ "and $0x7FFeFFFF, %%eax\n" "mov %%eax,%%cr0\n" /* Ensure PAE is not enabled */ "mov %%cr4,%%eax\n" "and $0xffffffdf, %%eax\n" "mov %%eax,%%cr4\n" "mov %1,%%eax \n" "mov %2,%%ebx \n" "jmp *%0" : : "g"(foo[2]), "g"(foo[0]), "g"(foo[1]) : "eax", "ebx" ); } extern void do_bios_call(uint32_t function, uint32_t arg1); extern int bios_call(char * into, uint32_t sector); static int spin_x = 0; static void spin(void) { static int spincnt = 0; draw_logo(spincnt+1); spincnt = (spincnt + 1) & 0x7; } static void clear_spin(void) { y = 16; //print_banner(""); } extern uint16_t * vbe_cont_info_mode_off; extern uint32_t *vbe_info_fbaddr; extern uint16_t vbe_info_pitch; extern uint16_t vbe_info_width; extern uint16_t vbe_info_height; extern uint8_t vbe_info_bpp; extern void bios_text_mode(void); void boot(void) { /* Did we ask for VGA text mode and are currently in a video mode? */ if (boot_mode == 5) { bios_text_mode(); } clear_(); draw_logo(0); print("Looking for ISO9660 filesystem... "); for (int i = 0x10; i < 0x15; ++i) { bios_call((char*)(DATA_LOAD_BASE + ISO_SECTOR_SIZE * i), i); root = (void*)(DATA_LOAD_BASE + ISO_SECTOR_SIZE * i); switch (root->type) { case 1: print("found.\n"); goto done; } } return; done: print("Looking for kernel... "); if (!navigate(kernel_path)) { print_("Failed to locate kernel.\n"); return; } print("found.\n"); kernel_load_start = (char*)(DATA_LOAD_BASE + dir_entry->extent_start_LSB * ISO_SECTOR_SIZE); print("Loading kernel... "); spin_x = x; for (int i = 0, j = 0; i < dir_entry->extent_length_LSB; j++) { if (!(j & 0x3FF)) spin(); bios_call(kernel_load_start + i, dir_entry->extent_start_LSB + j); i += ISO_SECTOR_SIZE; } print("\n"); print("Looking for ramdisk... "); if (!navigate(ramdisk_path)) { print_("Failed to locate ramdisk.\n"); return; } print("found.\n"); ramdisk_off = DATA_LOAD_BASE + dir_entry->extent_start_LSB * ISO_SECTOR_SIZE; print("Loading ramdisk... "); spin_x = x; for (int i = 0, j = 0; i < dir_entry->extent_length_LSB; j++) { if (!(j & 0x3FF)) spin(); bios_call((char*)(ramdisk_off + i), dir_entry->extent_start_LSB + j); i += ISO_SECTOR_SIZE; } print("\n"); ramdisk_len = dir_entry->extent_length_LSB; multiboot_header.cmdline = (uintptr_t)cmdline; draw_logo(0); if (vbe_info_width) { multiboot_header.framebuffer_addr = (uint32_t)vbe_info_fbaddr; multiboot_header.framebuffer_pitch = vbe_info_pitch; multiboot_header.framebuffer_width = vbe_info_width; multiboot_header.framebuffer_height = vbe_info_height; multiboot_header.framebuffer_bpp = vbe_info_bpp; } print("Loading kernel from 0x"); print_hex((uint32_t)kernel_load_start); print("... "); if (!load_kernel()) { print_("Failed to load kernel.\n"); return; } print("Relocating ramdisk from 0x"); print_hex((uint32_t)ramdisk_off); print(":0x"); print_hex(ramdisk_len); print(" to 0x"); print_hex(final_offset); print("... "); relocate_ramdisk(modules_mboot); finish_boot(); } #endif ================================================ FILE: boot/multiboot.h ================================================ #pragma once #define MULTIBOOT_MAGIC 0x1BADB002 #define MULTIBOOT_EAX_MAGIC 0x2BADB002 #define MULTIBOOT_FLAG_MEM 0x001 #define MULTIBOOT_FLAG_DEVICE 0x002 #define MULTIBOOT_FLAG_CMDLINE 0x004 #define MULTIBOOT_FLAG_MODS 0x008 #define MULTIBOOT_FLAG_AOUT 0x010 #define MULTIBOOT_FLAG_ELF 0x020 #define MULTIBOOT_FLAG_MMAP 0x040 #define MULTIBOOT_FLAG_DRIVE 0x080 #define MULTIBOOT_FLAG_CONFIG 0x100 #define MULTIBOOT_FLAG_LOADER 0x200 #define MULTIBOOT_FLAG_APM 0x400 #define MULTIBOOT_FLAG_VBE 0x800 #define MULTIBOOT_FLAG_FB 0x1000 struct multiboot { uint32_t flags; uint32_t mem_lower; uint32_t mem_upper; uint32_t boot_device; uint32_t cmdline; uint32_t mods_count; uint32_t mods_addr; uint32_t num; uint32_t size; uint32_t addr; uint32_t shndx; uint32_t mmap_length; uint32_t mmap_addr; uint32_t drives_length; uint32_t drives_addr; uint32_t config_table; uint32_t boot_loader_name; uint32_t apm_table; uint32_t vbe_control_info; uint32_t vbe_mode_info; uint16_t vbe_mode; uint16_t vbe_interface_seg; uint16_t vbe_interface_off; uint16_t vbe_interface_len; uint64_t framebuffer_addr; uint32_t framebuffer_pitch; uint32_t framebuffer_width; uint32_t framebuffer_height; uint8_t framebuffer_bpp; uint8_t framebuffer_type; } __attribute__ ((packed)); typedef struct { uint16_t attributes; uint8_t winA, winB; uint16_t granularity; uint16_t winsize; uint16_t segmentA, segmentB; uint32_t realFctPtr; uint16_t pitch; uint16_t Xres, Yres; uint8_t Wchar, Ychar, planes, bpp, banks; uint8_t memory_model, bank_size, image_pages; uint8_t reserved0; uint8_t red_mask, red_position; uint8_t green_mask, green_position; uint8_t blue_mask, blue_position; uint8_t rsv_mask, rsv_position; uint8_t directcolor_attributes; uint32_t physbase; uint32_t reserved1; uint16_t reserved2; } __attribute__ ((packed)) vbe_info_t; typedef struct { uint32_t mod_start; uint32_t mod_end; uint32_t cmdline; uint32_t reserved; } __attribute__ ((packed)) mboot_mod_t; typedef struct { uint32_t size; uint64_t base_addr; uint64_t length; uint32_t type; } __attribute__ ((packed)) mboot_memmap_t; extern struct multiboot *copy_multiboot(struct multiboot *mboot_ptr); extern void dump_multiboot(struct multiboot *mboot_ptr); extern char * ramdisk; extern struct multiboot * mboot_ptr; extern void boot(void); ================================================ FILE: boot/options.h ================================================ #pragma once struct option { int * value; char * title; char * description_1; char * description_2; }; extern struct option boot_options[20]; static int _boot_offset = 0; #define BOOT_OPTION(_value, default_val, option, d1, d2) \ int _value = default_val;\ boot_options[_boot_offset].value = &_value; \ boot_options[_boot_offset].title = option; \ boot_options[_boot_offset].description_1 = d1; \ boot_options[_boot_offset].description_2 = d2; \ _boot_offset++; struct bootmode { int index; char * key; char * title; }; #define BASE_SEL ((sizeof(boot_mode_names)/sizeof(*boot_mode_names))-1) extern int base_sel; #define BOOT_SET() do { \ base_sel = BASE_SEL; \ _boot_offset = 0; \ memset(boot_options, 0, sizeof(boot_options)); \ } while (0) extern char * VERSION_TEXT; extern char * HELP_TEXT; extern char * HELP_TEXT_OPT; extern char * COPYRIGHT_TEXT; extern char * LINK_TEXT; extern char * kernel_path; extern char * ramdisk_path; extern char cmdline[1024]; extern struct bootmode boot_mode_names[]; ================================================ FILE: boot/platform.c ================================================ /** * @brief Some platform-specific abstractions. * * Things like initial entry point, utility functions we need * in BIOS but don't already have, BIOS trampoline management, * and so on. * * @copyright * 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-2021 K. Lange */ extern int kmain(); #ifdef EFI_PLATFORM #include #include EFI_HANDLE ImageHandleIn; extern int init_graphics(); EFI_STATUS EFIAPI efi_main (EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { InitializeLib(ImageHandle, SystemTable); ST = SystemTable; ImageHandleIn = ImageHandle; init_graphics(); return kmain(); } #else #include #include #include "iso9660.h" #include "util.h" #include "text.h" extern char _bss_start[]; extern char _bss_end[]; void * memcpy(void * restrict dest, const void * restrict src, long n) { asm volatile("cld; rep movsb" : "=c"((int){0}) : "D"(dest), "S"(src), "c"(n) : "flags", "memory"); return dest; } void * memset(void * dest, int c, long n) { asm volatile("cld; rep stosb" : "=c"((int){0}) : "D"(dest), "a"(c), "c"(n) : "flags", "memory"); return dest; } extern void init_graphics(void); extern void do_bios_call(uint32_t function, uint32_t arg1); extern uint32_t vbe_cont_info_mode_off; extern uint16_t vbe_info_pitch; extern uint16_t vbe_info_width; extern uint16_t vbe_info_height; extern uint8_t vbe_info_bpp; extern uint16_t vbe_info; void text_reset(void) { /* Hide the cursor */ outportb(0x3D4, 14); outportb(0x3D5, 0xFF); outportb(0x3D4, 15); outportb(0x3D5, 0xFF); /* iirc this disables blink? */ inportb(0x3DA); outportb(0x3C0, 0x30); char b = inportb(0x3C1); b &= ~8; outportb(0x3c0, b); } extern int in_graphics_mode; int bios_text_mode(void) { do_bios_call(3, 3); extern char large_font[]; do_bios_call(5, (uintptr_t)large_font); vbe_info_width = 0; in_graphics_mode = 0; text_reset(); } int last_video_mode = -1; void bios_set_video(int mode) { last_video_mode = mode; do_bios_call(2, mode); do_bios_call(3, mode | 0x4000); init_graphics(); } int bios_video_mode(void) { int best_match = 0; int match_score = 0; #define MATCH(w,h,s) if (match_score < s && vbe_info_width == w && vbe_info_height == h) { best_match = *x; match_score = s; } uint32_t vbe_addr = ((vbe_cont_info_mode_off & 0xFFFF0000) >> 12) + (vbe_cont_info_mode_off & 0xFFFF); for (uint16_t * x = (uint16_t*)vbe_addr; *x != 0xFFFF; x++) { /* Query mode info */ do_bios_call(2, *x); if (!(vbe_info & (1 << 7))) continue; if (vbe_info_bpp < 24) continue; if (vbe_info_bpp == 32) { if (match_score < 9) { best_match = *x; match_score = 9; } MATCH(1024,768,10); MATCH(1280,720,50); MATCH(1280,800,60); MATCH(1440,900,75); MATCH(1920,1080,100); } else if (vbe_info_bpp == 24) { if (!match_score) { best_match = *x; match_score = 1; } MATCH(1024,768,3); MATCH(1280,720,4); MATCH(1280,800,5); MATCH(1440,900,6); MATCH(1920,1080,7); } } if (best_match) { bios_set_video(best_match); } else { vbe_info_width = 0; } } void bios_toggle_mode(void) { if (in_graphics_mode) { bios_text_mode(); } else if (last_video_mode != -1) { bios_set_video(last_video_mode); } } int bios_main(void) { /* Zero BSS */ memset(&_bss_start,0,(uintptr_t)&_bss_end-(uintptr_t)&_bss_start); text_reset(); bios_video_mode(); return kmain(); } extern volatile uint16_t dap_sectors; extern volatile uint32_t dap_buffer; extern volatile uint32_t dap_lba_low; extern volatile uint32_t dap_lba_high; extern volatile uint16_t drive_params_bps; extern uint8_t disk_space[]; int bios_call(char * into, uint32_t sector) { dap_sectors = 2048 / drive_params_bps; dap_buffer = (uint32_t)disk_space; dap_lba_low = sector * dap_sectors; dap_lba_high = 0; do_bios_call(1,0); memcpy(into, disk_space, 2048); } iso_9660_volume_descriptor_t * root = NULL; iso_9660_directory_entry_t * dir_entry = NULL; static char * dir_entries = NULL; int navigate(char * name) { dir_entry = (iso_9660_directory_entry_t*)&root->root; dir_entries = (char*)(DATA_LOAD_BASE + dir_entry->extent_start_LSB * ISO_SECTOR_SIZE); bios_call(dir_entries, dir_entry->extent_start_LSB); long offset = 0; while (1) { iso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)(dir_entries + offset); if (dir->length == 0) { if (offset < dir_entry->extent_length_LSB) { offset += 1; goto try_again; } break; } if (!(dir->flags & FLAG_HIDDEN)) { char file_name[dir->name_len + 1]; memcpy(file_name, dir->name, dir->name_len); file_name[dir->name_len] = 0; char * s = strchr(file_name,';'); if (s) { *s = '\0'; } if (!strcmp(file_name, name)) { dir_entry = dir; return 1; } } offset += dir->length; try_again: if ((long)(offset) > dir_entry->extent_length_LSB) break; } return 0; } #endif ================================================ FILE: boot/qemu.c ================================================ /** * @brief Detects if we were booted with QEMU and processes fwcfg. * * Determines if we're running in QEMU and looks for "fw_cfg" values * that can override the boot mode. * * TODO This should be perfectly capable of passing a full command * line through the fw_cfg interface, but right now we just look * at some bootmode strings... * * @copyright * 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-2021 K. Lange */ #include #include "util.h" #include "menu.h" #include "options.h" #include "text.h" struct fw_cfg_file { uint32_t size; uint16_t select; uint16_t reserved; char name[56]; }; void swap_bytes(void * in, int count) { char * bytes = in; if (count == 4) { uint32_t * t = in; *t = (bytes[0] << 24) | (bytes[1] << 12) | (bytes[2] << 8) | bytes[3]; } else if (count == 2) { uint16_t * t = in; *t = (bytes[0] << 8) | bytes[1]; } } int detect_qemu(void) { /* Try to detect qemu headless boot */ outports(0x510, 0x0000); if (inportb(0x511) == 'Q' && inportb(0x511) == 'E' && inportb(0x511) == 'M' && inportb(0x511) == 'U') { uint32_t count = 0; uint8_t * bytes = (uint8_t *)&count; outports(0x510,0x0019); for (int i = 0; i < 4; ++i) { bytes[i] = inportb(0x511); } swap_bytes(&count, 4); unsigned int bootmode_size = 0; int bootmode_index = -1; for (unsigned int i = 0; i < count; ++i) { struct fw_cfg_file file; uint8_t * tmp = (uint8_t *)&file; for (int j = 0; j < sizeof(struct fw_cfg_file); ++j) { tmp[j] = inportb(0x511); } if (!strcmp(file.name,"opt/org.toaruos.bootmode")) { swap_bytes(&file.size, 4); swap_bytes(&file.select, 2); bootmode_size = file.size; bootmode_index = file.select; } } if (bootmode_index != -1) { outports(0x510, bootmode_index); char tmp[33] = {0}; for (int i = 0; i < 32 && i < bootmode_size; ++i) { tmp[i] = inportb(0x511); } for (int i = 0; i < base_sel + 1; ++i) { if (!strcmp(tmp,boot_mode_names[i].key)) { boot_mode = boot_mode_names[i].index; return 1; } } print_("fw_cfg boot mode not recognized: "); print_(tmp); print_("\n"); } } return 0; } ================================================ FILE: boot/qemu.h ================================================ #pragma once int detect_qemu(void); ================================================ FILE: boot/text.c ================================================ /** * @brief Abstractions for text output. * * Tries to provide a common interface to text output for * EFI framebuffer, BIOS VESA framebuffer, and BIOS VGA text mode. * * I don't know why I haven't added a full printf to this. * * A lot of this could be rewritten... * * @copyright * 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-2021 K. Lange */ #include "text.h" #include "util.h" int txt_debug = 0; #ifdef EFI_PLATFORM #include extern EFI_SYSTEM_TABLE *ST; #else #include #endif #include "../apps/terminal-font.h" #define char_height LARGE_FONT_CELL_HEIGHT #define char_width LARGE_FONT_CELL_WIDTH static int offset_x = 0; static int offset_y = 0; static int center_x = 0; static int center_y = 0; static void write_char(int x, int y, int val, int attr); #ifdef EFI_PLATFORM int in_graphics_mode = 1; EFI_GRAPHICS_OUTPUT_PROTOCOL * GOP; static EFI_GUID efi_graphics_output_protocol_guid = {0x9042a9de,0x23dc,0x4a38, {0x96,0xfb,0x7a,0xde,0xd0,0x80,0x51,0x6a}}; int init_graphics() { UINTN count; EFI_HANDLE * handles; EFI_GRAPHICS_OUTPUT_PROTOCOL * gfx; EFI_STATUS status; status = uefi_call_wrapper(ST->BootServices->LocateHandleBuffer, 5, ByProtocol, &efi_graphics_output_protocol_guid, NULL, &count, &handles); if (EFI_ERROR(status)) goto no_graphics; status = uefi_call_wrapper(ST->BootServices->HandleProtocol, 3, handles[0], &efi_graphics_output_protocol_guid, (void **)&gfx); if (EFI_ERROR(status)) goto no_graphics; GOP = gfx; int total_width = GOP->Mode->Info->HorizontalResolution; int total_height = GOP->Mode->Info->VerticalResolution; offset_x = (total_width - 80 * char_width) / 2; offset_y = (total_height - 24 * char_height) / 2; center_x = total_width / 2; center_y = total_height / 2; return 0; no_graphics: return 1; } static void set_point(int x, int y, uint32_t color) { ((uint32_t *)GOP->Mode->FrameBufferBase)[(x + offset_x) + (y + offset_y) * GOP->Mode->Info->PixelsPerScanLine] = color; } void clear_() { x = 0; y = 0; memset((void*)GOP->Mode->FrameBufferBase,0,GOP->Mode->FrameBufferSize); } static void placech(unsigned char c, int x, int y, int attr) { write_char(x * char_width, y * char_height, c, attr); } #else extern uint32_t *vbe_info_fbaddr; extern uint16_t vbe_info_pitch; extern uint16_t vbe_info_width; extern uint16_t vbe_info_height; extern uint8_t vbe_info_bpp; int in_graphics_mode = 0; void init_graphics(void) { if (!vbe_info_width) return; in_graphics_mode = 1; offset_x = (vbe_info_width - 80 * char_width) / 2; offset_y = (vbe_info_height - 24 * char_height) / 2; center_x = vbe_info_width / 2; center_y = vbe_info_height / 2; } static void set_point(int x, int y, uint32_t color) { if (vbe_info_bpp == 24) { *((uint8_t*)vbe_info_fbaddr + (x + offset_x) * 3 + (y + offset_y) * (vbe_info_pitch)) = (color >> 0) & 0xFF; *((uint8_t*)vbe_info_fbaddr + (x + offset_x) * 3 + (y + offset_y) * (vbe_info_pitch) + 1) = (color >> 8) & 0xFF; *((uint8_t*)vbe_info_fbaddr + (x + offset_x) * 3 + (y + offset_y) * (vbe_info_pitch) + 2) = (color >> 16) & 0xFF; } else if (vbe_info_bpp == 32) { vbe_info_fbaddr[(x + offset_x) + (y + offset_y) * (vbe_info_pitch >> 2)] = color; } } static unsigned short * textmemptr = (unsigned short *)0xB8000; static void placech_vga(unsigned char c, int x, int y, int attr) { unsigned short *where; unsigned att = attr << 8; where = textmemptr + (y * 80 + x); *where = c | att; } static void placech(unsigned char c, int x, int y, int attr) { in_graphics_mode ? write_char(x * char_width, y * char_height, c, attr) : placech_vga(c,x,y,attr); } void clear_() { x = 0; y = 0; if (in_graphics_mode) { memset(vbe_info_fbaddr, 0, vbe_info_pitch * vbe_info_height); } else { for (int y = 0; y < 24; ++y) { for (int x = 0; x < 80; ++x) { placech_vga(' ', x, y, 0x00); } } } } #endif static uint32_t term_colors[] = { 0xFF000000, 0xFFCC0000, 0xFF4E9A06, 0xFFC4A000, 0xFF3465A4, 0xFF75507B, 0xFF06989A, 0xFFD3D7CF, 0xFF555753, 0xFFEF2929, 0xFF8AE234, 0xFFFCE94F, 0xFF729FCF, 0xFFAD7FA8, 0xFF34E2E2, 0xFFEEEEEC, }; char vga_to_ansi[] = { 0, 4, 2, 6, 1, 5, 3, 7, 8,12,10,14, 9,13,11,15 }; static void write_char(int x, int y, int val, int attr) { if (val > 128) { val = 4; } uint32_t fg_color = term_colors[vga_to_ansi[attr & 0xF]]; uint32_t bg_color = term_colors[vga_to_ansi[(attr >> 4) & 0xF]]; uint32_t colors[] = {bg_color, fg_color}; uint8_t * c = large_font[val]; for (uint8_t i = 0; i < char_height; ++i) { for (uint8_t j = 0; j < char_width; ++j) { set_point(x+j,y+i,colors[!!(c[i] & (1 << LARGE_FONT_MASK-j))]); } } } int x = 0; int y = 0; int attr = 0x07; void print_(char * str) { while (*str) { if (*str == '\n') { for (; x < 80; ++x) { placech(' ', x, y, attr); } x = 0; y += 1; if (y == 24) { y = 0; } } else { placech(*str, x, y, attr); x++; if (x == 80) { x = 0; y += 1; if (y == 24) { y = 0; } } } str++; } } void move_cursor(int _x, int _y) { x = _x; y = _y; } void set_attr(int _attr) { attr = _attr; } void print_banner(char * str) { if (!str) { for (int i = 0; i < 80; ++i) { placech(' ', i, y, attr); } y++; return; } int len = 0; char *c = str; while (*c) { len++; c++; } int off = (80 - len) / 2; for (int i = 0; i < 80; ++i) { placech((i >= off && i - off < len) ? str[i-off] : ' ', i, y, attr); } y++; } void print_hex_(unsigned int value) { char out[9] = {0}; for (int i = 7; i > -1; i--) { out[i] = "0123456789abcdef"[(value >> (4 * (7 - i))) & 0xF]; } print_(out); } void print_int_(unsigned int value) { if (value == 0) { print_("0"); return; } unsigned int n_width = 1; char tmp[32] = {0}; unsigned int val = value; while (val >= 10UL) { val /= 10UL; n_width++; } int i = n_width; while (i > 0) { tmp[i-1] = (value % 10UL) + '0'; value /= 10UL; i--; } while (i < n_width) { char t[2] = {tmp[i],'\0'}; print_(t); i++; } } static void draw_square(int x, int y, int stage) { for (int _y = 0; _y < 7; ++_y) { unsigned int color_green = 0xB2 - (y * 8 + _y) * 2; unsigned int color_blue = 0xFF; if (stage > 0 && y + 1 != stage) { color_green /= 2; color_blue /= 2; } unsigned int color = 0xFF000000 | (color_green << 8) | color_blue; for (int _x = 0; _x < 7; ++_x) { set_point(center_x - 32 - offset_x + x * 8 + _x, center_y - 32 - offset_y + y * 8 + _y, color); } } } void draw_logo(int stage) { if (!in_graphics_mode) { move_cursor(0,0); print_("Loading... "); char tmp[2] = {0}; tmp[0] = "/-\\|/-\\|"[stage]; print_(tmp); return; } uint64_t logo_squares = 0x981818181818FFFFUL; for (int y = 0; y < 8; ++y) { for (int x = 0; x < 8; ++x) { if (logo_squares & (1 << x)) { draw_square(x,y,stage); } } logo_squares >>= 8; } } ================================================ FILE: boot/text.h ================================================ #pragma once extern int txt_debug, x, y, attr; void move_cursor(int _x, int _y); void set_attr(int _attr); void print_(char * str); void print_hex_(unsigned int value); void print_int_(unsigned int value); void clear_(); void print_banner(char * str); #define print(s) do {if (txt_debug) {print_(s);}} while(0) #define clear() do {if (txt_debug) {clear_();}} while(0) #define print_hex(d) do {if (txt_debug) {print_hex_(d);}} while(0) ================================================ FILE: boot/util.c ================================================ /** * @brief Utility functions. * * Kind of a barebones libc. * * @copyright * 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-2021 K. Lange */ #include "util.h" int strcmp(const char * l, const char * r) { for (; *l == *r && *l; l++, r++); return *(unsigned char *)l - *(unsigned char *)r; } char * strchr(const char * s, int c) { while (*s) { if (*s == c) { return (char *)s; } s++; } return 0; } unsigned long strlen(const char *s) { unsigned long out = 0; while (*s) { out++; s++; } return out; } char * strcat(char *dest, const char *src) { char * end = dest; while (*end != '\0') { ++end; } while (*src) { *end = *src; end++; src++; } *end = '\0'; return dest; } ================================================ FILE: boot/util.h ================================================ #pragma once static inline unsigned short inports(unsigned short _port) { unsigned short rv; asm volatile ("inw %1, %0" : "=a" (rv) : "dN" (_port)); return rv; } static inline void outports(unsigned short _port, unsigned short _data) { asm volatile ("outw %1, %0" : : "dN" (_port), "a" (_data)); } static inline unsigned int inportl(unsigned short _port) { unsigned int rv; asm volatile ("inl %%dx, %%eax" : "=a" (rv) : "dN" (_port)); return rv; } static inline void outportl(unsigned short _port, unsigned int _data) { asm volatile ("outl %%eax, %%dx" : : "dN" (_port), "a" (_data)); } static inline unsigned char inportb(unsigned short _port) { unsigned char rv; asm volatile ("inb %1, %0" : "=a" (rv) : "dN" (_port)); return rv; } static inline void outportb(unsigned short _port, unsigned char _data) { asm volatile ("outb %1, %0" : : "dN" (_port), "a" (_data)); } static inline void inportsm(unsigned short port, unsigned char * data, unsigned long size) { asm volatile ("rep insw" : "+D" (data), "+c" (size) : "d" (port) : "memory"); } void * memcpy(void * restrict dest, const void * restrict src, long n); void * memset(void * dest, int c, long n); int strcmp(const char * l, const char * r); char * strchr(const char * s, int c); char * strcat(char *dest, const char *src); void copy_sectors(unsigned long lba, unsigned char * buf, int sectors); void copy_sector(unsigned long lba, unsigned char * buf); unsigned long strlen(const char *s); #define DATA_LOAD_BASE 0x4000000 ================================================ FILE: boot/video.c ================================================ /** * @brief Video mode management. * * Tries to abstract away differences between VESA mode setting * on BIOS and GOP mode setting on UEFI. Also provides the * video mode selection menu. * * @copyright * 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-2021 K. Lange */ #include "text.h" #include "util.h" #include "kbd.h" #ifdef EFI_PLATFORM #include extern EFI_SYSTEM_TABLE *ST; #else #include #endif int platform_count_modes(int *); int platform_list_modes(int sel, int select_this_mode); extern void init_graphics(void); void mode_selector(int sel, int ndx, char *str) { set_attr(sel == ndx ? 0x70 : 0x07); print_(str); if (x < 26) { while (x < 25) { print_(" "); } x = 26; } else if (x < 52) { while (x < 51) { print_(" "); } x = 52; } else { print_("\n"); } } static char * print_int_into(char * str, unsigned int value) { unsigned int n_width = 1; unsigned int i = 9; while (value > i && i < UINT32_MAX) { n_width += 1; i *= 10; i += 9; } char buf[n_width+1]; for (int i = 0; i < n_width + 1; i++) { buf[i] = 0; } i = n_width; while (i > 0) { unsigned int n = value / 10; int r = value % 10; buf[i - 1] = r + '0'; i--; value = n; } for (char * c = buf; *c; c++) { *str++ = *c; } return str; } int video_menu(void) { clear_(); int sel = 0; int sel_max = platform_count_modes(&sel); int select_this_mode = 0; int s = 0; do { move_cursor(0,0); set_attr(0x1f); print_banner("Select Video Mode"); set_attr(0x07); print_("\n"); if (platform_list_modes(sel,select_this_mode)) return 0; read_again: s = read_scancode(0); if (s == 0x50) { /* DOWN */ if (sel >= 0 && sel < sel_max - 1) { sel = (sel + 3) % sel_max; } else { sel = (sel + 1) % sel_max; } } else if (s == 0x48) { /* UP */ if (sel >= 1) { sel = (sel_max + sel - 3) % sel_max; } else { sel = (sel_max + sel - 1) % sel_max; } } else if (s == 0x4B) { /* LEFT */ if (sel >= 0) { if (sel % 3 != 0) { sel = (sel - 1) % sel_max; } else { sel += 2; } } } else if (s == 0x4D) { /* RIGHT */ if (sel >= 0) { if (sel % 3 != 2) { sel = (sel + 1) % sel_max; } else { sel -= 2; } } } else if (s == 0x1c) { select_this_mode = 1; continue; } else if (s == 0x01) { return 0; } else { goto read_again; } } while (1); } #ifdef EFI_PLATFORM extern EFI_GRAPHICS_OUTPUT_PROTOCOL * GOP; int platform_list_modes(int sel, int select_this_mode) { int index = 0; for (int i = 0; i < GOP->Mode->MaxMode; ++i) { EFI_STATUS status; UINTN size; EFI_GRAPHICS_OUTPUT_MODE_INFORMATION * info; status = uefi_call_wrapper(GOP->QueryMode, 4, GOP, i, &size, &info); if (EFI_ERROR(status) || info->PixelFormat != 1) { continue; } if (select_this_mode && sel == index) { uefi_call_wrapper(GOP->SetMode, 2, GOP, i); init_graphics(); return 1; } char tmp[100]; char * t = tmp; t = print_int_into(t, info->HorizontalResolution); *t = 'x'; t++; t = print_int_into(t, info->VerticalResolution); *t = '\0'; mode_selector(sel, index, tmp); index++; } return 0; } int platform_count_modes(int * current) { int index = 0; for (int i = 0; i < GOP->Mode->MaxMode; ++i) { EFI_STATUS status; UINTN size; EFI_GRAPHICS_OUTPUT_MODE_INFORMATION * info; status = uefi_call_wrapper(GOP->QueryMode, 4, GOP, i, &size, &info); if (EFI_ERROR(status) || info->PixelFormat != 1) { continue; } else { index++; } } return index; } #else extern void do_bios_call(uint32_t function, uint32_t arg1); extern uint32_t vbe_cont_info_mode_off; struct ColorFormat { uint8_t mask; uint8_t offset; }; struct VbeMode { uint16_t attributes; uint16_t old_shit; uint16_t granularity; uint16_t window_size; uint32_t segments; uint32_t old_bank_switching_thing; uint16_t pitch; uint16_t width; uint16_t height; uint16_t w_y; uint8_t planes; uint8_t bpp; uint8_t banks; uint8_t memory_model; uint8_t bank_size; uint8_t pages; uint8_t reserved; struct ColorFormat red; struct ColorFormat green; struct ColorFormat blue; struct ColorFormat alpha; uint8_t color_attributes; uint32_t framebuffer_addr; uint32_t memory_offset; uint32_t memory_size; uint8_t other[206]; } __attribute__((packed)); extern volatile struct VbeMode vbe_info; static struct VbeMode vbe_info_save; static int qualified(void) { if (!(vbe_info.attributes & (1 << 7))) return 0; if (vbe_info.bpp < 24) return 0; if (vbe_info.width < 640) return 0; if (vbe_info.height < 480) return 0; } static char tmp[40]; int platform_list_modes(int sel, int select_this_mode) { uint32_t vbe_addr = ((vbe_cont_info_mode_off & 0xFFFF0000) >> 12) + (vbe_cont_info_mode_off & 0xFFFF); memcpy(&vbe_info_save, (char*)&vbe_info, sizeof(struct VbeMode)); int index = 0; for (uint16_t * x = (uint16_t*)vbe_addr; *x != 0xFFFF; x++) { do_bios_call(2, *x); if (!qualified()) { memcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode)); continue; } if (select_this_mode && sel == index) { extern void bios_set_video(int); bios_set_video(*x); return 1; } char * t = tmp; t = print_int_into(t, vbe_info.width); *t = 'x'; t++; t = print_int_into(t, vbe_info.height); *t = 'x'; t++; t = print_int_into(t, vbe_info.bpp); *t = '\0'; memcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode)); mode_selector(sel, index, tmp); index++; } return 0; } extern int last_video_mode; int platform_count_modes(int * current) { uint32_t vbe_addr = ((vbe_cont_info_mode_off & 0xFFFF0000) >> 12) + (vbe_cont_info_mode_off & 0xFFFF); int count = 0; memcpy(&vbe_info_save, (char*)&vbe_info, sizeof(struct VbeMode)); for (uint16_t * x = (uint16_t*)vbe_addr; *x != 0xFFFF; x++) { if (*x == last_video_mode) *current = count; do_bios_call(2, *x); if (!qualified()) continue; count++; } memcpy((char*)&vbe_info, &vbe_info_save, sizeof(struct VbeMode)); return count; } #endif ================================================ FILE: build/aarch64.mk ================================================ ARCH=aarch64 ARCH_KERNEL_CFLAGS = -z max-page-size=0x1000 -nostdlib -mgeneral-regs-only -mno-outline-atomics -ffixed-x18 --sysroot=base ARCH_USER_CFLAGS = -Wno-psabi --sysroot=base TARGET=aarch64-unknown-toaru all: system system: misaka-kernel ramdisk.igz bootstub kernel8.img | $(BUILD_KRK) misaka-kernel: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o kernel/arch/aarch64/link.ld ${CC} -g -T kernel/arch/${ARCH}/link.ld ${KERNEL_CFLAGS} -o $@ ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o BOOTSTUB_OBJS = $(patsubst %.c,%.o,$(wildcard kernel/arch/aarch64/bootstub/*.c)) BOOTSTUB_OBJS += $(patsubst %.S,%.o,$(wildcard kernel/arch/aarch64/bootstub/*.S)) BOOTSTUB_OBJS += kernel/misc/kprintf.o kernel/misc/string.o bootstub: ${BOOTSTUB_OBJS} kernel/arch/aarch64/bootstub/link.ld ${CC} -g -T kernel/arch/aarch64/bootstub/link.ld ${KERNEL_CFLAGS} -o $@ ${BOOTSTUB_OBJS} RPI400_OBJS = $(patsubst %.c,%.o,$(wildcard kernel/arch/aarch64/rpi400/*.c)) RPI400_OBJS += $(patsubst %.S,%.o,$(wildcard kernel/arch/aarch64/rpi400/*.S)) RPI400_OBJS += kernel/misc/kprintf.o kernel/misc/string.o kernel/arch/aarch64/rpi400/start.o: misaka-kernel ramdisk.igz kernel8.img: ${RPI400_OBJS} kernel/arch/aarch64/rpi400/link.ld ${CC} -g -T kernel/arch/aarch64/rpi400/link.ld ${KERNEL_CFLAGS} -o $@.elf ${RPI400_OBJS} ${OC} $@.elf -O binary $@ QEMU = qemu-system-aarch64 EMU_MACH = virt EMU_CPU = cortex-a72 SMP ?= 4 RAM ?= 4G EMU_ARGS = -M $(EMU_MACH) EMU_ARGS += -m $(RAM) EMU_ARGS += -smp $(SMP) EMU_ARGS += -cpu $(EMU_CPU) EMU_ARGS += -no-reboot EMU_ARGS += -serial mon:stdio EMU_ARGS += -device ramfb EMU_ARGS += -device virtio-tablet-pci # Mouse with absolute positioning EMU_ARGS += -device virtio-keyboard-pci # Keyboard EMU_ARGS += -device AC97 EMU_ARGS += -d guest_errors EMU_ARGS += -net user EMU_ARGS += -netdev hubport,id=u1,hubid=0, -device e1000e,netdev=u1 EMU_ARGS += -name "ToaruOS ${ARCH}" EMU_RAMDISK = -fw_cfg name=opt/org.toaruos.initrd,file=ramdisk.igz EMU_KERNEL = -fw_cfg name=opt/org.toaruos.kernel,file=misaka-kernel run: system ${QEMU} ${EMU_ARGS} -kernel bootstub -append "root=/dev/ram0 migrate start=live-session ramfb vid=preset" ${EMU_RAMDISK} ${EMU_KERNEL} hvf: EMU_CPU = host -accel hvf hvf: system ${QEMU} ${EMU_ARGS} -kernel bootstub -append "root=/dev/ram0 migrate start=live-session ramfb vid=preset" ${EMU_RAMDISK} ${EMU_KERNEL} debug: system ${QEMU} ${EMU_ARGS} -kernel bootstub -append "root=/dev/ram0 migrate start=live-session vid=auto" ${EMU_RAMDISK} ${EMU_KERNEL} -d int 2>&1 BUILD_KRK=$(TOOLCHAIN)/local/bin/kuroko $(TOOLCHAIN)/local/bin/kuroko: kuroko/src/*.c mkdir -p $(TOOLCHAIN)/local/bin cc -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 ================================================ FILE: build/x86_64.mk ================================================ ARCH=x86_64 ARCH_KERNEL_CFLAGS = -mno-red-zone -fno-omit-frame-pointer -mfsgsbase -fPIE ARCH_KERNEL_CFLAGS += -mgeneral-regs-only -z max-page-size=0x1000 -nostdlib ARCH_USER_CFLAGS += -z max-page-size=0x1000 TARGET=x86_64-pc-toaru # Configs you can override. # SMP: Argument to -smp, use 1 to disable SMP. # RAM: Argument to -m, QEMU takes suffixes like "M" or "G". # EXTRA_ARGS: Added raw to the QEMU command line # EMU_KVM: Unset this (EMU_KVM=) to use TCG, or replace it with something like EMU_KVM=-enable-haxm # EMU_MACH: Argument to -M, 'pc' should be the older default in QEMU; we use q35 to test AHCI. SMP ?= 4 RAM ?= 3G EXTRA_ARGS ?= EMU_MACH ?= q35 EMU_KVM ?= -enable-kvm EMU_ARGS = -M q35 EMU_ARGS += -m $(RAM) EMU_ARGS += -smp $(SMP) EMU_ARGS += ${EMU_KVM} EMU_ARGS += -no-reboot EMU_ARGS += -serial mon:stdio EMU_ARGS += -device AC97 EMU_ARGS += -name "ToaruOS ${ARCH}" # UTC is the default setting. #EMU_ARGS += -rtc base=utc # Customize network options here. QEMU's default is an e1000(e) under PIIX (Q35), with user networking # so we don't need to do anything normally. #EMU_ARGS += -net user #EMU_ARGS += -netdev hubport,id=u1,hubid=0, -device e1000e,netdev=u1 -object filter-dump,id=f1,netdev=u1,file=qemu-e1000e.pcap #EMU_ARGS += -netdev hubport,id=u2,hubid=0, -device e1000e,netdev=u2 # Add an XHCI tablet if you want to dev on USB #EMU_ARGS += -device qemu-xhci -device usb-tablet all: system system: image.iso run: system ${EMU} ${EMU_ARGS} -cdrom image.iso fast: system ${EMU} ${EMU_ARGS} -cdrom image.iso \ -fw_cfg name=opt/org.toaruos.bootmode,string=normal \ run-vga: system ${EMU} ${EMU_ARGS} -cdrom image.iso \ -fw_cfg name=opt/org.toaruos.bootmode,string=vga \ test: system ${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" \ -nographic -no-reboot -audiodev none,id=id -serial null -serial mon:stdio \ -device qemu-xhci -device usb-tablet -trace "usb*" shell: system ${EMU} -M ${EMU_MACH} -m $(RAM) -smp $(SMP) ${EMU_KVM} -cdrom image.iso \ -nographic -no-reboot -audiodev none,id=id -serial null -serial mon:stdio \ -fw_cfg name=opt/org.toaruos.gettyargs,string="-a local /dev/ttyS1 115200 ${TERM}" \ -fw_cfg name=opt/org.toaruos.bootmode,string=headless misaka-kernel: ${KERNEL_ASMOBJS} ${KERNEL_OBJS} kernel/symbols.o ${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 cp $@.64 $@ ${STRIP} $@ # Loader stuff, legacy CDs fatbase/ramdisk.igz: ramdisk.igz cp $< $@ fatbase/kernel: misaka-kernel cp $< $@ cdrom/fat.img: fatbase/ramdisk.igz fatbase/kernel fatbase/efi/boot/bootx64.efi util/mkdisk.sh | dirs util/mkdisk.sh $@ fatbase cdrom/boot.sys: boot/bios/boot.o $(patsubst boot/%.c,boot/bios/%.o,$(wildcard boot/*.c)) boot/link.ld | dirs ${LD} -melf_i386 -T boot/link.ld -o $@ boot/bios/boot.o $(patsubst boot/%.c,boot/bios/%.o,$(wildcard boot/*.c)) boot/bios/%.o: boot/%.c boot/*.h | dirs ${CC} -m32 -c -Os -fno-pic -fno-pie -fno-strict-aliasing -finline-functions -ffreestanding -mgeneral-regs-only -o $@ $< boot/bios/boot.o: boot/boot.S | dirs ${AS} --32 -o $@ $< 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 EFI_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 boot/efi/%.o: boot/%.c boot/*.h | dirs $(CC) ${EFI_CFLAGS} -I /usr/include/efi/x86_64 -DEFI_FUNCTION_WRAPPER -c -o $@ $< boot/efi64.so: $(patsubst boot/%.c,boot/efi/%.o,$(wildcard boot/*.c)) boot/*.h $(LD) $(patsubst boot/%.c,boot/efi/%.o,$(wildcard boot/*.c)) ${EFI_LINK} -o $@ fatbase/efi/boot/bootx64.efi: boot/efi64.so mkdir -p fatbase/efi/boot objcopy ${EFI_SECTIONS} --target=efi-app-x86_64 $< $@ BUILD_KRK=$(TOOLCHAIN)/local/bin/kuroko $(TOOLCHAIN)/local/bin/kuroko: kuroko/src/*.c mkdir -p $(TOOLCHAIN)/local/bin cc -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 image.iso: cdrom/fat.img cdrom/boot.sys boot/mbr.S util/update-extents.krk | $(BUILD_KRK) xorriso -as mkisofs -R -J -c bootcat \ -b boot.sys -no-emul-boot -boot-load-size full \ -eltorito-alt-boot -e fat.img -no-emul-boot -isohybrid-gpt-basdat \ -o image.iso cdrom ${AS} --32 $$(kuroko util/make_mbr.krk) -o boot/mbr.o boot/mbr.S ${LD} -melf_i386 -T boot/link.ld -o boot/mbr.sys boot/mbr.o tail -c +513 image.iso > image.dat cat boot/mbr.sys image.dat > image.iso rm image.dat kuroko util/update-extents.krk ================================================ FILE: kernel/arch/aarch64/arch.c ================================================ /** * @file kernel/arch/aarch64/arch.c * @brief Global functions with arch-specific implementations. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include /** * @brief Enter userspace. * * Called by process startup. * Does not return. * * @param entrypoint Address to "return" to in userspace. * @param argc Number of arguments to provide to the new process. * @param argv Argument array to pass to the new process; make sure this is user-accessible! * @param envp Environment strings array * @param stack Userspace stack address. */ void arch_enter_user(uintptr_t entrypoint, int argc, char * argv[], char * envp[], uintptr_t stack) { asm volatile( "msr ELR_EL1, %0\n" /* entrypoint */ "msr SP_EL0, %1\n" /* stack */ "msr SPSR_EL1, %2\n" /* EL 0 */ :: "r"(entrypoint), "r"(stack), "r"(0)); update_process_times_on_exit(); register uint64_t x0 __asm__("x0") = argc; register uint64_t x1 __asm__("x1") = (uintptr_t)argv; register uint64_t x2 __asm__("x2") = (uintptr_t)envp; register uint64_t x4 __asm__("x4") = (uintptr_t)this_core->sp_el1; asm volatile( "mov sp, x4\n" "eret" :: "r"(x0), "r"(x1), "r"(x2), "r"(x4)); __builtin_unreachable(); } static void _kill_it(uintptr_t addr, const char * action, const char * desc, size_t size) { dprintf("core %d (pid=%d %s): invalid stack for signal %s (%#zx '%s' %zu)\n", this_core->cpu_id, this_core->current_process->id, this_core->current_process->name, action, addr, desc, size); task_exit(((128 + SIGSEGV) << 8) | SIGSEGV); } #define PUSH(stack, type, item) do { \ stack -= sizeof(type); \ if (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),MMU_PTR_WRITE)) \ _kill_it((uintptr_t)stack,"entry",#item,sizeof(type)); \ *((volatile type *) stack) = item; \ } while (0) #define POP(stack, type, item) do { \ if (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),0)) \ _kill_it((uintptr_t)stack,"return",#item,sizeof(type)); \ item = *((volatile type *) stack); \ stack += sizeof(type); \ } while (0) int arch_return_from_signal_handler(struct regs *r) { uintptr_t spsr; uintptr_t sp = r->user_sp; /* Restore floating point */ POP(sp, uintptr_t, this_core->current_process->thread.context.saved[13]); POP(sp, uintptr_t, this_core->current_process->thread.context.saved[12]); for (int i = 0; i < 64; ++i) { POP(sp, uint64_t, this_core->current_process->thread.fp_regs[63-i]); } arch_restore_floating((process_t*)this_core->current_process); POP(sp, sigset_t, this_core->current_process->blocked_signals); long originalSignal; POP(sp, long, originalSignal); /* Interrupt system call status */ POP(sp, long, this_core->current_process->interrupted_system_call); /* Process state */ POP(sp, uintptr_t, spsr); this_core->current_process->thread.context.saved[11] = (spsr & 0xf0000000); asm volatile ("msr SPSR_EL1, %0" :: "r"(this_core->current_process->thread.context.saved[11])); POP(sp, uintptr_t, this_core->current_process->thread.context.saved[10]); asm volatile ("msr ELR_EL1, %0" :: "r"(this_core->current_process->thread.context.saved[10])); /* Interrupt context registers */ POP(sp, struct regs, *r); asm volatile ("msr SP_EL0, %0" :: "r"(r->user_sp)); return originalSignal; } /** * @brief Enter a userspace signal handler. * * Similar to @c arch_enter_user but also setups up magic return addresses. * * Since signal handlers do to take complicated argument arrays, this only * supplies a @p signum argument. * * Does not return. * * @param entrypoint Userspace address of the signal handler, set by the process. * @param signum Signal number that caused this entry. */ void arch_enter_signal_handler(uintptr_t entrypoint, int signum, struct regs *r) { uintptr_t sp = (r->user_sp - 128) & 0xFFFFFFFFFFFFFFF0; /* Save essential registers */ PUSH(sp, struct regs, *r); /* Save context that wasn't pushed above... */ asm volatile ("mrs %0, ELR_EL1" : "=r"(this_core->current_process->thread.context.saved[10])); PUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[10]); asm volatile ("mrs %0, SPSR_EL1" : "=r"(this_core->current_process->thread.context.saved[11])); PUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[11]); PUSH(sp, long, this_core->current_process->interrupted_system_call); this_core->current_process->interrupted_system_call = 0; PUSH(sp, long, signum); PUSH(sp, sigset_t, this_core->current_process->blocked_signals); struct signal_config * config = (struct signal_config*)&this_core->current_process->signals[signum]; this_core->current_process->blocked_signals |= config->mask | (config->flags & SA_NODEFER ? 0 : (1UL << signum)); /* Save floating point */ arch_save_floating((process_t*)this_core->current_process); for (int i = 0; i < 64; ++i) { PUSH(sp, uint64_t, this_core->current_process->thread.fp_regs[i]); } PUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[12]); PUSH(sp, uintptr_t, this_core->current_process->thread.context.saved[13]); update_process_times_on_exit(); asm volatile( "msr ELR_EL1, %0\n" /* entrypoint */ "msr SP_EL0, %1\n" /* stack */ "msr SPSR_EL1, %2\n" /* spsr from context */ :: "r"(entrypoint), "r"(sp), "r"(0)); register uint64_t x0 __asm__("x0") = signum; register uint64_t x30 __asm__("x30") = 0x516; register uint64_t x4 __asm__("x4") = (uintptr_t)this_core->sp_el1; asm volatile( "mov sp, x4\n" "eret\nnop\nnop" :: "r"(x0), "r"(x30), "r"(x4)); __builtin_unreachable(); } /** * @brief Save FPU registers for this thread. */ void arch_restore_floating(process_t * proc) { asm volatile ( "ldr q0 , [%0, #(0 * 16)]\n" "ldr q1 , [%0, #(1 * 16)]\n" "ldr q2 , [%0, #(2 * 16)]\n" "ldr q3 , [%0, #(3 * 16)]\n" "ldr q4 , [%0, #(4 * 16)]\n" "ldr q5 , [%0, #(5 * 16)]\n" "ldr q6 , [%0, #(6 * 16)]\n" "ldr q7 , [%0, #(7 * 16)]\n" "ldr q8 , [%0, #(8 * 16)]\n" "ldr q9 , [%0, #(9 * 16)]\n" "ldr q10, [%0, #(10 * 16)]\n" "ldr q11, [%0, #(11 * 16)]\n" "ldr q12, [%0, #(12 * 16)]\n" "ldr q13, [%0, #(13 * 16)]\n" "ldr q14, [%0, #(14 * 16)]\n" "ldr q15, [%0, #(15 * 16)]\n" "ldr q16, [%0, #(16 * 16)]\n" "ldr q17, [%0, #(17 * 16)]\n" "ldr q18, [%0, #(18 * 16)]\n" "ldr q19, [%0, #(19 * 16)]\n" "ldr q20, [%0, #(20 * 16)]\n" "ldr q21, [%0, #(21 * 16)]\n" "ldr q22, [%0, #(22 * 16)]\n" "ldr q23, [%0, #(23 * 16)]\n" "ldr q24, [%0, #(24 * 16)]\n" "ldr q25, [%0, #(25 * 16)]\n" "ldr q26, [%0, #(26 * 16)]\n" "ldr q27, [%0, #(27 * 16)]\n" "ldr q28, [%0, #(28 * 16)]\n" "ldr q29, [%0, #(29 * 16)]\n" "ldr q30, [%0, #(30 * 16)]\n" "ldr q31, [%0, #(31 * 16)]\n" "msr fpcr, %1\n" "msr fpsr, %2\n" ::"r"(&proc->thread.fp_regs), "r"(proc->thread.context.saved[12]), "r"(proc->thread.context.saved[13]) ); } /** * @brief Restore FPU registers for this thread. */ void arch_save_floating(process_t * proc) { asm volatile ( "str q0 , [%2, #(0 * 16)]\n" "str q1 , [%2, #(1 * 16)]\n" "str q2 , [%2, #(2 * 16)]\n" "str q3 , [%2, #(3 * 16)]\n" "str q4 , [%2, #(4 * 16)]\n" "str q5 , [%2, #(5 * 16)]\n" "str q6 , [%2, #(6 * 16)]\n" "str q7 , [%2, #(7 * 16)]\n" "str q8 , [%2, #(8 * 16)]\n" "str q9 , [%2, #(9 * 16)]\n" "str q10, [%2, #(10 * 16)]\n" "str q11, [%2, #(11 * 16)]\n" "str q12, [%2, #(12 * 16)]\n" "str q13, [%2, #(13 * 16)]\n" "str q14, [%2, #(14 * 16)]\n" "str q15, [%2, #(15 * 16)]\n" "str q16, [%2, #(16 * 16)]\n" "str q17, [%2, #(17 * 16)]\n" "str q18, [%2, #(18 * 16)]\n" "str q19, [%2, #(19 * 16)]\n" "str q20, [%2, #(20 * 16)]\n" "str q21, [%2, #(21 * 16)]\n" "str q22, [%2, #(22 * 16)]\n" "str q23, [%2, #(23 * 16)]\n" "str q24, [%2, #(24 * 16)]\n" "str q25, [%2, #(25 * 16)]\n" "str q26, [%2, #(26 * 16)]\n" "str q27, [%2, #(27 * 16)]\n" "str q28, [%2, #(28 * 16)]\n" "str q29, [%2, #(29 * 16)]\n" "str q30, [%2, #(30 * 16)]\n" "str q31, [%2, #(31 * 16)]\n" "mrs %0, fpcr\n" "mrs %1, fpsr\n" : "=r"(proc->thread.context.saved[12]), "=r"(proc->thread.context.saved[13]) :"r"(&proc->thread.fp_regs) :"memory"); } /** * @brief Prepare for a fatal event by stopping all other cores. */ void arch_fatal_prepare(void) { if (processor_count > 1) { gic_send_sgi(2,-1); } } /** * @brief Halt all processors, including this one. * @see arch_fatal_prepare */ void arch_fatal(void) { arch_fatal_prepare(); while (1) { asm volatile ("wfi"); } } void arch_wakeup_others(void) { #if 1 for (int i = 0; i < processor_count; i++) { if (i == this_core->cpu_id) continue; if (!processor_local_data[i].current_process || (processor_local_data[i].current_process != processor_local_data[i].kernel_idle_task)) continue; gic_send_sgi(1,i); } #endif } /** * @brief Reboot the computer. * * At least on 'virt', there's a system control * register we can write to to reboot or at least * do a full shutdown. */ long arch_reboot(void) { arch_fatal_prepare(); /* Ensure other cores stop. */ /* This semihosting SYS_EXIT interface only works under TCG */ if (args_present("semihosting")) { uint64_t payload[2] = {0x20026, 0x0}; register uint32_t w0 asm("w0") = 0x18; register uint64_t x1 asm("x1") = (uintptr_t)payload; asm volatile ("hlt #0xF000" :: "r"(w0), "r"(x1) : "memory"); } uint32_t * psci = dtb_find_node("psci"); if (psci) { /* Try to force the QEMU SYSTEM_RESET interface if we have PSCI at all. */ register uint64_t x0 asm("x0") = 0x84000009; register uint64_t x1 asm("x1") = 0; asm volatile ("hvc 0" :: "r"(x0), "r"(x1) : "memory"); } dprintf("aarch64: No reboot method. Halting.\n"); arch_fatal(); return 0; } void aarch64_regs(struct regs *r) { #define reg(a,b) printf(" X%02d=0x%016zx X%02d=0x%016zx\n",a,r->x ## a, b, r->x ## b) reg(0,1); reg(2,3); reg(4,5); reg(6,7); reg(8,9); reg(10,11); reg(12,13); reg(14,15); reg(16,17); reg(18,19); reg(20,21); reg(22,23); reg(24,25); reg(26,27); reg(28,29); printf(" X30=0x%016zx SP=0x%016zx\n", r->x30, r->user_sp); #undef reg } void aarch64_context(process_t * proc) { printf(" SP=0x%016zx BP(x29)=0x%016zx\n", proc->thread.context.sp, proc->thread.context.bp); printf(" IP=0x%016zx TLSBASE=0x%016zx\n", proc->thread.context.ip, proc->thread.context.tls_base); printf(" X19=0x%016zx X20=%016zx\n", proc->thread.context.saved[0], proc->thread.context.saved[1]); printf(" X21=0x%016zx X22=%016zx\n", proc->thread.context.saved[2], proc->thread.context.saved[3]); printf(" X23=0x%016zx X24=%016zx\n", proc->thread.context.saved[4], proc->thread.context.saved[5]); printf(" X25=0x%016zx X26=%016zx\n", proc->thread.context.saved[6], proc->thread.context.saved[7]); printf(" X27=0x%016zx X28=%016zx\n", proc->thread.context.saved[8], proc->thread.context.saved[9]); printf(" ELR=0x%016zx SPSR=%016zx\n", proc->thread.context.saved[10], proc->thread.context.saved[11]); printf("fpcr=0x%016zx fpsr=%016zx\n", proc->thread.context.saved[12], proc->thread.context.saved[13]); } /* Syscall parameter accessors */ void arch_syscall_return(struct regs * r, long retval) { r->x0 = retval; } long arch_syscall_number(struct regs * r) { return r->x0; } long arch_syscall_arg0(struct regs * r) { return r->x1; } long arch_syscall_arg1(struct regs * r) { return r->x2; } long arch_syscall_arg2(struct regs * r) { return r->x3; } long arch_syscall_arg3(struct regs * r) { return r->x4; } long arch_syscall_arg4(struct regs * r) { return r->x5; } long arch_stack_pointer(struct regs * r) { return r->user_sp; } long 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 */ } /* No port i/o on arm, but these are still littered around some * drivers we need to remove... */ unsigned short inports(unsigned short _port) { return 0; } unsigned int inportl(unsigned short _port) { return 0; } unsigned char inportb(unsigned short _port) { return 0; } void inportsm(unsigned short port, unsigned char * data, unsigned long size) { } void outports(unsigned short _port, unsigned short _data) { } void outportl(unsigned short _port, unsigned int _data) { } void outportb(unsigned short _port, unsigned char _data) { } void outportsm(unsigned short port, unsigned char * data, unsigned long size) { } /* From DRM sources */ #define fourcc_code(a, b, c, d) ((uint32_t)(a) | ((uint32_t)(b) << 8) | \ ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24)) #define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */ static void ramfb_init(void) { extern uint8_t * lfb_vid_memory; extern uint16_t lfb_resolution_x; extern uint16_t lfb_resolution_y; extern uint16_t lfb_resolution_b; extern uint32_t lfb_resolution_s; extern size_t lfb_memsize; struct ramfbcfg { uint64_t addr; uint32_t fourcc; uint32_t flags; uint32_t width; uint32_t height; uint32_t stride; } __attribute__((packed)); uint32_t * fw_cfg = dtb_find_node_prefix("fw-cfg"); if (!fw_cfg) { dprintf("ramfb: no fw-cfg interface?\n"); return; } uint32_t * regs = dtb_node_find_property(fw_cfg, "reg"); if (!regs) { dprintf("ramfb: no regs for fw-cfg\n"); return; } volatile uint8_t * fw_cfg_addr = (volatile uint8_t*)(uintptr_t)(mmu_map_from_physical(swizzle(regs[3]))); volatile uint64_t * fw_cfg_data = (volatile uint64_t *)fw_cfg_addr; volatile uint32_t * fw_cfg_32 = (volatile uint32_t *)fw_cfg_addr; volatile uint16_t * fw_cfg_sel = (volatile uint16_t *)(fw_cfg_addr + 8); *fw_cfg_sel = 0; uint64_t response = fw_cfg_data[0]; (void)response; /* Needs to be big-endian */ *fw_cfg_sel = swizzle16(0x19); /* count response is 32-bit BE */ uint32_t count = swizzle(fw_cfg_32[0]); struct fw_cfg_file { uint32_t size; uint16_t select; uint16_t reserved; char name[56]; }; struct fw_cfg_file file; uint8_t * tmp = (uint8_t *)&file; /* Read count entries */ for (unsigned int i = 0; i < count; ++i) { for (unsigned int j = 0; j < sizeof(struct fw_cfg_file); ++j) { tmp[j] = fw_cfg_addr[0]; } file.size = swizzle(file.size); file.select = swizzle16(file.select); if (!strcmp(file.name,"etc/ramfb")) { static volatile struct ramfbcfg tmp; uintptr_t z = mmu_map_to_physical(NULL,(uint64_t)&tmp); lfb_resolution_x = 1440; lfb_resolution_y = 900; lfb_resolution_s = lfb_resolution_x*4; lfb_resolution_b = 32; lfb_memsize = lfb_resolution_s * lfb_resolution_y; uint64_t frames = lfb_memsize/4096; if ((lfb_memsize/4096)*4096!=lfb_memsize) frames++; uint64_t addr = mmu_allocate_n_frames(frames) << 12; lfb_vid_memory = mmu_map_from_physical(addr); /* Clear it while we're here */ memset(lfb_vid_memory, 0, lfb_memsize); tmp.addr = swizzle64(addr); tmp.width = swizzle(lfb_resolution_x); tmp.height = swizzle(lfb_resolution_y); tmp.flags = 0; tmp.fourcc = swizzle(DRM_FORMAT_XRGB8888); tmp.stride = swizzle(lfb_resolution_s); static volatile struct fwcfg_dma { volatile uint32_t control; volatile uint32_t length; volatile uint64_t address; } dma __attribute__((aligned(4096))); dma.control = swizzle((file.select << 16) | (1 << 3) | (1 << 4)); dma.length = swizzle(sizeof(struct ramfbcfg)); dma.address = swizzle64(z); asm volatile ("isb" ::: "memory"); fw_cfg_data[2] = swizzle64(mmu_map_to_physical(NULL,(uint64_t)&dma)); asm volatile ("isb" ::: "memory"); break; } } } void arch_framebuffer_initialize(void) { /* I'm not sure we have any options here... * lfbvideo calls this expecting it to fill in information * on a preferred video mode; maybe dtb has that? */ if (args_present("ramfb")) { ramfb_init(); } } char * _arch_args = NULL; const char * arch_get_cmdline(void) { /* this should be available from dtb directly as a string */ extern char * _arch_args; return _arch_args ? _arch_args : ""; } const char * arch_get_loader(void) { return ""; } /* These should probably assembly. */ void arch_enter_tasklet(void) { asm volatile ( "ldp x0, x1, [sp], #16\n" "br x1\n" ::: "memory"); __builtin_unreachable(); } static spin_lock_t deadlock_lock = { 0 }; void _spin_panic(const char * lock_name, spin_lock_t * target) { arch_fatal_prepare(); while (__sync_lock_test_and_set(deadlock_lock.latch, 0x01)); dprintf("core %d took over five seconds waiting to acquire %s (owner=%d in %s)\n", this_core->cpu_id, lock_name, target->owner - 1, target->func); //arch_dump_traceback(); __sync_lock_release(deadlock_lock.latch); arch_fatal(); } void arch_spin_lock_acquire(const char * name, spin_lock_t * target, const char * func) { #if 0 uint64_t expire = arch_perf_timer() + 5000000UL * arch_cpu_mhz(); #endif /* "loss of an exclusive monitor" is one of the things that causes an "event", * so we spin on wfe to try to load-acquire the latch */ asm volatile ( "sevl\n" /* And to avoid multiple jumps, we put the wfe first, so sevl will slide past the first one */ "1:\n" " wfe\n" #if 0 ); /* Yes, we can splice these assembly snippets with the clock check, this works fine. * If we've been trying to load the latch for five seconds, panic. */ if (arch_perf_timer() > expire) { _spin_panic(name, target); } asm volatile ( #endif "2:\n" " ldaxr w2, [ %1 ]\n" /* Acquire exclusive monitor and load latch value */ " 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 */ " stxr w2, %0, [ %1 ]\n" /* Store our core number as the latch value. */ " cbnz w2, 2b\n" /* If we failed to exclusively store, try to load again */ ::"r"(this_core->cpu_id+1),"r"(target->latch) : "x2","cc","memory"); /* Set these for compatibility because there's at least one place we check them; * TODO: just use the latch value since we're setting it to the same thing anyway? */ target->owner = this_core->cpu_id + 1; /* This is purely for debugging */ target->func = func; } void arch_spin_lock_release(spin_lock_t * target) { /* Clear owner debug data */ target->owner = 0; target->func = NULL; /* Release the exclusive monitor and clear the latch value */ __atomic_store_n(target->latch, 0, __ATOMIC_RELEASE); } ================================================ FILE: kernel/arch/aarch64/bootstub/bootstrap.S ================================================ .extern __bootstrap_stack_top .globl start start: ldr x30, =__bootstrap_stack_top mov sp, x30 bl kmain hang: b hang ================================================ FILE: kernel/arch/aarch64/bootstub/link.ld ================================================ OUTPUT_FORMAT(elf64-littleaarch64) ENTRY(start) SECTIONS { . = 0x40100000; phys = .; .text BLOCK(4K) : ALIGN(4K) { *(.bootstrap) code = .; *(.text) } .rodata BLOCK(4K) : ALIGN(4K) { *(.rodata) } .data BLOCK(4K) : ALIGN(4K) { data = .; *(.data) *(.symbols) PROVIDE(kernel_symbols_start = .); PROVIDE(kernel_symbols_end = .); PROVIDE(bss_start = .); } .bss BLOCK(4K) : ALIGN(4K) { bss = .; *(COMMON) *(.bss) *(.stack) } /* Some built-in stack space... */ . = ALIGN(0x1000); . = . + 0x1000; __bootstrap_stack_top = .; end = .; /DISCARD/ : { *(.comment) *(.eh_frame) *(.note.gnu.build-id) } } ================================================ FILE: kernel/arch/aarch64/bootstub/main.c ================================================ /** * @file kernel/arch/aarch64/bootstub/main.c * @brief Shim loader for QEMU virt machine. * * Loads at 0x4010_0000 where RAM is, sets up the MMU to have RAM * at our kernel virtual load address (0xffff_ffff_8000_0000), as * well as a direct mapping at -512GB for access to IO devices, * reads the kernel out of fw-cfg, loads it to the kernel virtual * load address, and then jumps to it. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2022 K. Lange */ #include #include #include #include //#define BOOTSTUB_LOG_ENABLED #define QEMU_DTB_BASE 0x40000000UL #define KERNEL_PHYS_BASE 0x41000000UL static uint32_t swizzle(uint32_t from) { uint8_t a = from >> 24; uint8_t b = from >> 16; uint8_t c = from >> 8; uint8_t d = from; return (d << 24) | (c << 16) | (b << 8) | (a); } void * malloc(size_t x) { printf("panic\n"); while (1); } static uint64_t swizzle64(uint64_t from) { uint8_t a = from >> 56; uint8_t b = from >> 48; uint8_t c = from >> 40; uint8_t d = from >> 32; uint8_t e = from >> 24; uint8_t f = from >> 16; uint8_t g = from >> 8; uint8_t h = from; return ((uint64_t)h << 56) | ((uint64_t)g << 48) | ((uint64_t)f << 40) | ((uint64_t)e << 32) | (d << 24) | (c << 16) | (b << 8) | (a); } static uint16_t swizzle16(uint16_t from) { uint8_t a = from >> 8; uint8_t b = from; return (b << 8) | (a); } struct fdt_header { uint32_t magic; uint32_t totalsize; uint32_t off_dt_struct; uint32_t off_dt_strings; uint32_t off_mem_rsvmap; uint32_t version; uint32_t last_comp_version; uint32_t boot_cpuid_phys; uint32_t size_dt_strings; uint32_t size_dt_struct; }; static uint32_t * parse_node(uint32_t * node, char * strings, int x) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 9) return NULL; if (swizzle(*node) != 1) { printf("Not a node? Got %x\n", swizzle(*node)); return NULL; } /* Skip the BEGIN_NODE */ node++; for (int i = 0; i < x; ++i) printf(" "); while (1) { char * x = (char*)node; if (x[0]) { printf("%c",x[0]); } else { node++; break; } if (x[1]) { printf("%c",x[1]); } else { node++; break; } if (x[2]) { printf("%c",x[2]); } else { node++; break; } if (x[3]) { printf("%c",x[3]); } else { node++; break; } node++; } printf("\n"); while (1) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 2) return node+1; if (swizzle(*node) == 3) { for (int i = 0; i < x; ++i) printf(" "); uint32_t len = swizzle(node[1]); uint32_t nameoff = swizzle(node[2]); printf(" property %s len=%u\n", strings + nameoff, len); node += 3; node += (len + 3) / 4; } else if (swizzle(*node) == 1) { node = parse_node(node, strings, x + 1); } } } static void dump_dtb(uintptr_t addr) { struct fdt_header * fdt = (struct fdt_header*)addr; #define P(o) printf(#o " = %#x\n", swizzle(fdt-> o)) P(magic); P(totalsize); P(off_dt_struct); P(off_dt_strings); P(off_mem_rsvmap); P(version); P(last_comp_version); P(boot_cpuid_phys); P(size_dt_strings); P(size_dt_struct); char * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings)); uint32_t * dtb_struct = (uint32_t *)(addr + swizzle(fdt->off_dt_struct)); parse_node(dtb_struct, dtb_strings, 0); } static uint32_t * find_subnode(uint32_t * node, char * strings, const char * name, uint32_t ** node_out, int (*cmp)(const char* a, const char *b)) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 9) return NULL; if (swizzle(*node) != 1) return NULL; node++; if (cmp((char*)node,name)) { *node_out = node; return NULL; } while ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++; node++; while (1) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 2) return node+1; if (swizzle(*node) == 3) { uint32_t len = swizzle(node[1]); node += 3; node += (len + 3) / 4; } else if (swizzle(*node) == 1) { node = find_subnode(node, strings, name, node_out, cmp); if (!node) return NULL; } } } static uint32_t * find_node_int(const char * name, int (*cmp)(const char*,const char*)) { uintptr_t addr = QEMU_DTB_BASE; struct fdt_header * fdt = (struct fdt_header*)addr; char * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings)); uint32_t * dtb_struct = (uint32_t *)(addr + swizzle(fdt->off_dt_struct)); uint32_t * out = NULL; find_subnode(dtb_struct, dtb_strings, name, &out, cmp); return out; } static int base_cmp(const char *a, const char *b) { return !strcmp(a,b); } static uint32_t * find_node(const char * name) { return find_node_int(name,base_cmp); } static int prefix_cmp(const char *a, const char *b) { return !memcmp(a,b,strlen(b)); } static uint32_t * find_node_prefix(const char * name) { return find_node_int(name,prefix_cmp); } static uint32_t * node_find_property_int(uint32_t * node, char * strings, const char * property, uint32_t ** out) { while ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++; node++; while (1) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 2) return node+1; if (swizzle(*node) == 3) { uint32_t len = swizzle(node[1]); uint32_t nameoff = swizzle(node[2]); if (!strcmp(strings + nameoff, property)) { *out = &node[1]; return NULL; } node += 3; node += (len + 3) / 4; } else if (swizzle(*node) == 1) { node = node_find_property_int(node+1, strings, property, out); if (!node) return NULL; } } } static uint32_t * node_find_property(uint32_t * node, const char * property) { uintptr_t addr = QEMU_DTB_BASE; struct fdt_header * fdt = (struct fdt_header*)addr; char * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings)); uint32_t * out = NULL; node_find_property_int(node, dtb_strings, property, &out); return out; } static size_t _early_log_write(size_t size, uint8_t * buffer) { #ifdef BOOTSTUB_LOG_ENABLED for (unsigned int i = 0; i < size; ++i) { *(volatile unsigned int *)(0x09000000) = buffer[i]; } #endif return size; } static size_t _later_log_write(size_t size, uint8_t * buffer) { #ifdef BOOTSTUB_LOG_ENABLED for (unsigned int i = 0; i < size; ++i) { *(volatile unsigned int *)(0xffffff8009000000) = buffer[i]; } #endif return size; } static struct BaseTables { uintptr_t l0_base[512]; uintptr_t l1_high_gbs[512]; uintptr_t l1_low_gbs[512]; uintptr_t l2_kernel[512]; } _baseTables __attribute__((aligned(4096))); #define PTE_VALID (1UL << 0) #define PTE_TABLE (1UL << 1) /* Table attributes */ #define PTE_NSTABLE (1UL << 63) #define PTE_APTABLE (3UL << 61) /* two bits */ #define PTE_APTABLE_A (1UL << 62) #define PTE_APTABLE_B (1UL << 61) #define PTE_UXNTABLE (1UL << 60) #define PTE_PXNTABLE (1UL << 59) /* Block attributes */ #define PTE_UXN (1UL << 54) #define PTE_PXN (1UL << 53) #define PTE_CONTIGUOUS (1UL << 52) #define PTE_NG (1UL << 11) #define PTE_AF (1UL << 10) #define PTE_SH (3UL << 8) /* two bits */ #define PTE_SH_A (1UL << 9) #define PTE_SH_B (1UL << 8) #define PTE_AP (3UL << 6) /* two bits */ #define PTE_AP_A (1UL << 7) #define PTE_AP_B (1UL << 6) #define PTE_NS (1UL << 5) #define PTE_ATTRINDX (7UL << 2) /* three bits */ #define PTE_ATTR_A (1UL << 4) #define PTE_ATTR_B (1UL << 3) #define PTE_ATTR_C (1UL << 2) static void bootstub_mmu_init(void) { /* Map memory */ _baseTables.l0_base[0] = (uintptr_t)&_baseTables.l1_low_gbs | PTE_VALID | PTE_TABLE | PTE_AF; /* equivalent to high_base_pml */ _baseTables.l0_base[511] = (uintptr_t)&_baseTables.l1_high_gbs | PTE_VALID | PTE_TABLE | PTE_AF; /* Mapping for us */ _baseTables.l1_low_gbs[1] = QEMU_DTB_BASE | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); /* -512GB is a map of 64GB of memory */ for (size_t i = 0; i < 64; ++i) { _baseTables.l1_high_gbs[i] = (i << 30) | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); } /* -2GiB, map kernel here */ _baseTables.l1_high_gbs[510] = (uintptr_t)&_baseTables.l2_kernel | PTE_VALID | PTE_TABLE | PTE_AF; for (size_t i = 0; i < 512; ++i) { _baseTables.l2_kernel[i] = (KERNEL_PHYS_BASE + (i << 21)) | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); } uint64_t sctlr = 0 | (1UL << 0) /* mmu enabled */ | (1UL << 2) /* cachability */ //| (1UL << 6) | (1UL << 12) /* instruction cachability */ | (1UL << 23) /* SPAN */ | (1UL << 28) /* nTLSMD */ | (1UL << 29) /* LSMAOE */ | (1UL << 20) /* TSCXT */ | (1UL << 7) /* ITD */ ; /* Translate control register */ uint64_t tcr = 0 | (3UL << 32) /* IPS 4TB? */ | (2UL << 30) /* TG1 4KB granules in TTBR1 */ | (16UL << 16) /* T1SZ 48-bit */ | (3UL << 28) /* SH1 */ | (1UL << 26) /* ORGN1 */ | (1UL << 24) /* IRGN1 */ | (0UL << 14) /* TG0 4KB granules in TTBR0 */ | (16UL << 0) /* T0SZ 48-bit */ | (3UL << 12) /* SH0 */ | (1UL << 10) /* ORGN0 */ | (1UL << 8) /* IRGN0 */ ; /* MAIR setup? */ uint64_t mair = (0x000000000044ff00); asm volatile ("msr MAIR_EL1,%0" :: "r"(mair)); /* Frob bits */ printf("bootstub: setting base values\n"); asm volatile ("msr TCR_EL1,%0" : : "r"(tcr)); asm volatile ("msr TTBR0_EL1,%0" : : "r"(&_baseTables.l0_base)); asm volatile ("msr TTBR1_EL1,%0" : : "r"(&_baseTables.l0_base)); printf("bootstub: frobbing bits\n"); asm volatile ("dsb ishst\ntlbi vmalle1is\ndsb ish\nisb" ::: "memory"); asm volatile ("msr SCTLR_EL1,%0" : : "r"(sctlr)); asm volatile ("isb" ::: "memory"); /* Point log output at new mmio address */ printf_output = &_later_log_write; printf("bootstub: MMU initialized\n"); } static void bootstub_read_kernel(uintptr_t kernel_load_addr) { /* See if we can find a qemu fw_cfg interface, we can use that for a ramdisk */ uint32_t * fw_cfg = find_node_prefix("fw-cfg"); if (fw_cfg) { printf("bootstub: found fw-cfg interface\n"); /* best guess until we bother parsing these */ uint32_t * regs = node_find_property(fw_cfg, "reg"); if (regs) { printf("bootstub: length of regs = %u\n", swizzle(regs[0])); printf("bootstub: addr of fw-cfg = %#x\n", swizzle(regs[3])); volatile uint8_t * fw_cfg_addr = (volatile uint8_t*)(uintptr_t)(swizzle(regs[3]) + 0xffffff8000000000); volatile uint64_t * fw_cfg_data = (volatile uint64_t *)fw_cfg_addr; volatile uint32_t * fw_cfg_32 = (volatile uint32_t *)fw_cfg_addr; volatile uint16_t * fw_cfg_sel = (volatile uint16_t *)(fw_cfg_addr + 8); *fw_cfg_sel = 0; uint64_t response = fw_cfg_data[0]; printf("bootstub: response: %c%c%c%c\n", (char)(response >> 0), (char)(response >> 8), (char)(response >> 16), (char)(response >> 24)); /* Needs to be big-endian */ *fw_cfg_sel = swizzle16(0x19); /* count response is 32-bit BE */ uint32_t count = swizzle(fw_cfg_32[0]); printf("bootstub: %u entries\n", count); struct fw_cfg_file { uint32_t size; uint16_t select; uint16_t reserved; char name[56]; }; struct fw_cfg_file file; uint8_t * tmp = (uint8_t *)&file; /* Read count entries */ for (unsigned int i = 0; i < count; ++i) { for (unsigned int j = 0; j < sizeof(struct fw_cfg_file); ++j) { tmp[j] = fw_cfg_addr[0]; } /* endian swap to get file size and selector ID */ file.size = swizzle(file.size); file.select = swizzle16(file.select); printf("bootstub: 0x%04x %s (%d bytes)\n", file.select, file.name, file.size); if (!strcmp(file.name,"opt/org.toaruos.kernel")) { printf("bootstub: Found kernel, loading\n"); uint8_t * x = (uint8_t*)kernel_load_addr; struct fwcfg_dma { volatile uint32_t control; volatile uint32_t length; volatile uint64_t address; } dma; dma.control = swizzle((file.select << 16) | (1 << 3) | (1 << 1)); dma.length = swizzle(file.size); dma.address = swizzle64((uintptr_t)x); fw_cfg_data[2] = swizzle64((uint64_t)&dma); if (dma.control) { printf("bootstub: error on dma read?\n"); return; } return; } } } } } static void bootstub_load_kernel(Elf64_Header * header) { /* Find load headers */ for (int i = 0; i < header->e_phnum; ++i) { Elf64_Phdr * phdr = (void*)((uintptr_t)header + (header->e_phoff + header->e_phentsize * i)); if (phdr->p_type == PT_LOAD) { printf("bootstub: Load %zu bytes @ %zx from off %zx\n", phdr->p_memsz, phdr->p_vaddr, phdr->p_offset); memset((void*)phdr->p_vaddr, 0, phdr->p_memsz); memcpy((void*)phdr->p_vaddr, (void*)((uintptr_t)header + phdr->p_offset), phdr->p_filesz); } else { printf("bootstub: Skip phdr %d\n", i); } } } static void bootstub_start_kernel(Elf64_Header * header) { printf("bootstub: Jump to kernel entry point at %zx\n", header->e_entry); void (*entry)(uintptr_t,uintptr_t,uintptr_t) = (void(*)(uintptr_t,uintptr_t,uintptr_t))header->e_entry; entry(QEMU_DTB_BASE, KERNEL_PHYS_BASE, 0); } int kmain(void) { extern char end[]; uintptr_t kernel_load_addr = (uintptr_t)&end; /* Initialize log */ *(volatile unsigned int *)(0x09000030) = 0x101; printf_output = &_early_log_write; printf("bootstub: Starting up\n"); /* Set up MMU */ bootstub_mmu_init(); /* Read the kernel from fw-cfg */ bootstub_read_kernel(kernel_load_addr); /* Examine kernel */ Elf64_Header *header = (void*)kernel_load_addr; bootstub_load_kernel(header); /* Jump to kernel */ bootstub_start_kernel(header); while (1) {} return 0; } ================================================ FILE: kernel/arch/aarch64/context.S ================================================ /** * @file kernel/arch/aarch64/context.S * @brief Kernel context switching utilities. * * arch_save_context and arch_restore_context are basically * setjmp and longjmp. _restore_context will also set the * previous thread as no longer running. */ .globl arch_save_context arch_save_context: /* x0 has our struct pointer */ mrs x1, TPIDR_EL0 mov x2, sp stp x2, x29, [x0] stp x30, x1, [x0, (1 * 16)] stp x19, x20, [x0, (2 * 16)] stp x21, x22, [x0, (3 * 16)] stp x23, x24, [x0, (4 * 16)] stp x25, x26, [x0, (5 * 16)] stp x27, x28, [x0, (6 * 16)] mrs x1, ELR_EL1 mrs x2, SPSR_EL1 stp x1, x2, [x0, (7 * 16)] mov x0, 0 ret .globl arch_restore_context arch_restore_context: ldr x1, [x18, 16] /* get previous */ ldr x2, [x18, 0] /* get current */ cmp x2, x1 /* compare current to prev, into x2 */ beq _restore_context_same /* 20 is the offset of ->status. */ add x1, x1, 20 /* equivalent to __atomic_and_and_fetch */ _restore_context_loop: ldxr w2, [x1] /* Load exclusive */ and w2, w2, 0xFFFFfff7 /* Unset running */ stlxr w4, w2, [x1] /* Store exclusive */ cbnz w4, _restore_context_loop /* try again if we failed */ _restore_context_same: /* x0 has our struct pointer */ ldp x2, x29, [x0] ldp x30, x1, [x0, (1 * 16)] ldp x19, x20, [x0, (2 * 16)] ldp x21, x22, [x0, (3 * 16)] ldp x23, x24, [x0, (4 * 16)] ldp x25, x26, [x0, (5 * 16)] ldp x27, x28, [x0, (6 * 16)] msr TPIDR_EL0, x1 mov sp, x2 ldp x1, x2, [x0, (7 * 16)] msr ELR_EL1, x1 msr SPSR_EL1, x2 mov x0, 1 ret /** * @brief Start of userspace thread. */ .globl arch_resume_user arch_resume_user: ldp x30, x0, [sp], #16 msr SP_EL0, x0 ldp x28, x29, [sp], #16 ldp x26, x27, [sp], #16 ldp x24, x25, [sp], #16 ldp x22, x23, [sp], #16 ldp x20, x21, [sp], #16 ldp x18, x19, [sp], #16 ldp x16, x17, [sp], #16 ldp x14, x15, [sp], #16 ldp x12, x13, [sp], #16 ldp x10, x11, [sp], #16 ldp x8, x9, [sp], #16 ldp x6, x7, [sp], #16 ldp x4, x5, [sp], #16 ldp x2, x3, [sp], #16 ldp x0, x1, [sp], #16 eret ================================================ FILE: kernel/arch/aarch64/dtb.c ================================================ /** * @file kernel/arch/aarch64/dtb.c * @brief Methods for parsing device tree binaries * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2022 K. Lange */ #include #include #include #include #include #include #include #include uintptr_t aarch64_dtb_phys = 0; static uint32_t * parse_node(uint32_t * node, char * strings, int x) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 9) return NULL; if (swizzle(*node) != 1) { printf("Not a node? Got %x\n", swizzle(*node)); return NULL; } /* Skip the BEGIN_NODE */ node++; for (int i = 0; i < x; ++i) printf(" "); while (1) { char * x = (char*)node; if (x[0]) { printf("%c",x[0]); } else { node++; break; } if (x[1]) { printf("%c",x[1]); } else { node++; break; } if (x[2]) { printf("%c",x[2]); } else { node++; break; } if (x[3]) { printf("%c",x[3]); } else { node++; break; } node++; } printf("\n"); while (1) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 2) return node+1; if (swizzle(*node) == 3) { for (int i = 0; i < x; ++i) printf(" "); uint32_t len = swizzle(node[1]); uint32_t nameoff = swizzle(node[2]); printf(" property %s len=%u\n", strings + nameoff, len); node += 3; node += (len + 3) / 4; } else if (swizzle(*node) == 1) { node = parse_node(node, strings, x + 1); } } } static uint32_t * find_subnode(uint32_t * node, char * strings, const char * name, uint32_t ** node_out, int (*cmp)(const char* a, const char *b)) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 9) return NULL; if (swizzle(*node) != 1) return NULL; node++; if (cmp((char*)node,name)) { *node_out = node; return NULL; } while ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++; node++; while (1) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 2) return node+1; if (swizzle(*node) == 3) { uint32_t len = swizzle(node[1]); node += 3; node += (len + 3) / 4; } else if (swizzle(*node) == 1) { node = find_subnode(node, strings, name, node_out, cmp); if (!node) return NULL; } } } static uint32_t * skip_node(uint32_t * node, void (*callback)(uint32_t * child)) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 9) return NULL; if (swizzle(*node) != 1) return NULL; node++; while ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++; node++; while (1) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 2) return node+1; if (swizzle(*node) == 3) { uint32_t len = swizzle(node[1]); node += 3; node += (len + 3) / 4; } else if (swizzle(*node) == 1) { if (callback) callback(node+1); node = skip_node(node, NULL); if (!node) return NULL; } } } void dtb_callback_direct_children(uint32_t * node, void (*callback)(uint32_t * child)) { skip_node(node-1, callback); } static uint32_t * find_node_int(const char * name, int (*cmp)(const char*,const char*)) { uintptr_t addr = (uintptr_t)mmu_map_from_physical(aarch64_dtb_phys); struct fdt_header * fdt = (struct fdt_header*)addr; char * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings)); uint32_t * dtb_struct = (uint32_t *)(addr + swizzle(fdt->off_dt_struct)); uint32_t * out = NULL; find_subnode(dtb_struct, dtb_strings, name, &out, cmp); return out; } static int base_cmp(const char *a, const char *b) { return !strcmp(a,b); } uint32_t * dtb_find_node(const char * name) { return find_node_int(name,base_cmp); } static int prefix_cmp(const char *a, const char *b) { return !memcmp(a,b,strlen(b)); } uint32_t * dtb_find_node_prefix(const char * name) { return find_node_int(name,prefix_cmp); } static uint32_t * node_find_property_int(uint32_t * node, char * strings, const char * property, uint32_t ** out) { while ((*node & 0xFF000000) && (*node & 0xFF0000) && (*node & 0xFF00) && (*node & 0xFF)) node++; node++; while (1) { while (swizzle(*node) == 4) node++; if (swizzle(*node) == 2) return node+1; if (swizzle(*node) == 3) { uint32_t len = swizzle(node[1]); uint32_t nameoff = swizzle(node[2]); if (!strcmp(strings + nameoff, property)) { *out = &node[1]; return NULL; } node += 3; node += (len + 3) / 4; } else if (swizzle(*node) == 1) { node = node_find_property_int(node+1, strings, property, out); if (!node) return NULL; } } } uint32_t * dtb_node_find_property(uint32_t * node, const char * property) { uintptr_t addr = (uintptr_t)mmu_map_from_physical(aarch64_dtb_phys); struct fdt_header * fdt = (struct fdt_header*)addr; char * dtb_strings = (char *)(addr + swizzle(fdt->off_dt_strings)); uint32_t * out = NULL; node_find_property_int(node, dtb_strings, property, &out); return out; } /** * Figure out 1) how much actual memory we have and 2) what the last * address of physical memory is. */ void dtb_memory_size(size_t * memaddr, size_t * physsize) { uint32_t * memory = dtb_find_node_prefix("memory"); if (!memory) { printf("dtb: Could not find memory node.\n"); arch_fatal(); } uint32_t * regs = dtb_node_find_property(memory, "reg"); if (!regs) { printf("dtb: memory node has no regs\n"); arch_fatal(); } /* Eventually, same with fw-cfg, we'll need to actually figure out * the size of the 'reg' cells, but at least right now it's been * 2 address, 2 size. */ uint64_t mem_addr = (uint64_t)swizzle(regs[3]) | ((uint64_t)swizzle(regs[2]) << 32UL); uint64_t mem_size = (uint64_t)swizzle(regs[5]) | ((uint64_t)swizzle(regs[4]) << 32UL); *memaddr = mem_addr; *physsize = mem_size; } void dtb_locate_cmdline(char ** args_out) { uint32_t * chosen = dtb_find_node("chosen"); if (chosen) { uint32_t * prop = dtb_node_find_property(chosen, "bootargs"); if (prop) { *args_out = (char*)&prop[2]; args_parse((char*)&prop[2]); } } } void dtb_pcie_base(void) { extern uintptr_t pcie_ecam_phys; uint32_t * pcie = dtb_find_node_prefix("pcie"); if (pcie) { /* See if it has a regs */ uint32_t * regs = dtb_node_find_property(pcie, "reg"); if (regs) { pcie_ecam_phys = (uintptr_t)swizzle(regs[3]) | ((uintptr_t)swizzle(regs[2]) << 32UL); dprintf("dtb: pcie base is %#zx\n", (uint64_t)pcie_ecam_phys); } } } static ssize_t read_dtb(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { if ((size_t)offset > node->length) { return 0; } if ((size_t)offset + size > node->length) { size_t i = node->length - offset; size = i; } memcpy(buffer, (void *)((uintptr_t)mmu_map_from_physical(aarch64_dtb_phys) + (uintptr_t)offset), size); return size; } void dtb_device(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); snprintf(fnode->name, 10, "dtb"); fnode->inode = 0; fnode->device = fnode; fnode->uid = 0; fnode->gid = 0; fnode->mask = 0770; fnode->length = 1048576; fnode->flags = FS_BLOCKDEVICE; fnode->read = read_dtb; vfs_mount("/dev/dtb", fnode, "dtb", ""); } ================================================ FILE: kernel/arch/aarch64/entry.S ================================================ /** * @file kernel/arch/aarch64/entry.S * @brief Entrypoint stub. * * This gets us off of the bootstab's stack and onto * our own temporary stack, which we'll ideally switch * off of again when we start up the root process. */ .extern __bootstrap_stack_top .globl start start: ldr x30, =__bootstrap_stack_top mov sp, x30 bl kmain hang: b hang ================================================ FILE: kernel/arch/aarch64/fwcfg.c ================================================ /** * @file kernel/arch/aarch64/fwcfg.c * @brief Methods for dealing with QEMU's fw-cfg interface on aarch64. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include static struct fwcfg_dma { volatile uint32_t control; volatile uint32_t length; volatile uint64_t address; } dma __attribute__((aligned(4096))); void fwcfg_load_initrd(uintptr_t * ramdisk_phys_base, size_t * ramdisk_size) { uintptr_t z = 0; size_t z_pages= 0; uintptr_t uz = 0; size_t uz_pages = 0; extern char end[]; uintptr_t ramdisk_map_start = mmu_map_to_physical(NULL, (uintptr_t)&end); /* See if we can find a qemu fw_cfg interface, we can use that for a ramdisk */ uint32_t * fw_cfg = dtb_find_node_prefix("fw-cfg"); if (fw_cfg) { dprintf("fw-cfg: found interface\n"); /* best guess until we bother parsing these */ uint32_t * regs = dtb_node_find_property(fw_cfg, "reg"); if (regs) { volatile uint8_t * fw_cfg_addr = (volatile uint8_t*)(uintptr_t)(mmu_map_from_physical(swizzle(regs[3]))); volatile uint64_t * fw_cfg_data = (volatile uint64_t *)fw_cfg_addr; volatile uint32_t * fw_cfg_32 = (volatile uint32_t *)fw_cfg_addr; volatile uint16_t * fw_cfg_sel = (volatile uint16_t *)(fw_cfg_addr + 8); *fw_cfg_sel = 0; uint64_t response = fw_cfg_data[0]; (void)response; /* Needs to be big-endian */ *fw_cfg_sel = swizzle16(0x19); /* count response is 32-bit BE */ uint32_t count = swizzle(fw_cfg_32[0]); struct fw_cfg_file { uint32_t size; uint16_t select; uint16_t reserved; char name[56]; }; struct fw_cfg_file file; uint8_t * tmp = (uint8_t *)&file; /* Read count entries */ for (unsigned int i = 0; i < count; ++i) { for (unsigned int j = 0; j < sizeof(struct fw_cfg_file); ++j) { tmp[j] = fw_cfg_addr[0]; } /* endian swap to get file size and selector ID */ file.size = swizzle(file.size); file.select = swizzle16(file.select); if (!strcmp(file.name,"opt/org.toaruos.initrd")) { dprintf("fw-cfg: initrd found\n"); z_pages = (file.size + 0xFFF) / 0x1000; z = ramdisk_map_start; ramdisk_map_start += z_pages * 0x1000; uint8_t * x = mmu_map_from_physical(z); dma.control = swizzle((file.select << 16) | (1 << 3) | (1 << 1)); dma.length = swizzle(file.size); dma.address = swizzle64(z); asm volatile ("isb" ::: "memory"); fw_cfg_data[2] = swizzle64(mmu_map_to_physical(NULL,(uint64_t)&dma)); asm volatile ("isb" ::: "memory"); if (dma.control) { dprintf("fw-cfg: Error on DMA read (control: %#x)\n", dma.control); return; } dprintf("fw-cfg: initrd loaded x=%#zx\n", (uintptr_t)x); if (x[0] == 0x1F && x[1] == 0x8B) { dprintf("fw-cfg: will attempt to read size from %#zx\n", (uintptr_t)(x + file.size - sizeof(uint32_t))); uint32_t size; memcpy(&size, (x + file.size - sizeof(uint32_t)), sizeof(uint32_t)); dprintf("fw-cfg: compressed ramdisk unpacks to %u bytes\n", size); uz_pages = (size + 0xFFF) / 0x1000; uz = ramdisk_map_start; ramdisk_map_start += uz_pages * 0x1000; uint8_t * ramdisk = mmu_map_from_physical(uz); gzip_inputPtr = x; gzip_outputPtr = ramdisk; if (gzip_decompress()) { dprintf("fw-cfg: gzip failure, not mounting ramdisk\n"); return; } memmove(x, ramdisk, size); dprintf("fw-cfg: Unpacked ramdisk at %#zx\n", (uintptr_t)ramdisk); *ramdisk_phys_base = z; *ramdisk_size = size; } else { dprintf("fw-cfg: Ramdisk at %#zx\n", (uintptr_t)x); *ramdisk_phys_base = z; *ramdisk_size = file.size; } return; } } } } } static ssize_t read_fwcfg(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { volatile uint8_t * addr; if (offset == 0x510) { /* Read selector. Must be 16 bit; selector will be byte swapped from big-endian. */ addr = (uint8_t*)node->device + 8; if (size != 2) return -EINVAL; uint16_t b = *(volatile uint16_t *)addr; b = (b >> 8) | ((b & 0xFF) << 8); *(uint16_t*)buffer = b; } else if (offset == 0x511) { /* Read one data byte */ addr = (uint8_t*)node->device; if (size != 1) return -EINVAL; *buffer = *addr; } else { return -EINVAL; } return size; } static ssize_t write_fwcfg(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { volatile uint8_t * addr; if (offset == 0x510) { /* Write selector. Must be 16 bit; selector will be byte swapped to big-endian. */ addr = (uint8_t*)node->device + 8; if (size != 2) return -EINVAL; uint16_t b = *(uint16_t *)buffer; b = (b >> 8) | ((b & 0xFF) << 8); *(volatile uint16_t*)addr = b; } else if (offset == 0x511) { /* Write one data byte. */ addr = (uint8_t*)node->device; if (size != 1) return -EINVAL; *addr = *buffer; } else { return -EINVAL; } return size; } void fwcfg_device(void) { uint32_t * fw_cfg = dtb_find_node_prefix("fw-cfg"); if (!fw_cfg) return; uint32_t * regs = dtb_node_find_property(fw_cfg, "reg"); if (!regs) return; uint8_t * fw_cfg_addr = (uint8_t*)(uintptr_t)(mmu_map_from_physical(swizzle(regs[3]))); fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0, sizeof(fs_node_t)); strcpy(fnode->name, "fwcfg"); fnode->flags = FS_BLOCKDEVICE; fnode->mask = 0660; fnode->read = read_fwcfg; fnode->write = write_fwcfg; fnode->device = fw_cfg_addr; char addr[100]; snprintf(addr, 99, "%p", (void*)fw_cfg_addr); vfs_mount("/dev/fwcfg", fnode, "qemu-fwcfg", addr); } ================================================ FILE: kernel/arch/aarch64/gic.c ================================================ /** * @file kernel/arch/aarch64/virtio.c * @brief Rudimentary, hacky implementations of virtio input devices. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include #include struct irq_callback * irq_callbacks[256] = {0}; static spin_lock_t irq_acquire; volatile uint32_t * gic_regs = NULL; volatile uint32_t * gicc_regs = NULL; void gic_map_regs(uintptr_t rpi_tag) { if (rpi_tag) { gic_regs = (volatile uint32_t*)mmu_map_mmio_region(0xff841000, 0x1000); gicc_regs = (volatile uint32_t*)mmu_map_mmio_region(0xff842000, 0x2000); } else { gic_regs = (volatile uint32_t*)mmu_map_mmio_region(0x08000000, 0x1000); gicc_regs = (volatile uint32_t*)mmu_map_mmio_region(0x08010000, 0x2000); } } void gic_send_sgi(uint8_t intid, int target) { uint64_t tlf = ((target == -1) ? 1UL : 0UL) << 24; uint64_t sii = intid & 0xF; uint64_t ctl = ((target == -1) ? 0UL : (1UL << target)) << 16; gic_regs[0x3c0] = (tlf|sii|ctl); } void gic_assign_interrupt(int irq, int (*callback)(process_t*,int,void*), void * data) { spin_lock(irq_acquire); struct irq_callback * cb = calloc(sizeof(struct irq_callback),1); cb->callback = callback; cb->owner = (process_t*)this_core->current_process; cb->data = data; cb->next = NULL; if (irq_callbacks[irq]) { struct irq_callback * parent = irq_callbacks[irq]; while (parent->next) { parent = parent->next; } parent->next = cb; } else { irq_callbacks[irq] = cb; } spin_unlock(irq_acquire); } void gic_map_pci_interrupt(const char * name, uint32_t device, int * int_out, int (*callback)(process_t*,int,void*), void * isr_addr) { uint32_t phys_hi = (pci_extract_bus(device) << 16) | (pci_extract_slot(device) << 11); uint32_t pin = pci_read_field(device, PCI_INTERRUPT_PIN, 1); #if 0 dprintf("%s: device %#x, slot = %d (0x%04x), irq pin = %d\n", name, device, pci_extract_slot(device), phys_hi, pin); #endif uint32_t * pcie_dtb = dtb_find_node_prefix("pcie@"); if (!pcie_dtb) { dprintf("%s: can't find dtb entry\n", name); return; } uint32_t * intMask = dtb_node_find_property(pcie_dtb, "interrupt-map-mask"); if (!intMask) { dprintf("%s: can't find property 'interrupt-map-mask'\n", name); return; } uint32_t * intMap = dtb_node_find_property(pcie_dtb, "interrupt-map"); if (!intMap) { dprintf("%s: can't find property 'interrupt-map'\n", name); return; } for (int i = 0; i < (int)swizzle(intMap[0])/4; i += 10) { if (swizzle(intMap[i+2]) == (swizzle(intMask[2]) & phys_hi)) { if (swizzle(intMap[i+5]) == (swizzle(intMask[5]) & pin)) { #if 0 dprintf("%s: %#x %#x %#x %#x\n", name, swizzle(intMap[i+2]), swizzle(intMap[i+3]), swizzle(intMap[i+4]), swizzle(intMap[i+5])); dprintf("%s: Matching device and pin, Interrupt maps to %d\n", name, swizzle(intMap[i+10])); #endif *int_out = swizzle(intMap[i+10]); gic_assign_interrupt(*int_out, callback, isr_addr); return; } } } } ================================================ FILE: kernel/arch/aarch64/irq.S ================================================ /** * @file kernel/arch/aarch64/irq.S * @brief Exception entry points. * * These are still very hacky and rudimentary and we're * probably saving way more context than we actually need. */ sync_common: /* Push all regular registers */ stp x0, x1, [sp, #-16]! stp x2, x3, [sp, #-16]! stp x4, x5, [sp, #-16]! stp x6, x7, [sp, #-16]! stp x8, x9, [sp, #-16]! stp x10, x11, [sp, #-16]! stp x12, x13, [sp, #-16]! stp x14, x15, [sp, #-16]! stp x16, x17, [sp, #-16]! stp x18, x19, [sp, #-16]! stp x20, x21, [sp, #-16]! stp x22, x23, [sp, #-16]! stp x24, x25, [sp, #-16]! stp x26, x27, [sp, #-16]! stp x28, x29, [sp, #-16]! /* combine x30 (link register) with stack */ mrs x0, SP_EL0 stp x30, x0, [sp, #-16]! /* Pass the current stack as the first arg (struct regs *r) */ mov x0, sp /* Make sure x18 has our kernel CPU-local pointer */ mrs x18, TPIDR_EL1 .extern aarch64_sync_enter bl aarch64_sync_enter mrs x0, SPSR_EL1 and w0, w0, #0x200000 cbz w0, _sync_cont mrs x0, MDSCR_EL1 orr x0, x0, #1 msr MDSCR_EL1, x0 _sync_cont: ldp x30, x0, [sp], #16 msr SP_EL0, x0 ldp x28, x29, [sp], #16 ldp x26, x27, [sp], #16 ldp x24, x25, [sp], #16 ldp x22, x23, [sp], #16 ldp x20, x21, [sp], #16 ldp x18, x19, [sp], #16 ldp x16, x17, [sp], #16 ldp x14, x15, [sp], #16 ldp x12, x13, [sp], #16 ldp x10, x11, [sp], #16 ldp x8, x9, [sp], #16 ldp x6, x7, [sp], #16 ldp x4, x5, [sp], #16 ldp x2, x3, [sp], #16 ldp x0, x1, [sp], #16 eret fault_common: stp x0, x1, [sp, #-16]! stp x2, x3, [sp, #-16]! stp x4, x5, [sp, #-16]! stp x6, x7, [sp, #-16]! stp x8, x9, [sp, #-16]! stp x10, x11, [sp, #-16]! stp x12, x13, [sp, #-16]! stp x14, x15, [sp, #-16]! stp x16, x17, [sp, #-16]! stp x18, x19, [sp, #-16]! stp x20, x21, [sp, #-16]! stp x22, x23, [sp, #-16]! stp x24, x25, [sp, #-16]! stp x26, x27, [sp, #-16]! stp x28, x29, [sp, #-16]! mrs x0, SP_EL0 stp x30, x0, [sp, #-16]! mov x0, sp mrs x18, TPIDR_EL1 .extern aarch64_fault_enter bl aarch64_fault_enter ldp x30, x0, [sp], #16 msr SP_EL0, x0 ldp x28, x29, [sp], #16 ldp x26, x27, [sp], #16 ldp x24, x25, [sp], #16 ldp x22, x23, [sp], #16 ldp x20, x21, [sp], #16 ldp x18, x19, [sp], #16 ldp x16, x17, [sp], #16 ldp x14, x15, [sp], #16 ldp x12, x13, [sp], #16 ldp x10, x11, [sp], #16 ldp x8, x9, [sp], #16 ldp x6, x7, [sp], #16 ldp x4, x5, [sp], #16 ldp x2, x3, [sp], #16 ldp x0, x1, [sp], #16 eret irq_common: stp x0, x1, [sp, #-16]! stp x2, x3, [sp, #-16]! stp x4, x5, [sp, #-16]! stp x6, x7, [sp, #-16]! stp x8, x9, [sp, #-16]! stp x10, x11, [sp, #-16]! stp x12, x13, [sp, #-16]! stp x14, x15, [sp, #-16]! stp x16, x17, [sp, #-16]! stp x18, x19, [sp, #-16]! stp x20, x21, [sp, #-16]! stp x22, x23, [sp, #-16]! stp x24, x25, [sp, #-16]! stp x26, x27, [sp, #-16]! stp x28, x29, [sp, #-16]! mrs x0, SP_EL0 stp x30, x0, [sp, #-16]! mov x0, sp mrs x18, TPIDR_EL1 .extern aarch64_irq_enter bl aarch64_irq_enter mrs x0, SPSR_EL1 and w0, w0, #0x200000 cbz w0, _irq_cont mrs x0, MDSCR_EL1 orr x0, x0, #1 msr MDSCR_EL1, x0 _irq_cont: ldp x30, x0, [sp], #16 msr SP_EL0, x0 ldp x28, x29, [sp], #16 ldp x26, x27, [sp], #16 ldp x24, x25, [sp], #16 ldp x22, x23, [sp], #16 ldp x20, x21, [sp], #16 ldp x18, x19, [sp], #16 ldp x16, x17, [sp], #16 ldp x14, x15, [sp], #16 ldp x12, x13, [sp], #16 ldp x10, x11, [sp], #16 ldp x8, x9, [sp], #16 ldp x6, x7, [sp], #16 ldp x4, x5, [sp], #16 ldp x2, x3, [sp], #16 ldp x0, x1, [sp], #16 eret .globl _exception_vector .balign 0x800 _exception_vector: /* AArch64 exception vectors are divided up; * our first set of vectors is for if we are using SP0 * in EL1, which we don't currently do so these should * all probably just be loop branches to catch this. */ _exc_sp0_sync: b . .balign 0x80 _exc_sp0_irq: b . .balign 0x80 _exc_sp0_fiq: b . .balign 0x80 _exc_sp0_serror: b . /* These are EL1-EL1 fault handlers, for when we encounter * an exception in the kernel. We normally have interrupts * masked in the kernel so we should only see synchronous * exceptions - faults. */ .balign 0x80 _exc_spx_sync: b fault_common .balign 0x80 _exc_spx_irq: b . .balign 0x80 _exc_spx_fiq: b . .balign 0x80 _exc_spx_serror: b . /* These are EL0-EL1 transition handlers. Synchronous is going * to be faults and system calls (SVC requests); */ .balign 0x80 _exc_lower_sync: b sync_common /* Actual interrupts */ .balign 0x80 _exc_lower_irq: b irq_common /* "Fast" interrupts. TODO */ .balign 0x80 _exc_lower_fiq: b . .balign 0x80 _exc_lower_serror: b . .balign 0x80 /* These are going to be calls from 32-bit userspace, which we're * not going to support, so just blank all of these out as well. */ _exc_lower_32_sync: b . .balign 0x80 _exc_lower_32_irq: b . .balign 0x80 _exc_lower_32_fiq: b . .balign 0x80 _exc_lower_32_serror: b . ================================================ FILE: kernel/arch/aarch64/link.ld ================================================ OUTPUT_FORMAT(elf64-littleaarch64) ENTRY(start) SECTIONS { . = 0xffffffff80000000; phys = .; .text BLOCK(4K) : ALIGN(4K) { *(.bootstrap) code = .; *(.text) } .rodata BLOCK(4K) : ALIGN(4K) { *(.rodata) } .data BLOCK(4K) : ALIGN(4K) { data = .; *(.data) *(.symbols) PROVIDE(kernel_symbols_start = .); PROVIDE(kernel_symbols_end = .); PROVIDE(bss_start = .); } .bss BLOCK(4K) : ALIGN(4K) { bss = .; *(COMMON) *(.bss) *(.stack) } /* Some built-in stack space... */ . = ALIGN(0x1000); . = . + 0x4000; __bootstrap_stack_top = .; end = .; /DISCARD/ : { *(.comment) *(.eh_frame) *(.note.gnu.build-id) } } ================================================ FILE: kernel/arch/aarch64/main.c ================================================ /** * @file kernel/arch/aarch64/main.c * @brief Kernel C entry point and initialization for QEMU aarch64 'virt' machine. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern void fbterm_initialize(void); extern void mmu_init(size_t memsize, size_t phys, uintptr_t firstFreePage, uintptr_t endOfInitrd); extern void aarch64_regs(struct regs *r); extern void fwcfg_load_initrd(uintptr_t * ramdisk_phys_base, size_t * ramdisk_size); extern void fwcfg_device(void); extern void virtio_input(void); extern void aarch64_smp_start(void); extern char end[]; extern char * _arch_args; /* ARM says the system clock tick rate is generally in * the range of 1-50MHz. Since we throw around integer * MHz ratings that's not great, so let's give it a few * more digits for long-term accuracy? */ uint64_t sys_timer_freq = 0; uint64_t arch_boot_time = 0; /**< No idea where we're going to source this from, need an RTC. */ uint64_t basis_time = 0; #define SUBSECONDS_PER_SECOND 1000000 /** * TODO can this be marked 'inline'? * * Read the system timer timestamp. */ uint64_t arch_perf_timer(void) { uint64_t val; asm volatile ("mrs %0,CNTPCT_EL0" : "=r"(val)); return val * 100; } /** * @warning This function is incorrectly named. * @brief Get the frequency of the perf timer. * * This is not the CPU frequency. We do present it as such for x86-64, * and I think for our TSC timing that is generally true, but not here. */ size_t arch_cpu_mhz(void) { return sys_timer_freq; } /** * @brief Figure out the rate of the system timer and get boot time from RTC. * * We use the system timer as our performance tracker, as it operates at few * megahertz at worst which is good enough for us. We do want slightly bigger * numbers to make our integer divisions more accurate... */ static void arch_clock_initialize(uintptr_t rpi_tag) { /* Get frequency of system timer */ uint64_t val; asm volatile ("mrs %0,CNTFRQ_EL0" : "=r"(val)); sys_timer_freq = val / 10000; /* Get boot time from RTC */ if (rpi_tag) { arch_boot_time = 1644908027UL; } else { /* QEMU RTC */ void * clock_addr = mmu_map_from_physical(0x09010000); arch_boot_time = *(volatile uint32_t*)clock_addr; } /* Get the "basis time" - the perf timestamp we got the wallclock time at */ basis_time = arch_perf_timer() / sys_timer_freq; /* Report the reference clock speed */ dprintf("timer: Using %ld MHz as arch_perf_timer frequency.\n", arch_cpu_mhz()); } static void update_ticks(uint64_t ticks, uint64_t *timer_ticks, uint64_t *timer_subticks) { *timer_subticks = ticks - basis_time; *timer_ticks = *timer_subticks / SUBSECONDS_PER_SECOND; *timer_subticks = *timer_subticks % SUBSECONDS_PER_SECOND; } int gettimeofday(struct timeval * t, void *z) { uint64_t tsc = arch_perf_timer(); uint64_t timer_ticks, timer_subticks; update_ticks(tsc / sys_timer_freq, &timer_ticks, &timer_subticks); t->tv_sec = arch_boot_time + timer_ticks; t->tv_usec = timer_subticks; return 0; } uint64_t now(void) { struct timeval t; gettimeofday(&t, NULL); return t.tv_sec; } static spin_lock_t _time_set_lock; int settimeofday(struct timeval * t, void *z) { if (!t) return -EINVAL; if (t->tv_sec < 0 || t->tv_usec < 0 || t->tv_usec > 1000000) return -EINVAL; spin_lock(_time_set_lock); uint64_t clock_time = now(); arch_boot_time += t->tv_sec - clock_time; spin_unlock(_time_set_lock); return 0; } void relative_time(unsigned long seconds, unsigned long subseconds, unsigned long * out_seconds, unsigned long * out_subseconds) { if (!arch_boot_time) { *out_seconds = 0; *out_subseconds = 0; return; } uint64_t tsc = arch_perf_timer(); uint64_t timer_ticks, timer_subticks; update_ticks(tsc / sys_timer_freq, &timer_ticks, &timer_subticks); if (subseconds + timer_subticks >= SUBSECONDS_PER_SECOND) { *out_seconds = timer_ticks + seconds + (subseconds + timer_subticks) / SUBSECONDS_PER_SECOND; *out_subseconds = (subseconds + timer_subticks) % SUBSECONDS_PER_SECOND; } else { *out_seconds = timer_ticks + seconds; *out_subseconds = timer_subticks + subseconds; } } #define TIMER_IRQ 27 static void set_tick(void) { asm volatile ( "mrs x0, CNTFRQ_EL0\n" "mov x1, 100\n" // without this, one second "udiv x0, x0, x1\n" "msr CNTV_TVAL_EL0, x0\n" "mov x0, 1\n" "msr CNTV_CTL_EL0, x0\n" :::"x0","x1"); } void timer_start(void) { /* mask irqs */ asm volatile ("msr DAIFSet, #0b1111"); /* Enable the local timer */ set_tick(); /* This is global, we only need to do this once... */ gic_regs[0] = 1; /* This is specific to this CPU */ gicc_regs[0] = 1; gicc_regs[1] = 0x1ff; /* Timer interrupts are private peripherals, so each CPU gets one */ gic_regs[64] = 0xFFFFffff; //(1 << TIMER_IRQ); gic_regs[160] = 0xFFFFffff; //(1 << TIMER_IRQ); /* These are shared? */ gic_regs[65] = 0xFFFFFFFF; gic_regs[66] = 0xFFFFFFFF; gic_regs[67] = 0xFFFFFFFF; gic_regs[520] = 0x07070707; gic_regs[521] = 0x07070707; gic_regs[543] = 0x07070707; } static volatile uint64_t time_slice_basis = 0; /**< When the last clock update happened */ static spin_lock_t ticker_lock; static void update_clock(void) { uint64_t clock_ticks = arch_perf_timer() / sys_timer_freq; uint64_t timer_ticks, timer_subticks; update_ticks(clock_ticks, &timer_ticks, &timer_subticks); spin_lock(ticker_lock); if (time_slice_basis + SUBSECONDS_PER_SECOND/4 <= clock_ticks) { update_process_usage(clock_ticks - time_slice_basis, sys_timer_freq); time_slice_basis = clock_ticks; } spin_unlock(ticker_lock); wakeup_sleepers(timer_ticks, timer_subticks); } static volatile unsigned int * _log_device_addr = 0; static size_t _early_log_write(size_t size, uint8_t * buffer) { for (unsigned int i = 0; i < size; ++i) { *_log_device_addr = buffer[i]; } return size; } static void early_log_initialize(void) { /* QEMU UART */ _log_device_addr = mmu_map_from_physical(0x09000000); printf_output = &_early_log_write; } void arch_set_core_base(uintptr_t base) { /* It doesn't actually seem that this register has * any real meaning, it's just available for us * to load with our thread pointer. It's possible * that the 'mrs' for it is just as fast as regular * register reference? */ asm volatile ("msr TPIDR_EL1,%0" : : "r"(base)); /* this_cpu pointer, which we can tell gcc is reserved * by our ABI and then bind as a 'register' variable. */ asm volatile ("mrs x18, TPIDR_EL1"); } void arch_set_tls_base(uintptr_t tlsbase) { asm volatile ("msr TPIDR_EL0,%0" : : "r"(tlsbase)); } void arch_set_kernel_stack(uintptr_t stack) { /* This is currently unused... it seems we're handling * things correctly and getting the right stack already, * but XXX should look into this later. */ this_core->sp_el1 = stack; } static void exception_handlers(void) { extern char _exception_vector[]; asm volatile("msr VBAR_EL1, %0" :: "r"(&_exception_vector)); } void aarch64_sync_enter(struct regs * r) { uint64_t esr, far, elr, spsr; asm volatile ("mrs %0, ESR_EL1" : "=r"(esr)); asm volatile ("mrs %0, FAR_EL1" : "=r"(far)); asm volatile ("mrs %0, ELR_EL1" : "=r"(elr)); asm volatile ("mrs %0, SPSR_EL1" : "=r"(spsr)); #if 0 dprintf("EL0-EL1 sync: %d (%s) ESR: %#zx FAR: %#zx ELR: %#zx SPSR: %#zx\n", this_core ? (this_core->current_process ? this_core->current_process->id : -1) : -1, this_core ? (this_core->current_process ? this_core->current_process->name : "?") : "?", esr, far, elr, spsr); #endif if (esr == 0x2000000) { arch_fatal_prepare(); dprintf("Unknown exception: ESR: %#zx FAR: %#zx ELR: %#zx SPSR: %#zx\n", esr, far, elr, spsr); dprintf("Instruction at ELR: 0x%08x\n", *(uint32_t*)elr); arch_dump_traceback(); aarch64_regs(r); arch_fatal(); } if (this_core->current_process) { this_core->current_process->time_switch = arch_perf_timer(); } if ((esr >> 26) == 0x32) { /* Single step trap */ uint64_t val; asm volatile("mrs %0, MDSCR_EL1" : "=r"(val)); val &= ~(1 << 0); asm volatile("msr MDSCR_EL1, %0" :: "r"(val)); if (this_core->current_process->flags & PROC_FLAG_TRACE_SIGNALS) { ptrace_signal(SIGTRAP, PTRACE_EVENT_SINGLESTEP); } goto _resume_user; } /* Magic signal return */ if (elr == 0x516 && far == 0x516) { return_from_signal_handler(r); goto _resume_user; } /* System call */ if ((esr >> 26) == 0x15) { //dprintf("pid %d syscall %zd elr=%#zx\n", // this_core->current_process->id, r->x0, elr); extern void syscall_handler(struct regs *); syscall_handler(r); goto _resume_user; } /* KVM is mad at us; usually means our code is broken or we neglected a cache. */ if (far == 0x1de7ec7edbadc0de) { printf("kvm: blip (esr=%#zx, elr=%#zx; pid=%d [%s])\n", esr, elr, this_core->current_process->id, this_core->current_process->name); goto _resume_user; } /* Unexpected fault, eg. page fault. */ dprintf("In process %d (%s)\n", this_core->current_process->id, this_core->current_process->name); dprintf("ESR: %#zx FAR: %#zx ELR: %#zx SPSR: %#zx\n", esr, far, elr, spsr); aarch64_regs(r); uint64_t tpidr_el0; asm volatile ("mrs %0, TPIDR_EL0" : "=r"(tpidr_el0)); dprintf(" TPIDR_EL0=%#zx\n", tpidr_el0); send_signal(this_core->current_process->id, SIGSEGV, 1); _resume_user: process_check_signals(r); update_process_times_on_exit(); } static void spin(void) { while (1) { asm volatile ("wfi"); } } #define EOI(x) do { \ gicc_regs[4] = (x); \ } while (0) void aarch64_interrupt_dispatch(int from_wfi) { uint32_t iar = gicc_regs[3]; uint32_t irq = iar & 0x3FF; /* Currently we aren't using the CPU value and I'm not sure we have any use for it, we know who we are? */ //uint32_t cpu = (iar >> 10) & 0x7; switch (irq) { case TIMER_IRQ: update_clock(); set_tick(); EOI(iar); if (from_wfi) { switch_next(); } else { switch_task(1); } return; case 1: EOI(iar); if (from_wfi) switch_next(); break; /* Arbitrarily chosen SGI for panic signal from another core */ case 2: spin(); break; case 1022: case 1023: return; default: if (irq >= 32 && irq < 1022) { struct irq_callback * cb = irq_callbacks[irq-32]; if (cb) { while (cb) { int res = cb->callback(cb->owner, irq-32, cb->data); if (res) break; cb = cb->next; } /* Maybe warn? We have a lot of spurious irqs, though */ } else { dprintf("irq: unhandled irq %d\n", irq); } EOI(iar); } else { dprintf("gic: Unhandled interrupt: %d\n", irq); EOI(iar); } return; } } void aarch64_irq_enter(struct regs * r) { if (this_core->current_process) { this_core->current_process->time_switch = arch_perf_timer(); } aarch64_interrupt_dispatch(0); process_check_signals(r); } /** * @brief Kernel fault handler. */ void aarch64_fault_enter(struct regs * r) { uint64_t esr, far, elr, spsr; asm volatile ("mrs %0, ESR_EL1" : "=r"(esr)); asm volatile ("mrs %0, FAR_EL1" : "=r"(far)); asm volatile ("mrs %0, ELR_EL1" : "=r"(elr)); asm volatile ("mrs %0, SPSR_EL1" : "=r"(spsr)); arch_fatal_prepare(); dprintf("EL1-EL1 fault handler, core %d\n", this_core->cpu_id); if (this_core && this_core->current_process) { dprintf("In process %d (%s)\n", this_core->current_process->id, this_core->current_process->name); } dprintf("ESR: %#zx FAR: %#zx ELR: %#zx SPSR: %#zx\n", esr, far, elr, spsr); aarch64_regs(r); uint64_t tpidr_el0; asm volatile ("mrs %0, TPIDR_EL0" : "=r"(tpidr_el0)); dprintf(" TPIDR_EL0=%#zx\n", tpidr_el0); extern void aarch64_safe_dump_traceback(uintptr_t elr, struct regs * r); aarch64_safe_dump_traceback(elr, r); arch_fatal(); } void aarch64_sp0_fault_enter(struct regs * r) { arch_fatal_prepare(); dprintf("EL1-EL1 sp0 entry?\n"); arch_fatal(); } /** * @brief Enable FPU and NEON (SIMD) * * This enables the FPU in EL0. I'm not sure if we can enable it * there but not in EL1... that would be nice to avoid accidentally * introducing FPU code in the kernel that would corrupt our FPU state. */ void fpu_enable(void) { uint64_t cpacr_el1; asm volatile ("mrs %0, CPACR_EL1" : "=r"(cpacr_el1)); cpacr_el1 |= (3 << 20) | (3 << 16); asm volatile ("msr CPACR_EL1, %0" :: "r"(cpacr_el1)); /* Enable access to physical timer */ uint64_t clken = 0; asm volatile ("mrs %0,CNTKCTL_EL1" : "=r"(clken)); clken |= (1 << 0); asm volatile ("msr CNTKCTL_EL1,%0" :: "r"(clken)); } /** * @brief Called in a loop by kernel idle tasks. */ void arch_pause(void) { /* XXX This actually works even if we're masking interrupts, but * the interrupt function won't be called, so we'll need to change * it once we start getting actual hardware interrupts. */ asm volatile ("wfi"); aarch64_interrupt_dispatch(1); } /** * @brief Force a cache clear across an address range. * * GCC has a __clear_cache() function that is supposed to do this * but I haven't figured out what bits I need to set in what system * register to allow that to be callable from EL0, so we actually expose * it as a new sysfunc system call for now. We'll be generous and quietly * skip regions that are not accessible to the calling process. * * This is critical for the dynamic linker to reset the icache for * regions that have been loaded from new libraries. */ void arch_clear_icache(uintptr_t start, uintptr_t end) { for (uintptr_t x = start; x < end; x += 64) { if (!mmu_validate_user_pointer((void*)x, 64, MMU_PTR_WRITE)) continue; asm volatile ("dc cvau, %0" :: "r"(x)); } for (uintptr_t x = start; x < end; x += 64) { if (!mmu_validate_user_pointer((void*)x, 64, MMU_PTR_WRITE)) continue; asm volatile ("ic ivau, %0" :: "r"(x)); } } void aarch64_processor_data(void) { asm volatile ("mrs %0, MIDR_EL1" : "=r"(this_core->midr)); } static void symbols_install(void) { ksym_install(); kernel_symbol_t * k = (kernel_symbol_t *)&kernel_symbols_start; while ((uintptr_t)k < (uintptr_t)&kernel_symbols_end) { ksym_bind(k->name, (void*)k->addr); k = (kernel_symbol_t *)((uintptr_t)k + sizeof *k + strlen(k->name) + 1); } } /** * Main kernel C entrypoint for qemu's -machine virt * * By this point, a 'bootstub' has already set up some * initial page tables so the linear physical mapping * is where we would normally expect it to be, we're * at -2GiB, and there's some other mappings so that * a bit of RAM is 1:1. */ int kmain(uintptr_t dtb_base, uintptr_t phys_base, uintptr_t rpi_tag) { extern uintptr_t aarch64_kernel_phys_base; aarch64_kernel_phys_base = phys_base; extern uintptr_t aarch64_dtb_phys; aarch64_dtb_phys = dtb_base; if (rpi_tag) { extern uint8_t * lfb_vid_memory; extern uint16_t lfb_resolution_x; extern uint16_t lfb_resolution_y; extern uint16_t lfb_resolution_b; extern uint32_t lfb_resolution_s; extern size_t lfb_memsize; struct rpitag * tag = (struct rpitag*)rpi_tag; lfb_vid_memory = mmu_map_from_physical(tag->phys_addr); lfb_resolution_x = tag->x; lfb_resolution_y = tag->y; lfb_resolution_s = tag->s; lfb_resolution_b = tag->b; lfb_memsize = tag->size; fbterm_initialize(); } else { /* Uncomment to get serial log on startup; otherwise, you can set * 'qemu-serial-log' in the command line to get it later, which will * still dump the early log messages. */ //early_log_initialize(); } dprintf("%s %d.%d.%d-%s %s %s\n", __kernel_name, __kernel_version_major, __kernel_version_minor, __kernel_version_lower, __kernel_version_suffix, __kernel_version_codename, __kernel_arch); dprintf("boot: dtb @ %#zx kernel @ %#zx\n", dtb_base, phys_base); /* Initialize TPIDR_EL1 */ arch_set_core_base((uintptr_t)&processor_local_data[0]); /* Set up the system timer and get an RTC time. */ arch_clock_initialize(rpi_tag); /* Set up exception handlers early... */ exception_handlers(); /* Load ramdisk over fw-cfg. */ uintptr_t ramdisk_phys_base = 0; size_t ramdisk_size = 0; if (rpi_tag) { /* XXX Should this whole set of things be a "platform_init()" thing, where we * figure out the platform and just do the stuff? */ struct rpitag * tag = (struct rpitag*)rpi_tag; rpi_load_ramdisk(tag, &ramdisk_phys_base, &ramdisk_size); /* TODO figure out memory size - mailbox commands */ mmu_init(0, 512 * 1024 * 1024, 0x80000, (uintptr_t)&end + ramdisk_size - 0xffffffff80000000UL); dprintf("rpi: mmu reinitialized\n"); rpi_set_cmdline(&_arch_args); } else { /* * TODO virt shim should load the ramdisk for us, so we can use the same code * as we have for the RPi above and not have to use fwcfg to load it... */ fwcfg_load_initrd(&ramdisk_phys_base, &ramdisk_size); /* Probe DTB for memory layout. */ size_t memaddr, memsize; dtb_memory_size(&memaddr, &memsize); /* Initialize the MMU based on the memory we got from dtb */ mmu_init( memaddr, memsize, 0x40100000 /* Should be end of DTB, but we're really just guessing */, (uintptr_t)&end + ramdisk_size - 0xffffffff80000000UL); /* Find the cmdline */ dtb_locate_cmdline(&_arch_args); if (args_present("qemu-serial-log")) { early_log_initialize(); } /* Check for PCIe address? */ dtb_pcie_base(); } gic_map_regs(rpi_tag); /* Set up all the other arch-specific stuff here */ fpu_enable(); symbols_install(); generic_startup(); /* Initialize the framebuffer and fbterm here */ framebuffer_initialize(); if (!rpi_tag) { fbterm_initialize(); } /* Ramdisk */ ramdisk_mount(ramdisk_phys_base, ramdisk_size); extern void dtb_device(void); dtb_device(); /* Load MIDR */ aarch64_processor_data(); /* Set up the system virtual timer to produce interrupts for userspace scheduling */ timer_start(); /* Start other cores here */ if (!rpi_tag ){ aarch64_smp_start(); /* Install drivers that may need to sleep here */ virtio_input(); /* QEMU fwcfg interface */ fwcfg_device(); /* Set up serial input */ extern void pl011_start(void); pl011_start(); } else { extern void rpi_smp_init(void); rpi_smp_init(); extern void null_input(void); null_input(); extern void miniuart_start(void); miniuart_start(); } generic_main(); return 0; } ================================================ FILE: kernel/arch/aarch64/mmu.c ================================================ /** * @file kernel/arch/aarch64/mmu.c * @brief Nearly identical to the x86-64 implementation. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include static volatile uint32_t *frames; static size_t nframes; static size_t total_memory = 0; static size_t unavailable_memory = 0; static uint64_t ram_starts_at = 0; uintptr_t aarch64_kernel_phys_base = 0; /* TODO Used for CoW later. */ //static uint8_t * mem_refcounts = NULL; #define PAGE_SHIFT 12 #define PAGE_SIZE 0x1000UL #define PAGE_SIZE_MASK 0xFFFFffffFFFFf000UL #define PAGE_LOW_MASK 0x0000000000000FFFUL #define LARGE_PAGE_SIZE 0x200000UL #define PHYS_MASK 0x7fffffffffUL #define CANONICAL_MASK 0xFFFFffffFFFFUL #define INDEX_FROM_BIT(b) ((b) >> 5) #define OFFSET_FROM_BIT(b) ((b) & 0x1F) #define _pagemap __attribute__((aligned(PAGE_SIZE))) = {0} union PML init_page_region[512] _pagemap; union PML high_base_pml[512] _pagemap; union PML heap_base_pml[512] _pagemap; union PML heap_base_pd[512] _pagemap; union PML heap_base_pt[512*3] _pagemap; union PML kbase_pmls[65][512] _pagemap; #define PTE_VALID (1UL << 0) #define PTE_TABLE (1UL << 1) /* Table attributes */ #define PTE_NSTABLE (1UL << 63) #define PTE_APTABLE (3UL << 61) /* two bits */ #define PTE_APTABLE_A (1UL << 62) #define PTE_APTABLE_B (1UL << 61) #define PTE_UXNTABLE (1UL << 60) #define PTE_PXNTABLE (1UL << 59) /* Block attributes */ #define PTE_UXN (1UL << 54) #define PTE_PXN (1UL << 53) #define PTE_CONTIGUOUS (1UL << 52) #define PTE_NG (1UL << 11) #define PTE_AF (1UL << 10) #define PTE_SH (3UL << 8) /* two bits */ #define PTE_SH_A (1UL << 9) #define PTE_SH_B (1UL << 8) #define PTE_AP (3UL << 6) /* two bits */ #define PTE_AP_A (1UL << 7) #define PTE_AP_B (1UL << 6) #define PTE_NS (1UL << 5) #define PTE_ATTRINDX (7UL << 2) /* three bits */ #define PTE_ATTR_A (1UL << 4) #define PTE_ATTR_B (1UL << 3) #define PTE_ATTR_C (1UL << 2) void mmu_frame_set(uintptr_t frame_addr) { if (frame_addr < ram_starts_at) return; frame_addr -= ram_starts_at; if (frame_addr < nframes * PAGE_SIZE) { uint64_t frame = frame_addr >> 12; uint64_t index = INDEX_FROM_BIT(frame); uint32_t offset = OFFSET_FROM_BIT(frame); __sync_or_and_fetch(&frames[index], ((uint32_t)1 << offset)); asm ("isb" ::: "memory"); } } static uintptr_t lowest_available = 0; void mmu_frame_clear(uintptr_t frame_addr) { if (frame_addr < ram_starts_at) return; frame_addr -= ram_starts_at; if (frame_addr < nframes * PAGE_SIZE) { uint64_t frame = frame_addr >> PAGE_SHIFT; uint64_t index = INDEX_FROM_BIT(frame); uint32_t offset = OFFSET_FROM_BIT(frame); __sync_and_and_fetch(&frames[index], ~((uint32_t)1 << offset)); asm ("isb" ::: "memory"); if (frame < lowest_available) lowest_available = frame; } } int mmu_frame_test(uintptr_t frame_addr) { if (frame_addr < ram_starts_at) return 1; frame_addr -= ram_starts_at; if (!(frame_addr < nframes * PAGE_SIZE)) return 1; uint64_t frame = frame_addr >> PAGE_SHIFT; uint64_t index = INDEX_FROM_BIT(frame); uint32_t offset = OFFSET_FROM_BIT(frame); asm ("" ::: "memory"); return !!(frames[index] & ((uint32_t)1 << offset)); } static spin_lock_t frame_alloc_lock = { 0 }; static spin_lock_t kheap_lock = { 0 }; static spin_lock_t mmio_space_lock = { 0 }; static spin_lock_t module_space_lock = { 0 }; void mmu_frame_release(uintptr_t frame_addr) { spin_lock(frame_alloc_lock); mmu_frame_clear(frame_addr); spin_unlock(frame_alloc_lock); } uintptr_t mmu_first_n_frames(int n) { for (uint64_t i = 0; i < nframes * PAGE_SIZE; i += PAGE_SIZE) { int bad = 0; for (int j = 0; j < n; ++j) { if (mmu_frame_test(i + ram_starts_at + PAGE_SIZE * j)) { bad = j + 1; } } if (!bad) { return (i + ram_starts_at) / PAGE_SIZE; } } arch_fatal_prepare(); dprintf("Failed to allocate %d contiguous frames.\n", n); arch_dump_traceback(); arch_fatal(); return (uintptr_t)-1; } uintptr_t mmu_first_frame(void) { uintptr_t i, j; for (i = INDEX_FROM_BIT(lowest_available); i < INDEX_FROM_BIT(nframes); ++i) { if (frames[i] != (uint32_t)-1) { for (j = 0; j < (sizeof(uint32_t)*8); ++j) { uint32_t testFrame = (uint32_t)1 << j; if (!(frames[i] & testFrame)) { uintptr_t out = (i << 5) + j; lowest_available = out + 1; return out + (ram_starts_at >> 12); } } } } if (lowest_available != 0) { lowest_available = 0; return mmu_first_frame(); } arch_fatal_prepare(); dprintf("Out of memory.\n"); arch_dump_traceback(); arch_fatal(); return (uintptr_t)-1; } void mmu_frame_allocate(union PML * page, unsigned int flags) { /* If page is not set... */ if (page->bits.page == 0) { spin_lock(frame_alloc_lock); uintptr_t index = mmu_first_frame(); mmu_frame_set(index << PAGE_SHIFT); page->bits.page = index; spin_unlock(frame_alloc_lock); } page->bits.table_page = 1; page->bits.present = 1; page->bits.ap = (!(flags & MMU_FLAG_WRITABLE) ? 2 : 0) | (!(flags & MMU_FLAG_KERNEL) ? 1 : 0); page->bits.af = 1; page->bits.sh = 2; page->bits.attrindx = ((flags & MMU_FLAG_NOCACHE) | (flags & MMU_FLAG_WRITETHROUGH)) ? 0 : 1; if (!(flags & MMU_FLAG_KERNEL)) { page->bits.attrindx = 1; if ((flags & MMU_FLAG_WC) == MMU_FLAG_WC) { page->bits.attrindx = 2; } } asm volatile ("dsb ishst\ntlbi vmalle1is\ndsb ish\nisb" ::: "memory"); #if 0 page->bits.writable = (flags & MMU_FLAG_WRITABLE) ? 1 : 0; page->bits.user = (flags & MMU_FLAG_KERNEL) ? 0 : 1; page->bits.nocache = (flags & MMU_FLAG_NOCACHE) ? 1 : 0; page->bits.writethrough = (flags & MMU_FLAG_WRITETHROUGH) ? 1 : 0; page->bits.size = (flags & MMU_FLAG_SPEC) ? 1 : 0; page->bits.nx = (flags & MMU_FLAG_NOEXECUTE) ? 1 : 0; #endif } void mmu_frame_map_address(union PML * page, unsigned int flags, uintptr_t physAddr) { /* frame set physAddr, set page in entry, call frame_allocate to set attribute bits */ mmu_frame_set(physAddr); page->bits.page = physAddr >> PAGE_SHIFT; mmu_frame_allocate(page, flags); } void * mmu_map_from_physical(uintptr_t frameaddress) { return (void*)(frameaddress | HIGH_MAP_REGION); } #define PDP_MASK 0x3fffffffUL #define PD_MASK 0x1fffffUL #define PT_MASK PAGE_LOW_MASK #define ENTRY_MASK 0x1FF union PML * mmu_get_page_other(union PML * root, uintptr_t virtAddr) { //printf("mmu_get_page_other(%#zx, %#zx);\n", (uintptr_t)root, virtAddr); /* Walk it */ uintptr_t realBits = virtAddr & CANONICAL_MASK; uintptr_t pageAddr = realBits >> PAGE_SHIFT; unsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK; unsigned int pdp_entry = (pageAddr >> 18) & ENTRY_MASK; unsigned int pd_entry = (pageAddr >> 9) & ENTRY_MASK; unsigned int pt_entry = (pageAddr) & ENTRY_MASK; /* Get the PML4 entry for this address */ if (!root[pml4_entry].bits.present) { return NULL; } union PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT); if (!pdp[pdp_entry].bits.present) { return NULL; } if (!pdp[pdp_entry].bits.table_page) { return NULL; } union PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT); if (!pd[pd_entry].bits.present) { return NULL; } if (!pd[pd_entry].bits.table_page) { return NULL; } union PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT); return (union PML *)&pt[pt_entry]; } uintptr_t mmu_map_to_physical(union PML * root, uintptr_t virtAddr) { if (!root) { if (virtAddr >= MODULE_BASE_START) { return (virtAddr - MODULE_BASE_START) + aarch64_kernel_phys_base; } else if (virtAddr >= HIGH_MAP_REGION) { return (virtAddr - HIGH_MAP_REGION); } return (uintptr_t)virtAddr; } uintptr_t realBits = virtAddr & CANONICAL_MASK; uintptr_t pageAddr = realBits >> PAGE_SHIFT; unsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK; unsigned int pdp_entry = (pageAddr >> 18) & ENTRY_MASK; unsigned int pd_entry = (pageAddr >> 9) & ENTRY_MASK; unsigned int pt_entry = (pageAddr) & ENTRY_MASK; if (!root[pml4_entry].bits.present) return (uintptr_t)-1; union PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT); if (!pdp[pdp_entry].bits.present) return (uintptr_t)-2; if (!pdp[pdp_entry].bits.table_page) return ((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT) | (virtAddr & PDP_MASK); union PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT); if (!pd[pd_entry].bits.present) return (uintptr_t)-3; if (!pd[pd_entry].bits.table_page) return ((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT) | (virtAddr & PD_MASK); union PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT); if (!pt[pt_entry].bits.present) return (uintptr_t)-4; return ((uintptr_t)pt[pt_entry].bits.page << PAGE_SHIFT) | (virtAddr & PT_MASK); } union PML * mmu_get_page(uintptr_t virtAddr, int flags) { /* This is all the same as x86, thankfully? */ uintptr_t realBits = virtAddr & CANONICAL_MASK; uintptr_t pageAddr = realBits >> PAGE_SHIFT; unsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK; unsigned int pdp_entry = (pageAddr >> 18) & ENTRY_MASK; unsigned int pd_entry = (pageAddr >> 9) & ENTRY_MASK; unsigned int pt_entry = (pageAddr) & ENTRY_MASK; union PML * root = this_core->current_pml; /* Get the PML4 entry for this address */ spin_lock(frame_alloc_lock); if (!root[pml4_entry].bits.present) { if (!(flags & MMU_GET_MAKE)) goto _noentry; uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); /* zero it */ memset(mmu_map_from_physical(newPage), 0, PAGE_SIZE); root[pml4_entry].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF; } spin_unlock(frame_alloc_lock); union PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT); spin_lock(frame_alloc_lock); if (!pdp[pdp_entry].bits.present) { if (!(flags & MMU_GET_MAKE)) goto _noentry; uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); /* zero it */ memset(mmu_map_from_physical(newPage), 0, PAGE_SIZE); pdp[pdp_entry].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF; } spin_unlock(frame_alloc_lock); if (!pdp[pdp_entry].bits.table_page) { printf("Warning: Tried to get page for a 1GiB block! %d\n", pdp_entry); return NULL; } union PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT); spin_lock(frame_alloc_lock); if (!pd[pd_entry].bits.present) { if (!(flags & MMU_GET_MAKE)) goto _noentry; uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); /* zero it */ memset(mmu_map_from_physical(newPage), 0, PAGE_SIZE); pd[pd_entry].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF; } spin_unlock(frame_alloc_lock); if (!pd[pd_entry].bits.table_page) { printf("Warning: Tried to get page for a 2MiB block!\n"); return NULL; } union PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT); return (union PML *)&pt[pt_entry]; _noentry: spin_unlock(frame_alloc_lock); printf("no entry for requested page\n"); return NULL; } static int copy_page_maybe(union PML * pt_in, union PML * pt_out, size_t l, uintptr_t address) { spin_lock(frame_alloc_lock); /* TODO cow bits */ char * page_in = mmu_map_from_physical((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); char * page_out = mmu_map_from_physical(newPage); memcpy(page_out,page_in,PAGE_SIZE); asm volatile ("dmb sy\nisb" ::: "memory"); for (uintptr_t x = (uintptr_t)page_out; x < (uintptr_t)page_out + PAGE_SIZE; x += 64) { asm volatile ("dc cvau, %0" :: "r"(x)); } for (uintptr_t x = (uintptr_t)page_out; x < (uintptr_t)page_out + PAGE_SIZE; x += 64) { asm volatile ("ic ivau, %0" :: "r"(x)); } pt_out[l].raw = 0; pt_out[l].bits.table_page = 1; pt_out[l].bits.present = 1; pt_out[l].bits.ap = pt_in[l].bits.ap; pt_out[l].bits.af = pt_in[l].bits.af; pt_out[l].bits.sh = pt_in[l].bits.sh; pt_out[l].bits.attrindx = pt_in[l].bits.attrindx; pt_out[l].bits.page = newPage >> PAGE_SHIFT; asm volatile ("" ::: "memory"); spin_unlock(frame_alloc_lock); return 0; } union PML * mmu_clone(union PML * from) { /* Clone the current PMLs... */ if (!from) from = this_core->current_pml; /* First get a page for ourselves. */ spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); union PML * pml4_out = mmu_map_from_physical(newPage); /* Zero bottom half */ memset(&pml4_out[0], 0, 256 * sizeof(union PML)); /* Copy top half */ memcpy(&pml4_out[256], &from[256], 256 * sizeof(union PML)); /* Copy PDPs */ for (size_t i = 0; i < 256; ++i) { if (from[i].bits.present) { union PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT); spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); union PML * pdp_out = mmu_map_from_physical(newPage); memset(pdp_out, 0, 512 * sizeof(union PML)); pml4_out[i].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF; /* Copy the PDs */ for (size_t j = 0; j < 512; ++j) { if (pdp_in[j].bits.present) { union PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); union PML * pd_out = mmu_map_from_physical(newPage); memset(pd_out, 0, 512 * sizeof(union PML)); pdp_out[j].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF; /* Now copy the PTs */ for (size_t k = 0; k < 512; ++k) { if (pd_in[k].bits.present) { union PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); union PML * pt_out = mmu_map_from_physical(newPage); memset(pt_out, 0, 512 * sizeof(union PML)); pd_out[k].raw = (newPage) | PTE_VALID | PTE_TABLE | PTE_AF; /* Now, finally, copy pages */ for (size_t l = 0; l < 512; ++l) { uintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT)); if (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue; if (pt_in[l].bits.present) { if (1) { //pt_in[l].bits.user) { copy_page_maybe(pt_in, pt_out, l, address); } else { /* If it's not a user page, just copy directly */ pt_out[l].raw = pt_in[l].raw; } } /* Else, mmap'd files? */ } } } } } } } return pml4_out; } uintptr_t mmu_allocate_a_frame(void) { spin_lock(frame_alloc_lock); uintptr_t index = mmu_first_frame(); mmu_frame_set(index << PAGE_SHIFT); spin_unlock(frame_alloc_lock); return index; } uintptr_t mmu_allocate_n_frames(int n) { spin_lock(frame_alloc_lock); uintptr_t index = mmu_first_n_frames(n); for (int i = 0; i < n; ++i) { mmu_frame_set((index+i) << PAGE_SHIFT); } spin_unlock(frame_alloc_lock); return index; } size_t mmu_count_user(union PML * from) { /* We walk 'from' and count user pages */ size_t out = 0; for (size_t i = 0; i < 256; ++i) { if (from[i].bits.present) { out++; union PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT); for (size_t j = 0; j < 512; ++j) { if (pdp_in[j].bits.present) { out++; union PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); for (size_t k = 0; k < 512; ++k) { if (pd_in[k].bits.present) { out++; union PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); for (size_t l = 0; l < 512; ++l) { /* Calculate final address to skip SHM */ uintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT)); if (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue; if (pt_in[l].bits.present) { if (pt_in[l].bits.ap & 1) { out++; } } } } } } } } } return out; } size_t mmu_count_shm(union PML * from) { /* We walk 'from' and count shm region stuff */ size_t out = 0; for (size_t i = 0; i < 256; ++i) { if (from[i].bits.present) { union PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT); for (size_t j = 0; j < 512; ++j) { if (pdp_in[j].bits.present) { union PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); for (size_t k = 0; k < 512; ++k) { if (pd_in[k].bits.present) { union PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); for (size_t l = 0; l < 512; ++l) { /* Calculate final address to skip SHM */ uintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT)); if (address < USER_DEVICE_MAP && address >= USER_SHM_HIGH) continue; if (pt_in[l].bits.present) { if (pt_in[l].bits.ap & 1) { out++; } } } } } } } } } return out; } size_t mmu_total_memory(void) { return total_memory; } size_t mmu_used_memory(void) { size_t ret = 0; size_t i, j; for (i = 0; i < INDEX_FROM_BIT(nframes); ++i) { for (j = 0; j < 32; ++j) { uint32_t testFrame = (uint32_t)0x1 << j; if (frames[i] & testFrame) { ret++; } } } return ret * 4 - unavailable_memory; } void mmu_free(union PML * from) { /* walk and free pages */ if (!from) { printf("can't clear NULL directory\n"); return; } spin_lock(frame_alloc_lock); for (size_t i = 0; i < 256; ++i) { if (from[i].bits.present) { union PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT); for (size_t j = 0; j < 512; ++j) { if (pdp_in[j].bits.present) { union PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); for (size_t k = 0; k < 512; ++k) { if (pd_in[k].bits.present) { union PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); for (size_t l = 0; l < 512; ++l) { uintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT)); /* Do not free shared mappings; SHM subsystem does that for SHM, devices don't need it. */ if (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue; if (pt_in[l].bits.present) { /* Free only user pages */ if (pt_in[l].bits.ap & 1) { mmu_frame_clear((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT); pt_in[l].raw = 0; //free_page_maybe(pt_in,l,address); } } } mmu_frame_clear((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); pd_in[k].raw = 0; } } mmu_frame_clear((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); pdp_in[j].raw = 0; } } mmu_frame_clear((uintptr_t)from[i].bits.page << PAGE_SHIFT); from[i].raw = 0; } } uintptr_t physAddr = (((uintptr_t)from) & PHYS_MASK); mmu_frame_clear(physAddr); asm volatile ("dsb ishst\ntlbi vmalle1is\ndsb ish\nisb" ::: "memory"); spin_unlock(frame_alloc_lock); } union PML * mmu_get_kernel_directory(void) { return mmu_map_from_physical((uintptr_t)&init_page_region - MODULE_BASE_START + aarch64_kernel_phys_base); } void mmu_set_directory(union PML * new_pml) { /* Set the EL0 and EL1 directy things? * There are two of these... */ if (!new_pml) new_pml = mmu_get_kernel_directory(); this_core->current_pml = new_pml; uintptr_t pml_phys = mmu_map_to_physical(new_pml, (uintptr_t)new_pml); asm volatile ( "msr TTBR0_EL1,%0\n" "msr TTBR1_EL1,%0\n" "isb sy\n" "dsb ishst\n" "tlbi vmalle1is\n" "dsb ish\n" "isb\n" :: "r"(pml_phys) : "memory"); } void mmu_invalidate(uintptr_t addr) { } int mmu_get_page_deep(uintptr_t virtAddr, union PML ** pml4_out, union PML ** pdp_out, union PML ** pd_out, union PML ** pt_out) { /* This is all the same as x86, thankfully? */ uintptr_t realBits = virtAddr & CANONICAL_MASK; uintptr_t pageAddr = realBits >> PAGE_SHIFT; unsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK; unsigned int pdp_entry = (pageAddr >> 18) & ENTRY_MASK; unsigned int pd_entry = (pageAddr >> 9) & ENTRY_MASK; unsigned int pt_entry = (pageAddr) & ENTRY_MASK; /* Zero all the outputs */ *pdp_out = NULL; *pd_out = NULL; *pt_out = NULL; spin_lock(frame_alloc_lock); union PML * root = this_core->current_pml; *pml4_out = (union PML *)&root[pml4_entry]; if (!root[pml4_entry].bits.present) goto _noentry; union PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT); *pdp_out = (union PML *)&pdp[pdp_entry]; if (!pdp[pdp_entry].bits.present) goto _noentry; union PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT); *pd_out = (union PML *)&pd[pd_entry]; if (!pd[pd_entry].bits.present) goto _noentry; union PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT); *pt_out = (union PML *)&pt[pt_entry]; spin_unlock(frame_alloc_lock); return 0; _noentry: spin_unlock(frame_alloc_lock); return 1; } static int maybe_release_directory(union PML * parent, union PML * child) { /* child points to one entry, to get the base, we can page align it */ union PML * table = (union PML *)((uintptr_t)child & PAGE_SIZE_MASK); /* Is everything in the table free? */ for (int i = 0; i < 512; ++i) { if (table[i].bits.present) return 0; } uintptr_t old_page = (parent->bits.page << PAGE_SHIFT); /* Then we can mark 'parent' as freed, clear the whole thing. */ parent->raw = 0; mmu_frame_clear(old_page); return 1; } void mmu_unmap_user(uintptr_t addr, size_t size) { for (uintptr_t a = addr; a < addr + size; a += PAGE_SIZE) { union PML * pml4, * pdp, * pd, * pt; if (a >= USER_DEVICE_MAP && a <= USER_SHM_HIGH) continue; if (mmu_get_page_deep(a, &pml4, &pdp, &pd, &pt)) continue; spin_lock(frame_alloc_lock); /* Free this page if it was present */ if (pt && pt->bits.present) { if (pt->bits.ap & 1) { mmu_frame_clear((uintptr_t)pt->bits.page << PAGE_SHIFT); pt->bits.present = 0; pt->bits.ap = 0; } if (maybe_release_directory(pd, pt)) { if (maybe_release_directory(pdp, pd)) { maybe_release_directory(pml4, pdp); } } mmu_invalidate(a); } spin_unlock(frame_alloc_lock); } } static char * heapStart = NULL; extern char end[]; void * sbrk(size_t bytes) { if (!heapStart) { arch_fatal_prepare(); printf("sbrk: Called before heap was ready.\n"); arch_dump_traceback(); arch_fatal(); } if (!bytes) { /* Skip lock acquisition if we just wanted to know where the break was. */ return heapStart; } if (bytes & PAGE_LOW_MASK) { arch_fatal_prepare(); printf("sbrk: Size must be multiple of 4096, was %#zx\n", bytes); arch_dump_traceback(); arch_fatal(); } spin_lock(kheap_lock); void * out = heapStart; for (uintptr_t p = (uintptr_t)out; p < (uintptr_t)out + bytes; p += PAGE_SIZE) { union PML * page = mmu_get_page(p, MMU_GET_MAKE); mmu_frame_allocate(page, MMU_FLAG_WRITABLE | MMU_FLAG_KERNEL); } heapStart += bytes; spin_unlock(kheap_lock); return out; } static uintptr_t mmio_base_address = MMIO_BASE_START; void * mmu_map_mmio_region(uintptr_t physical_address, size_t size) { if (size & PAGE_LOW_MASK) { arch_fatal_prepare(); printf("mmu_map_mmio_region: MMIO region size must be multiple of 4096 bytes, was %#zx.\n", size); arch_dump_traceback(); arch_fatal(); } spin_lock(mmio_space_lock); void * out = (void*)mmio_base_address; for (size_t i = 0; i < size; i += PAGE_SIZE) { union PML * p = mmu_get_page(mmio_base_address + i, MMU_GET_MAKE); mmu_frame_map_address(p, MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE | MMU_FLAG_NOCACHE | MMU_FLAG_WRITETHROUGH, physical_address + i); } mmio_base_address += size; spin_unlock(mmio_space_lock); return out; } static uintptr_t module_base_address = MODULE_BASE_START; void * mmu_map_module(size_t size) { if (size & PAGE_LOW_MASK) { size += (PAGE_LOW_MASK + 1) - (size & PAGE_LOW_MASK); } spin_lock(module_space_lock); void * out = (void*)module_base_address; for (size_t i = 0; i < size; i += PAGE_SIZE) { union PML * p = mmu_get_page(module_base_address + i, MMU_GET_MAKE); mmu_frame_allocate(p, MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE); } module_base_address += size; spin_unlock(module_space_lock); return out; } void mmu_unmap_module(uintptr_t start_address, size_t size) { } int mmu_copy_on_write(uintptr_t address) { return 1; } int mmu_validate_user_pointer(const void * addr, size_t size, int flags) { //printf("mmu_validate_user_pointer(%#zx, %lu, %u);\n", (uintptr_t)addr, size, flags); if (addr == NULL && !(flags & MMU_PTR_NULL)) return 0; if (size > 0x800000000000) return 0; uintptr_t base = (uintptr_t)addr; uintptr_t end = size ? (base + (size - 1)) : base; /* Get start page, end page */ uintptr_t page_base = base >> 12; uintptr_t page_end = end >> 12; for (uintptr_t page = page_base; page <= page_end; ++page) { if ((page & 0xffff800000000) != 0 && (page & 0xffff800000000) != 0xffff800000000) return 0; union PML * page_entry = mmu_get_page_other(this_core->current_process->thread.page_directory->directory, page << 12); if (!page_entry) { return 0; } if (!page_entry->bits.present) { return 0; } if (!(page_entry->bits.ap & 1)) { return 0; } if ((page_entry->bits.ap & 2) && (flags & MMU_PTR_WRITE)) { return 0; //if (mmu_copy_on_write((uintptr_t)(page << 12))) return 0; } } return 1; } static uintptr_t k2p(void * x) { return ((uintptr_t)x - MODULE_BASE_START) + aarch64_kernel_phys_base; } void mmu_init(uintptr_t memaddr, size_t memsize, uintptr_t firstFreePage, uintptr_t endOfRamDisk) { this_core->current_pml = (union PML*)mmu_get_kernel_directory(); /* Convert from bytes to kibibytes */ total_memory = memsize / 1024; /* We don't currently support gaps in this setup. */ unavailable_memory = 0; /* MAIR setup? */ uint64_t mair = (0x000000000044ff00); asm volatile ("msr MAIR_EL1,%0" :: "r"(mair)); asm volatile ("mrs %0,MAIR_EL1" : "=r"(mair)); dprintf("mmu: MAIR_EL1=0x%016lx\n", mair); asm volatile ("" ::: "memory"); /* Replicate the mapping we already have */ init_page_region[511].raw = k2p(&high_base_pml) | PTE_VALID | PTE_TABLE | PTE_AF; init_page_region[510].raw = k2p(&heap_base_pml) | PTE_VALID | PTE_TABLE | PTE_AF; /* "Identity" map at -512GiB */ for (size_t i = 0; i < 500; ++i) { high_base_pml[i].raw = (i << 30) | PTE_VALID | PTE_AF | (1 << 2); } /* Set up some space to map us */ /* How many 2MiB spans do we need to cover to endOfRamDisk? */ size_t twoms = (endOfRamDisk + (LARGE_PAGE_SIZE - 1)) / LARGE_PAGE_SIZE; /* init_page_region[511] -> high_base_pml[510] -> kbase_pmls[0] -> kbase_pmls[1+n] */ high_base_pml[510].raw = k2p(&kbase_pmls[0]) | PTE_VALID | PTE_TABLE | PTE_AF; for (size_t j = 0; j < twoms; ++j) { kbase_pmls[0][j].raw = k2p(&kbase_pmls[1+j]) | PTE_VALID | PTE_TABLE | PTE_AF; for (int i = 0; i < 512; ++i) { kbase_pmls[1+j][i].raw = (uintptr_t)(aarch64_kernel_phys_base + LARGE_PAGE_SIZE * j + PAGE_SIZE * i) | PTE_VALID | PTE_AF | PTE_SH_A | PTE_TABLE | (1 << 2); } } /* We should be ready to switch to our page directory? */ asm volatile ("msr TTBR0_EL1,%0" : : "r"(k2p(&init_page_region))); asm volatile ("msr TTBR1_EL1,%0" : : "r"(k2p(&init_page_region))); asm volatile ("dsb ishst\ntlbi vmalle1is\ndsb ish\nisb" ::: "memory"); /* Let's map some heap. */ heap_base_pml[0].raw = k2p(&heap_base_pd) | PTE_VALID | PTE_TABLE | PTE_AF; heap_base_pd[0].raw = k2p(&heap_base_pt[0]) | PTE_VALID | PTE_TABLE | PTE_AF; heap_base_pd[1].raw = k2p(&heap_base_pt[512]) | PTE_VALID | PTE_TABLE | PTE_AF; heap_base_pd[2].raw = k2p(&heap_base_pt[1024]) | PTE_VALID | PTE_TABLE | PTE_AF; /* Physical frame allocator. We're gonna do this the same as the one we have x86-64, because * I can't be bothered to think of anything better right now... */ ram_starts_at = memaddr; nframes = (memsize) >> 12; size_t bytesOfFrames = INDEX_FROM_BIT(nframes * 8); bytesOfFrames = (bytesOfFrames + PAGE_LOW_MASK) & PAGE_SIZE_MASK; /* TODO we should figure out where the DTB ends on virt, as that's where we can * start doing this... */ size_t pagesOfFrames = bytesOfFrames >> 12; /* Map pages for it... */ for (size_t i = 0; i < pagesOfFrames; ++i) { heap_base_pt[i].raw = (firstFreePage + (i << 12)) | PTE_VALID | PTE_AF | PTE_SH_A | PTE_TABLE | (1 << 2); } asm volatile ("dsb ishst\ntlbi vmalle1is\ndsb ish\nisb" ::: "memory"); /* Just assume everything is in use. */ frames = (void*)((uintptr_t)KERNEL_HEAP_START); memset((void*)frames, 0x00, bytesOfFrames); /* Set frames as in use... */ for (uintptr_t i = memaddr; i < firstFreePage + bytesOfFrames; i+= PAGE_SIZE) { mmu_frame_set(i); } /* Set kernel space as in use */ for (uintptr_t i = 0; i < twoms * LARGE_PAGE_SIZE; i += PAGE_SIZE) { mmu_frame_set(aarch64_kernel_phys_base + i); } heapStart = (char*)KERNEL_HEAP_START + bytesOfFrames; lowest_available = (firstFreePage + bytesOfFrames) - memaddr; module_base_address = endOfRamDisk + MODULE_BASE_START; if (module_base_address & PAGE_LOW_MASK) { module_base_address = (module_base_address & PAGE_SIZE_MASK) + PAGE_SIZE; } } ================================================ FILE: kernel/arch/aarch64/pl011.c ================================================ /** * @file kernel/arch/aarch64/pl011.c * @brief Rudimentary serial driver for the pl011 uart * * TODO: Headers with sensible register names and macros for bits. * TODO: TTY configuration support. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2022-2025 K. Lange */ #include #include #include #include #include #include #include #include static int pl011_irq(process_t * this, int irq, void * data) { volatile uint32_t * uart_mapped = (volatile uint32_t *)data; uint32_t mis = uart_mapped[16]; if (mis) { if (mis & (1 << 4)) { make_process_ready(this); } uart_mapped[17] = mis; return 1; } return 0; } static void pl011_fill_name(pty_t * pty, char * name) { snprintf(name, 100, "/dev/ttyS0"); } static void pl011_write_out(pty_t * pty, uint8_t c) { volatile uint32_t * uart_mapped = (volatile uint32_t *)pty->_private; uart_mapped[0] = c; } static void pl011_thread(void * arg) { volatile uint32_t * uart_mapped = (volatile uint32_t *)arg; pty_t * pty = pty_new(NULL, 0); pty->write_out = pl011_write_out; pty->fill_name = pl011_fill_name; pty->slave->gid = 2; /* dialout group */ pty->slave->mask = 0660; pty->_private = arg; vfs_mount("/dev/ttyS0", pty->slave, "pl011", ""); /* Set up interrupt callback */ gic_assign_interrupt(1, pl011_irq, (void*)uart_mapped); /* Enable interrupts */ uart_mapped[14] |= (1 << 4); asm volatile ("isb" ::: "memory"); /* Handle incoming data */ while (1) { while ((uart_mapped[6] & (1 << 4))) { switch_task(0); } uint8_t rx = uart_mapped[0]; tty_input_process(pty, rx); } } void pl011_start(void) { uint32_t * uart = dtb_find_node_prefix("pl011"); if (!uart) return; /* I know this is going to be 0x09000000, but let's find it anyway */ uint32_t * reg = dtb_node_find_property(uart, "reg"); uintptr_t uart_base = swizzle(reg[3]); volatile uint32_t * uart_mapped = (volatile uint32_t*)mmu_map_mmio_region(uart_base, 0x1000); /* * PL011 UART configuration * * Setup line control here so we do it atomically, otherwise doing it in the thread * can cause other cores printing debug messages to write when the port is disabled, * which causes QEMU to print warning messages. * * We are missing a few things because this is only intended to work with QEMU. * We should probably add support for baud rate configuration and actually map * the line control configuration to the TTY state. */ uart_mapped[12] = 0; /* UARTCR - Disable the UART */ uart_mapped[11] = 0x70; /* UARTLCR - Stick parity disabled, 8-bit words, tx+rx FIFO enabled; one stop bit, (odd parity), parity disabled, no break */ uart_mapped[12] = 0x301; /* UARTCR - Enable tx/rx, enable UART */ spawn_worker_thread(pl011_thread, "[pl011]", (void*)uart_mapped); } ================================================ FILE: kernel/arch/aarch64/rpi.c ================================================ /** * @file kernel/arch/aarch64/rpi.c * @brief Raspberry Pi-specific stuff. * * Probably going to be mailbox interfaces and such. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2022 K. Lange */ #include #include #include #include #include #include extern char end[]; void rpi_load_ramdisk(struct rpitag * tag, uintptr_t * ramdisk_phys_base, size_t * ramdisk_size) { dprintf("rpi: compressed ramdisk is at %#x \n", tag->ramdisk_start); dprintf("rpi: end of ramdisk is at %#x \n", tag->ramdisk_end); dprintf("rpi: uncompress ramdisk to %#zx \n", (uintptr_t)&end); uint32_t size; memcpy(&size, (void*)(uintptr_t)(tag->ramdisk_end - sizeof(uint32_t)), sizeof(uint32_t)); dprintf("rpi: size of uncompressed ramdisk is %#x\n", size); gzip_inputPtr = (uint8_t*)(uintptr_t)tag->ramdisk_start; gzip_outputPtr = (uint8_t*)&end; if (gzip_decompress()) { dprintf("rpi: gzip failure, not mounting ramdisk\n"); while (1); } dprintf("rpi: ramdisk decompressed\n"); for (size_t i = 0; i < size; i += 64) { asm volatile ("dc cvac, %0\n" :: "r"((uintptr_t)&end + i) : "memory"); } *ramdisk_phys_base = mmu_map_to_physical(NULL, (uintptr_t)&end); *ramdisk_size = size; dprintf("rpi: ramdisk_phys_base set to %#zx\n", *ramdisk_phys_base); } void rpi_set_cmdline(char ** args_out) { *args_out = (char *)"vid=preset start=live-session migrate root=/dev/ram0"; } ================================================ FILE: kernel/arch/aarch64/rpi400/fbterm.c ================================================ #include #include #include static int fbterm_scroll = 0; static void (*write_char)(int, int, int, uint32_t) = NULL; static int (*get_width)(void) = NULL; static int (*get_height)(void) = NULL; static void (*scroll_terminal)(void) = NULL; static int x = 0; static int y = 0; static int term_state = 0; static char term_buf[1024] = {0}; static int term_buf_c = 0; /* Is this in a header somewhere? */ extern uint8_t * lfb_vid_memory; extern uint16_t lfb_resolution_x; extern uint16_t lfb_resolution_y; extern uint16_t lfb_resolution_b; extern uint32_t lfb_resolution_s; extern size_t lfb_memsize; /* Bitmap font details */ #include "../../../../apps/terminal-font.h" #define char_height LARGE_FONT_CELL_HEIGHT #define char_width LARGE_FONT_CELL_WIDTH /* Default colors */ #define BG_COLOR 0xFF000000 /* Background */ #define FG_COLOR 0xFFCCCCCC /* Main text color */ static uint32_t fg_color = FG_COLOR; static uint32_t bg_color = BG_COLOR; extern uint32_t lfb_resolution_s; static inline void set_point(int x, int y, uint32_t value) { if (lfb_resolution_b == 32) { ((uint32_t*)lfb_vid_memory)[y * (lfb_resolution_s/4) + x] = value; } else if (lfb_resolution_b == 24) { lfb_vid_memory[y * lfb_resolution_s + x * 3 + 0] = (value >> 0) & 0xFF; lfb_vid_memory[y * lfb_resolution_s + x * 3 + 1] = (value >> 8) & 0xFF; lfb_vid_memory[y * lfb_resolution_s + x * 3 + 2] = (value >> 16) & 0xFF; } } static void fb_write_char(int _x, int _y, int val, uint32_t color) { if (val > 128) { val = 4; } int x = 1 + _x * char_width; int y = _y * char_height; uint8_t * c = large_font[val]; for (uint8_t i = 0; i < char_height; ++i) { for (uint8_t j = 0; j < char_width; ++j) { if (c[i] & (1 << (LARGE_FONT_MASK-j))) { set_point(x+j,y+i,color); } else { set_point(x+j,y+i,bg_color); } } } } /** * @brief Basic 16-color ANSI palette with Tango colors. */ static uint32_t term_colors[] = { 0xFF000000, 0xFFCC0000, 0xFF4E9A06, 0xFFC4A000, 0xFF3465A4, 0xFF75507B, 0xFF06989A, 0xFFD3D7CF, 0xFF555753, 0xFFEF2929, 0xFF8AE234, 0xFFFCE94F, 0xFF729FCF, 0xFFAD7FA8, 0xFF34E2E2, 0xFFEEEEEC, }; static int fb_get_width(void) { return (lfb_resolution_x - 1) / char_width; } static int fb_get_height(void) { return lfb_resolution_y / char_height; } static void fb_scroll_terminal(void) { memmove(lfb_vid_memory, lfb_vid_memory + sizeof(uint32_t) * lfb_resolution_x * char_height, (lfb_resolution_y - char_height) * lfb_resolution_x * 4); memset(lfb_vid_memory + sizeof(uint32_t) * (lfb_resolution_y - char_height) * lfb_resolution_x, 0x00, char_height * lfb_resolution_x * 4); } static void draw_square(int x, int y) { int center_x = lfb_resolution_x / 2; int center_y = lfb_resolution_y / 2; for (size_t _y = 0; _y < 7; ++_y) { uint32_t color = 0xFF00B2FF - (y * 8 + _y) * 0x200; for (size_t _x = 0; _x < 7; ++_x) { set_point(center_x - 32 + x * 8 + _x, center_y - 32 + y * 8 + _y, color); } } } static void fbterm_draw_logo(void) { uint64_t logo_squares = 0x981818181818FFFFUL; for (size_t y = 0; y < 8; ++y) { for (size_t x = 0; x < 8; ++x) { if (logo_squares & (1 << x)) { draw_square(x,y); } } logo_squares >>= 8; } } static void fbterm_init_framebuffer(void) { write_char = fb_write_char; get_width = fb_get_width; get_height = fb_get_height; scroll_terminal = fb_scroll_terminal; fbterm_draw_logo(); } static void cursor_update(void) { if (x >= get_width()) { x = 0; y++; } if (y >= get_height()) { if (fbterm_scroll) { y--; scroll_terminal(); } else { y = 0; } } } static void process_char(char ch) { if (term_state == 1) { if (ch == '[') { term_buf_c = 0; term_buf[term_buf_c] = '\0'; term_state = 2; } else { term_state = 0; process_char(ch); } return; } else if (term_state == 2) { if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { /* do the thing */ switch (ch) { case 'm': { char * arg = &term_buf[0]; char * next; int argC = 0; int isBold = 0; do { next = strchr(arg, ';'); if (next) { *next = '\0'; next++; } int asInt = atoi(arg); if (asInt == 0) { fg_color = FG_COLOR; bg_color = BG_COLOR; isBold = 0; } else if (asInt == 1) { isBold = 1; } else if (asInt == 22) { fg_color = FG_COLOR; isBold = 0; } else if (asInt >= 30 && asInt <= 37) { fg_color = term_colors[asInt-30 + (isBold ? 8 : 0)]; } else if (asInt >= 90 && asInt <= 97) { fg_color = term_colors[asInt-90 + 8]; } else if (asInt >= 40 && asInt <= 47) { bg_color = term_colors[asInt-40 + (isBold ? 8 : 0)]; } else if (asInt >= 100 && asInt <= 107) { bg_color = term_colors[asInt-100 + 8]; } else if (asInt == 38) { fg_color = FG_COLOR; } else if (asInt == 48) { bg_color = BG_COLOR; } else if (asInt == 7) { uint32_t tmp = fg_color; fg_color = bg_color; bg_color = tmp; } arg = next; argC++; } while (arg); break; } case 'G': { /* Set cursor column */ x = atoi(term_buf) - 1; break; } case 'K': { if (atoi(term_buf) == 0) { for (int i = x; i < get_width(); ++i) { write_char(i,y,' ',bg_color); } } break; } } term_state = 0; } else { term_buf[term_buf_c++] = ch; term_buf[term_buf_c] = '\0'; } return; } else if (ch == '\033') { term_state = 1; return; } write_char(x,y,' ',bg_color); switch (ch) { case '\n': x = 0; y++; break; case '\r': x = 0; break; case '\b': if (x) { x--; write_char(x,y,' ',fg_color); } break; default: if ((unsigned int)ch > 127) return; write_char(x,y,ch,fg_color); x++; break; } cursor_update(); } static size_t (*previous_writer)(size_t,uint8_t*) = NULL; size_t fbterm_write(size_t size, uint8_t *buffer) { if (!buffer) return 0; for (unsigned int i = 0; i < size; ++i) { process_char(buffer[i]); } if (previous_writer) previous_writer(size,buffer); return size; } void fbterm_initialize(void) { if (!lfb_resolution_x) { return; } fbterm_init_framebuffer(); previous_writer = printf_output; printf_output = fbterm_write; printf("fbterm: Generic framebuffer text output enabled.\n"); } ================================================ FILE: kernel/arch/aarch64/rpi400/link.ld ================================================ OUTPUT_FORMAT(elf64-littleaarch64) ENTRY(start) SECTIONS { . = 0x80000; phys = .; .text BLOCK(4K) : ALIGN(4K) { *(.bootstrap) code = .; *(.text) } .rodata BLOCK(4K) : ALIGN(4K) { *(.rodata) } .data BLOCK(4K) : ALIGN(4K) { data = .; *(.data) *(.symbols) PROVIDE(kernel_symbols_start = .); PROVIDE(kernel_symbols_end = .); PROVIDE(bss_start = .); } __bss_start = .; .bss BLOCK(4K) : ALIGN(4K) { bss = .; *(COMMON) *(.bss) *(.stack) } __bss_end = .; __bss_size = __bss_end - __bss_start; /* Some built-in stack space... */ . = ALIGN(0x1000); . = . + 0x1000; __bootstrap_stack_top = .; end = .; /DISCARD/ : { *(.comment) *(.eh_frame) *(.note.gnu.build-id) } } ================================================ FILE: kernel/arch/aarch64/rpi400/main.c ================================================ /** * @file kernel/arch/aarch64/rpi400/main.c * @brief Boot stub for Raspberry Pi 400. * * This gets built into kernel8.img, which embeds the actual kernel and * a compress ramdisk. The bootstub is responsible for acquiring the * initial framebuffer, setting the cores to max speed, setting up the * MMU, and loading the actual kernel at -2GiB. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2022 K. Lange */ #include #include #include #include #include #define MMIO_BASE 0xFE000000UL #define MBOX_BASE (MMIO_BASE + 0xB880) #define MBOX_READ (MBOX_BASE + 0x00) #define MBOX_STATUS (MBOX_BASE + 0x18) #define MBOX_WRITE (MBOX_BASE + 0x20) #define MBOX_FULL 0x80000000 #define MBOX_EMPTY 0x40000000 #define MBOX_RESPONSE 0x80000000 #define MBOX_REQUEST 0 volatile uint32_t __attribute__((aligned(16))) mbox[36]; static uint32_t mmio_read32(uintptr_t addr) { uint32_t res = *((volatile uint32_t*)(addr)); return res; } static void mmio_write32(uintptr_t addr, uint32_t val) { (*((volatile uint32_t*)(addr))) = val; } uint32_t mbox_call(uint8_t ch) { uint32_t r = ((uint32_t)((uintptr_t)&mbox) & ~0xF) | (ch & 0xF); while (mmio_read32(MBOX_STATUS) == MBOX_FULL); /* wait for mailbox to be ready */ mmio_write32(MBOX_WRITE, r); while (1) { while (mmio_read32(MBOX_STATUS) & MBOX_EMPTY); if (r == mmio_read32(MBOX_READ)) return mbox[1] == MBOX_RESPONSE; } } uint8_t * lfb_vid_memory = 0; uint16_t lfb_resolution_x = 0; uint16_t lfb_resolution_y = 0; uint16_t lfb_resolution_b = 0; uint32_t lfb_resolution_s = 0; size_t lfb_memsize = 0; void * malloc(size_t x) { while (1); } #define MB(j) mbox[i++] = j int rpi_fb_init(void) { int i = 0; MB(35 * 4); MB(MBOX_REQUEST); MB(0x48003); MB(8); MB(0); int fb_width = i; MB(1920); int fb_height = i; MB(1080); MB(0x48004); MB(8); MB(8); MB(1920); MB(1080); MB(0x48009); MB(8); MB(8); MB(0); MB(0); MB(0x48005); MB(4); MB(4); int fb_bpp = i; MB(32); MB(0x48006); MB(4); MB(4); MB(1); MB(0x40001); MB(8); MB(8); int fb_pointer = i; MB(4096); int fb_size = i; MB(0); MB(0x40008); MB(4); MB(4); int fb_pitch = i; MB(0); MB(0); if (mbox_call(8) && mbox[fb_bpp] == 32 && mbox[fb_pointer] != 0) { lfb_vid_memory = (uint8_t*)(uintptr_t)(mbox[fb_pointer] & 0x3FFFFFFF); lfb_resolution_x = mbox[fb_width]; lfb_resolution_y = mbox[fb_height]; lfb_resolution_s = mbox[fb_pitch]; lfb_resolution_b = mbox[fb_bpp]; lfb_memsize = mbox[fb_size]; for (unsigned int y = 0; y < lfb_resolution_y; ++y) { for (unsigned int x = 0; x < lfb_resolution_x; ++x) { *(volatile uint32_t *)((uintptr_t)lfb_vid_memory + y * lfb_resolution_s + x * 4) = 0x3ea3f0; } } extern void fbterm_initialize(void); fbterm_initialize(); return 0; } return 1; } void rpi_cpu_freq(void) { int max_rate = 0; { int i = 0; MB(13 * 4); MB(MBOX_REQUEST); MB(0x30004); MB(8); MB(0); MB(3); /* arm core */ int max_hz = i; MB(0); MB(0x30047); MB(8); MB(0); MB(3); /* arm core */ int cur_hz = i; MB(0); MB(0); mbox_call(8); printf("bootstub: max clock rate is %u Hz, current is %u Hz\n", mbox[max_hz], mbox[cur_hz]); max_rate = mbox[max_hz]; } if (max_rate) { int i = 0; MB(9 * 4); MB(MBOX_REQUEST); MB(0x38002); MB(12); MB(0); MB(3); int rate = i; MB(max_rate); MB(0); /* do not skip turbo setting */ MB(0); mbox_call(8); printf("bootstub: clock rate set to %u Hz\n", mbox[rate]); } } extern char _kernel_start[]; extern char _kernel_end[]; extern char _ramdisk_start[]; extern char _ramdisk_end[]; static struct BaseTables { uintptr_t l0_base[512]; uintptr_t l1_high_gbs[512]; uintptr_t l1_low_gbs[512]; uintptr_t l2_kernel[512]; } _baseTables __attribute__((aligned(4096))); #define PTE_VALID (1UL << 0) #define PTE_TABLE (1UL << 1) /* Table attributes */ #define PTE_NSTABLE (1UL << 63) #define PTE_APTABLE (3UL << 61) /* two bits */ #define PTE_APTABLE_A (1UL << 62) #define PTE_APTABLE_B (1UL << 61) #define PTE_UXNTABLE (1UL << 60) #define PTE_PXNTABLE (1UL << 59) /* Block attributes */ #define PTE_UXN (1UL << 54) #define PTE_PXN (1UL << 53) #define PTE_CONTIGUOUS (1UL << 52) #define PTE_NG (1UL << 11) #define PTE_AF (1UL << 10) #define PTE_SH (3UL << 8) /* two bits */ #define PTE_SH_A (1UL << 9) #define PTE_SH_B (1UL << 8) #define PTE_AP (3UL << 6) /* two bits */ #define PTE_AP_A (1UL << 7) #define PTE_AP_B (1UL << 6) #define PTE_NS (1UL << 5) #define PTE_ATTRINDX (7UL << 2) /* three bits */ #define PTE_ATTR_A (1UL << 4) #define PTE_ATTR_B (1UL << 3) #define PTE_ATTR_C (1UL << 2) #define KERNEL_PHYS_BASE 0x2000000UL static void bootstub_mmu_init(void) { /* Map memory */ _baseTables.l0_base[0] = (uintptr_t)&_baseTables.l1_low_gbs | PTE_VALID | PTE_TABLE | PTE_AF; /* equivalent to high_base_pml */ _baseTables.l0_base[511] = (uintptr_t)&_baseTables.l1_high_gbs | PTE_VALID | PTE_TABLE | PTE_AF; /* Mapping for us */ _baseTables.l1_low_gbs[0] = 0x00000000UL | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); _baseTables.l1_low_gbs[1] = 0x40000000UL | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); _baseTables.l1_low_gbs[2] = 0x80000000UL | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); _baseTables.l1_low_gbs[3] = 0xc0000000UL | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); /* -512GB is a map of 64GB of memory */ for (size_t i = 0; i < 64; ++i) { _baseTables.l1_high_gbs[i] = (i << 30) | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); } /* -2GiB, map kernel here */ _baseTables.l1_high_gbs[510] = (uintptr_t)&_baseTables.l2_kernel | PTE_VALID | PTE_TABLE | PTE_AF; for (size_t i = 0; i < 512; ++i) { _baseTables.l2_kernel[i] = (KERNEL_PHYS_BASE + (i << 21)) | PTE_VALID | PTE_AF | PTE_SH_A | (1 << 2); } uint64_t sctlr = 0 | (1UL << 0) /* mmu enabled */ | (1UL << 2) /* cachability */ //| (1UL << 6) | (1UL << 12) /* instruction cachability */ | (1UL << 23) /* SPAN */ | (1UL << 28) /* nTLSMD */ | (1UL << 29) /* LSMAOE */ | (1UL << 20) /* TSCXT */ | (1UL << 7) /* ITD */ ; /* Translate control register */ uint64_t tcr = 0 | (3UL << 32) /* 36 bits? */ | (2UL << 30) /* TG1 4KB granules in TTBR1 */ | (16UL << 16) /* T1SZ 48-bit */ | (3UL << 28) /* SH1 */ | (1UL << 26) /* ORGN1 */ | (1UL << 24) /* IRGN1 */ | (0UL << 14) /* TG0 4KB granules in TTBR0 */ | (16UL << 0) /* T0SZ 48-bit */ | (3UL << 12) /* SH0 */ | (1UL << 10) /* ORGN0 */ | (1UL << 8) /* IRGN0 */ ; /* MAIR setup? */ uint64_t mair = (0x000000000044ff00); asm volatile ("msr MAIR_EL1,%0" :: "r"(mair)); /* Frob bits */ printf("bootstub: setting base values\n"); asm volatile ("msr TCR_EL1,%0" : : "r"(tcr)); asm volatile ("msr TTBR0_EL1,%0" : : "r"(&_baseTables.l0_base)); asm volatile ("msr TTBR1_EL1,%0" : : "r"(&_baseTables.l0_base)); printf("bootstub: frobbing bits\n"); asm volatile ("dsb ishst\ntlbi vmalle1is\ndsb ish\nisb" ::: "memory"); printf("bootstub: enabling mmu\n"); asm volatile ("msr SCTLR_EL1,%0" : : "r"(sctlr)); asm volatile ("isb" ::: "memory"); printf("bootstub: MMU initialized\n"); } static void bootstub_load_kernel(Elf64_Header * header) { /* Find load headers */ for (int i = 0; i < header->e_phnum; ++i) { Elf64_Phdr * phdr = (void*)((uintptr_t)header + (header->e_phoff + header->e_phentsize * i)); if (phdr->p_type == PT_LOAD) { printf("bootstub: Load %zu bytes @ %zx from off %zx\n", phdr->p_memsz, phdr->p_vaddr, phdr->p_offset); memset((void*)phdr->p_vaddr, 0, phdr->p_memsz); memcpy((void*)phdr->p_vaddr, (void*)((uintptr_t)header + phdr->p_offset), phdr->p_filesz); } else { printf("bootstub: Skip phdr %d\n", i); } } } struct rpitag tag_data = {0}; static void bootstub_start_kernel(uintptr_t dtb, Elf64_Header * header) { printf("bootstub: Jump to kernel entry point at %zx\n", header->e_entry); void (*entry)(uintptr_t,uintptr_t,uintptr_t) = (void(*)(uintptr_t,uintptr_t,uintptr_t))header->e_entry; entry(dtb, KERNEL_PHYS_BASE, (uintptr_t)&tag_data); } static void bootstub_exit_el2(void) { uint64_t spsr_el2, sctlr_el1; asm volatile ("mrs %0, SPSR_EL2\n" :"=r"(spsr_el2)); printf("bootstub: SPSR_EL2=%#zx\n", spsr_el2); asm volatile ("mrs %0, SCTLR_EL1\n" :"=r"(sctlr_el1)); printf("bootstub: SCTLR_EL1=%#zx\n", sctlr_el1); /* get us out of EL2 */ asm volatile ( "ldr x0, =0x1004\n" "mrs x1, SCTLR_EL2\n" "orr x1, x1, x0\n" "msr SCTLR_EL2, x1\n" "ldr x0, =0x30d01804\n" "msr SCTLR_EL1, x0\n" ::: "x0", "x1"); printf("bootstub: sctlr_el1 set\n"); asm volatile ( "ldr x0, =0x80000000\n" "msr HCR_EL2, x0\n" ::: "x0"); printf("bootstub: hcr set\n"); #if 0 asm volatile ( "ldr x0, =0x431\n" "msr SCR_EL3, x0\n" ::: "x0"); printf("bootstub: SCR_EL3 set\n"); #endif asm volatile ( "ldr x0, =0x3c5\n" "msr SPSR_EL2, x0\n" ::: "x0"); printf("bootstub: spsr_el2 set\n"); asm volatile ( "mov x0, sp\n" "msr SP_EL1, x0\n" "adr x0, in_el1\n" "msr ELR_EL2, x0\n" "eret\n" "in_el1:\n" ::: "x0", "memory", "cc" ); printf("bootstub: out of EL2?\n"); uint64_t CurrentEL; asm volatile ("mrs %0, CurrentEL" : "=r"(CurrentEL)); printf("in el%zu\n", CurrentEL >> 2); } void kmain(uint32_t dtb_address, uint32_t base_addr) { if (rpi_fb_init()) { /* Panic */ while (1); } printf("rpi4 bootstub, kernel base address is %#x, dtb is at %#x\n", base_addr, dtb_address); printf("framebuffer (%u x %u) @ %#zx\n", lfb_resolution_x, lfb_resolution_y, (uintptr_t)lfb_vid_memory); uint64_t CurrentEL; asm volatile ("mrs %0, CurrentEL" : "=r"(CurrentEL)); printf("in el%zu\n", CurrentEL >> 2); printf("kernel @ %#zx (%zu bytes) ramdisk @ %#zx (%zu bytes)\n", (uintptr_t)&_kernel_start, (size_t)((uintptr_t)&_kernel_end - (uintptr_t)&_kernel_start), (uintptr_t)&_ramdisk_start, (size_t)((uintptr_t)&_ramdisk_end - (uintptr_t)&_ramdisk_start)); rpi_cpu_freq(); bootstub_exit_el2(); bootstub_mmu_init(); tag_data.phys_addr = (uint32_t)(uintptr_t)lfb_vid_memory; tag_data.x = lfb_resolution_x; tag_data.y = lfb_resolution_y; tag_data.s = lfb_resolution_s; tag_data.b = lfb_resolution_b; tag_data.size = lfb_memsize; tag_data.ramdisk_start = (uintptr_t)&_ramdisk_start; tag_data.ramdisk_end = (uintptr_t)&_ramdisk_end; Elf64_Header *header = (void*)&_kernel_start; bootstub_load_kernel(header); /* Jump to kernel */ bootstub_start_kernel(dtb_address, header); while (1); } ================================================ FILE: kernel/arch/aarch64/rpi400/start.S ================================================ .extern __bootstrap_stack_top .extern __bss_start .extern __bss_size .section ".bootstrap" .globl start start: ldr x30, =__bootstrap_stack_top mov x1, x4 mov sp, x30 ldr x5, =__bss_start ldr w6, =__bss_size 3: cbz w6, 4f str xzr, [x5], #8 sub w6, w6, #1 cbnz w6, 3b 4: bl kmain hang: b hang .section ".rodata" .align 12 .globl _kernel_start _kernel_start: .incbin "misaka-kernel" .globl _kernel_end _kernel_end: .align 12 .globl _ramdisk_start _ramdisk_start: .incbin "ramdisk.igz" .global _ramdisk_end _ramdisk_end: ================================================ FILE: kernel/arch/aarch64/rpi_miniuart.c ================================================ /** * @file kernel/arch/aarch64/rpi_miniuart.c * @brief Rudimentary serial driver for the rpi's miniuart * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2022 K. Lange */ #include #include #include #include #include #include #include #include #define UART_BAUD 921600 //#define UART_BAUD 115200 static uintptr_t gpio_base = 0; static uint32_t mmio_read(uintptr_t addr) { uint32_t res = *((volatile uint32_t*)(addr)); return res; } static void mmio_write(uintptr_t addr, uint32_t val) { (*((volatile uint32_t*)(addr))) = val; } /** * GPIO initialization mostly from Adam Greenwood-Byrne's rpi4-osdev */ #define PERI_BASE 0xFE000000 #define GPIO_BASE (PERI_BASE + 0x200000) #define GPFSEL0 (0x00) #define GPSET0 (0x1c) #define GPCLR0 (0x28) #define GPPUPPDN0 (0xe4) /** * AUX register offsets from the peripheral manual */ #define AUX_IRQ 0x00 #define AUX_ENABLES 0x04 #define AUX_MU_IO_REG 0x40 #define AUX_MU_IER_REG 0x44 #define AUX_MU_IIR_REG 0x48 #define AUX_MU_LCR_REG 0x4c #define AUX_MU_MCR_REG 0x50 #define AUX_MU_LSR_REG 0x54 #define AUX_MU_CNTL_REG 0x60 #define AUX_MU_BAUD_REG 0x68 #define BAUD_CALC(rate) ((500000000UL/(rate*8))-1) static int gpio_call(uint32_t pin, uint32_t value, uint32_t base, uint32_t field_size, uint32_t field_max) { uint32_t mask = (1 << field_size) - 1; if (pin > field_max) return 0; if (value > mask) return 0; uint32_t fields = 32 / field_size; uint32_t reg = base + (pin / fields) * 4; uint32_t shift = (pin % fields) * field_size; uint32_t cur = mmio_read(gpio_base + reg); cur &= ~(mask << shift); cur |= (value << shift); mmio_write(gpio_base + reg, cur); return 1; } static int miniuart_irq(process_t * this, int irq, void * data) { uintptr_t uart_mapped = (uintptr_t)data; asm volatile ("dmb sy" ::: "memory"); uint32_t aux_cause = mmio_read(uart_mapped + AUX_IRQ); if (aux_cause & 1) { uint32_t uart_iir = mmio_read(uart_mapped + AUX_MU_IIR_REG); if (uart_iir & (1 << 2)) { make_process_ready(this); } return 1; } return 0; } static void miniuart_fill_name(pty_t * pty, char * name) { snprintf(name, 100, "/dev/ttyUART1"); } static void miniuart_write_out(pty_t * pty, uint8_t c) { uintptr_t uart_mapped = (uintptr_t)pty->_private; while (!(mmio_read(uart_mapped + AUX_MU_LSR_REG) & 0x20)); mmio_write(uart_mapped + AUX_MU_IO_REG, (uint8_t)c); } static void miniuart_thread(void * arg) { uintptr_t uart_mapped = (uintptr_t)arg; gic_assign_interrupt(0x5D, miniuart_irq, (void*)uart_mapped); mmio_write(uart_mapped + AUX_ENABLES, 1); /* Enable mini uart */ mmio_write(uart_mapped + AUX_MU_IER_REG, 0); /* Disable interrupts while we set up */ mmio_write(uart_mapped + AUX_MU_CNTL_REG, 0); /* Disable transmit/receive */ mmio_write(uart_mapped + AUX_MU_LCR_REG, 3); /* 8-bit output (XXX shouldn't this just be '1'?) */ mmio_write(uart_mapped + AUX_MU_MCR_REG, 0); /* RTS is high */ mmio_write(uart_mapped + AUX_MU_IER_REG, 0); /* Disable interrupts again? */ mmio_write(uart_mapped + AUX_MU_IIR_REG, 0xC6); /* ack and clear interrupts */ mmio_write(uart_mapped + AUX_MU_BAUD_REG, BAUD_CALC(UART_BAUD)); asm volatile ("dmb sy" ::: "memory"); gpio_call(14, 0, GPPUPPDN0, 2, 53); gpio_call(14, 2, GPFSEL0, 3, 53); gpio_call(15, 0, GPPUPPDN0, 2, 53); gpio_call(15, 2, GPFSEL0, 3, 53); asm volatile ("dmb sy" ::: "memory"); mmio_write(uart_mapped + AUX_MU_CNTL_REG, 3); /* tx, rx enable */ pty_t * pty = pty_new(NULL, 0); pty->write_out = miniuart_write_out; pty->fill_name = miniuart_fill_name; pty->slave->gid = 2; /* dialout group */ pty->slave->mask = 0660; pty->_private = arg; pty->tios.c_cflag = CREAD | CS8 | B921600; vfs_mount("/dev/ttyUART1", pty->slave, "rpiminiuart", ""); /* Enable interrupts */ mmio_write(uart_mapped + AUX_MU_IER_REG, 1); /* enable receive interrupt */ mmio_write(uart_mapped + AUX_MU_IIR_REG, 0xC6); /* ack and clear interrupts */ asm volatile ("isb" ::: "memory"); /* Handle incoming data */ while (1) { while (!(mmio_read(uart_mapped + AUX_MU_LSR_REG) & 0x01)) { switch_task(0); } uint8_t rx = mmio_read(uart_mapped + AUX_MU_IO_REG); tty_input_process(pty, rx); } } void miniuart_start(void) { gpio_base = (uintptr_t)mmu_map_mmio_region(GPIO_BASE, 0x1000); void * uart_mapped = mmu_map_mmio_region(0xFE215000, 0x1000); spawn_worker_thread(miniuart_thread, "[miniuart]", uart_mapped); } ================================================ FILE: kernel/arch/aarch64/smp.c ================================================ /** * @file kernel/arch/aarch64/smp.c * @brief Routines for locating and starting other CPUs. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2022 K. Lange */ #include #include #include #include #include #include #include #include #include extern process_t * spawn_kidle(int); extern void timer_start(void); extern void aarch64_processor_data(void); static uint32_t cpu_on = 0; static int method = 0; static volatile uint32_t _smp_mutex = 0; volatile uintptr_t aarch64_jmp_target = 0; volatile uint64_t aarch64_sctlr = 0; volatile uint64_t aarch64_tcr = 0; volatile uint64_t aarch64_mair = 0; volatile uint64_t aarch64_vbar = 0; volatile uintptr_t aarch64_ttbr0 = 0; volatile uintptr_t aarch64_ttbr1 = 0; volatile uintptr_t aarch64_stack = 0; void ap_start(uint64_t core_id) { dprintf("smp: core %zu is online\n", core_id); extern void arch_set_core_base(uintptr_t base); arch_set_core_base((uintptr_t)&processor_local_data[core_id]); this_core->cpu_id = core_id; extern void fpu_enable(void); fpu_enable(); aarch64_processor_data(); this_core->current_pml = mmu_get_kernel_directory(); this_core->kernel_idle_task = spawn_kidle(0); this_core->current_process = this_core->kernel_idle_task; asm volatile ("isb"); timer_start(); _smp_mutex = 1; asm volatile ("isb" ::: "memory"); switch_next(); } void smp_bootstrap(void) { asm volatile ( /* Store x0, which is our core ID */ "mov x3, x0\n" /* Set up TTBR1 with high memory directory */ "ldr x0, aarch64_ttbr1\n" "msr TTBR1_EL1, x0\n" /* Load stack pointer */ "ldr x0, aarch64_stack\n" "mov sp, x0\n" /* Set up TTBR0 with our temporary directory */ "ldr x0, aarch64_ttbr0\n" "msr TTBR0_EL1, x0\n" "dsb ishst\n" "tlbi vmalle1is\n" "dsb ish\n" "isb\n" /* Load VBAR from first core */ "ldr x0, aarch64_vbar\n" "msr VBAR_EL1, x0\n" /* Load MAIR from first core */ "ldr x0, aarch64_mair\n" "msr MAIR_EL1, x0\n" /* Load TCR from first core */ "ldr x0, aarch64_tcr\n" "msr TCR_EL1, x0\n" /* Load SCTLR from first core, enable mmu */ "ldr x0, aarch64_sctlr\n" "ldr x1, aarch64_jmp_target\n" "msr SCTLR_EL1, x0\n" "isb\n" /* Restore core ID as argument */ "mov x0, x3\n" /* Jump to C entrypoint */ "br x1\n"); __builtin_unreachable(); } static void start_cpu(uint32_t * node) { uint32_t * cpuid = dtb_node_find_property(node, "reg"); if (!cpuid) { dprintf("smp: skipping cpus node without 'reg' parameter\n"); return; } uint32_t num = swizzle(cpuid[2]); dprintf("smp: cpu node %d %#zx '%s'\n", num, (uintptr_t)node, (char *)(node)); if (num == 0) return; if (method == 0x637668) { _smp_mutex = 0; aarch64_stack = (uintptr_t)sbrk(4096) + 4096; uint64_t x0 = cpu_on; uint64_t x1 = num; uint64_t x2 = mmu_map_to_physical(NULL, (uintptr_t)&smp_bootstrap); uint64_t x3 = num; asm volatile ("dc civac, %0\ndsb sy" :: "r"(&aarch64_stack) : "memory"); asm volatile ("isb" ::: "memory"); asm volatile ( "mov x0, %0\n" "mov x1, %1\n" "mov x2, %2\n" "mov x3, %3\n" "hvc 0" :: "r"(x0), "r"(x1), "r"(x2), "r"(x3) : "x0","x1","x2","x3"); while (_smp_mutex == 0); processor_count = num + 1; } else { dprintf("smp: Don't know how to turn on with '%#x'\n", method); /* smc? */ } } #define _pagemap __attribute__((aligned(4096))) = {0} static union PML startup_ttbr0[2][512] _pagemap; void aarch64_smp_start(void) { uint32_t * psci = dtb_find_node("psci"); if (!psci) { dprintf("smp: no 'psci' interface node\n"); return; } uint32_t * psci_method = dtb_node_find_property(psci, "method"); uint32_t * psci_cpu_on = dtb_node_find_property(psci, "cpu_on"); if (!psci_method || !psci_cpu_on) { dprintf("smp: don't know how to turn on these cores\n"); return; } dprintf("smp: startup method is '0x%x'\n", psci_method[2]); method = psci_method[2]; cpu_on = swizzle(psci_cpu_on[2]); uint32_t * cpus = dtb_find_node("cpus"); if (!cpus) { dprintf("smp: no 'cpus' node\n"); return; } aarch64_jmp_target = (uintptr_t)ap_start; asm volatile ("mrs %0, MAIR_EL1" : "=r"(aarch64_mair)); asm volatile ("mrs %0, TCR_EL1" : "=r"(aarch64_tcr)); asm volatile ("mrs %0, SCTLR_EL1" : "=r"(aarch64_sctlr)); asm volatile ("mrs %0, VBAR_EL1" : "=r"(aarch64_vbar)); startup_ttbr0[0][0].raw = mmu_map_to_physical(NULL, (uintptr_t)&startup_ttbr0[1]) | (0x3) | (1 << 10); for (long i = 0; i < 512; ++i) { startup_ttbr0[1][i].raw = (i << 30) | (1 << 2) | 1 | (1 << 10); } aarch64_ttbr0 = mmu_map_to_physical(NULL, (uintptr_t)&startup_ttbr0[0]); aarch64_ttbr1 = mmu_map_to_physical(NULL, (uintptr_t)mmu_get_kernel_directory()); asm volatile ( "dsb ishst\n" "tlbi vmalle1is\n" "dsb ish\n" "isb\n" ); dtb_callback_direct_children(cpus, start_cpu); } void rpi_smp_exit_el2(void) { asm volatile ( "ldr x0, =0x1004\n" "mrs x1, SCTLR_EL2\n" "orr x1, x1, x0\n" "msr SCTLR_EL2, x1\n" "ldr x0, =0x30d01804\n" "msr SCTLR_EL1, x0\n" "ldr x0, =0x80000000\n" "msr HCR_EL2, x0\n" "ldr x0, =0x3c5\n" "msr SPSR_EL2, x0\n" "adr x0, smp_bootstrap\n" "msr ELR_EL2, x0\n" "mov x0, x6\n" "eret\n" ::: "x0", "x1"); __builtin_unreachable(); } void rpi_smp_init(void) { aarch64_jmp_target = (uintptr_t)ap_start; asm volatile ("mrs %0, MAIR_EL1" : "=r"(aarch64_mair)); asm volatile ("mrs %0, TCR_EL1" : "=r"(aarch64_tcr)); asm volatile ("mrs %0, SCTLR_EL1" : "=r"(aarch64_sctlr)); asm volatile ("mrs %0, VBAR_EL1" : "=r"(aarch64_vbar)); startup_ttbr0[0][0].raw = mmu_map_to_physical(NULL, (uintptr_t)&startup_ttbr0[1]) | (0x3) | (1 << 10); for (long i = 0; i < 512; ++i) { startup_ttbr0[1][i].raw = (i << 30) | (2 << 2) | 1 | (1 << 10); } aarch64_ttbr0 = mmu_map_to_physical(NULL, (uintptr_t)&startup_ttbr0[0]); aarch64_ttbr1 = mmu_map_to_physical(NULL, (uintptr_t)mmu_get_kernel_directory()); asm volatile ( "dsb ishst\n" "tlbi vmalle1is\n" "dsb ish\n" "isb\n" ); asm volatile ("dc cvac, %0\n" :: "r"(&aarch64_jmp_target)); asm volatile ("dc cvac, %0\n" :: "r"(&aarch64_mair)); asm volatile ("dc cvac, %0\n" :: "r"(&aarch64_tcr)); asm volatile ("dc cvac, %0\n" :: "r"(&aarch64_sctlr)); asm volatile ("dc cvac, %0\n" :: "r"(&aarch64_vbar)); asm volatile ("dc cvac, %0\n" :: "r"(&aarch64_ttbr0)); asm volatile ("dc cvac, %0\n" :: "r"(&aarch64_ttbr1)); asm volatile ("dc cvac, %0\n" :: "r"(&startup_ttbr0[0])); asm volatile ("dc cvac, %0\n" :: "r"(&startup_ttbr0[1])); uintptr_t spinners[] = {0xd8, 0xe0, 0xe8, 0xf0}; uintptr_t low_mem = (uintptr_t)mmu_map_mmio_region(0, 0x1000); union PML * p = mmu_get_page(low_mem, 0); p->bits.page = 0; asm volatile ("dsb ishst\ntlbi vmalle1is\ndsb ish\nisb" ::: "memory"); dprintf("smp: zero page mapped at %#zx, page is %#zx\n", low_mem, mmu_map_to_physical(mmu_get_kernel_directory(), low_mem)); for (int i = 1; i < 4; ++i) { _smp_mutex = 0; aarch64_stack = (uintptr_t)sbrk(4096) + 4096; asm volatile ("dc cvac, %0\n" :: "r"(&aarch64_stack)); uintptr_t target = mmu_map_to_physical(NULL, (uintptr_t)&rpi_smp_exit_el2); *(volatile uintptr_t*)(low_mem + spinners[i]) = target; asm volatile ("dmb sy\nisb\ndc cvac, %0\nisb\nsev" :: "r"(low_mem) : "memory"); while (_smp_mutex == 0); processor_count = i + 1; } } ================================================ FILE: kernel/arch/aarch64/traceback.c ================================================ /** * @file kernel/arch/aarch64/traceback.c * @brief Kernel fault traceback generator. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include extern char end[]; static uintptr_t matching_symbol(uintptr_t ip, char ** name) { hashmap_t * symbols = ksym_get_map(); uintptr_t best_match = 0; for (size_t i = 0; i < symbols->size; ++i) { hashmap_entry_t * x = symbols->entries[i]; while (x) { void* sym_addr = x->value; char* sym_name = x->key; if ((uintptr_t)sym_addr < ip && (uintptr_t)sym_addr > best_match) { best_match = (uintptr_t)sym_addr; *name = sym_name; } x = x->next; } } return best_match; } static int validate_pointer(uintptr_t base, size_t size) { uintptr_t end = size ? (base + (size - 1)) : base; uintptr_t page_base = base >> 12; uintptr_t page_end = end >> 12; for (uintptr_t page = page_base; page <= page_end; ++page) { if ((page & 0xffff800000000) != 0 && (page & 0xffff800000000) != 0xffff800000000) return 0; union PML * page_entry = mmu_get_page_other(this_core->current_process->thread.page_directory->directory, page << 12); if (!page_entry) return 0; if (!page_entry->bits.present) return 0; } return 1; } static void dump_traceback(uintptr_t ip, uintptr_t bp, uintptr_t x30) { int depth = 0; int max_depth = 20; while (bp && ip && depth < max_depth) { dprintf(" 0x%016zx ", ip); #if 0 if (ip >= 0xffffffff80000000UL) { char * name = NULL; struct LoadedModule * mod = find_module(ip, &name); if (mod) { dprintf("\a in module '%s', base address %#zx (offset %#zx)\n", name, mod->baseAddress, ip - mod->baseAddress); } else { dprintf("\a (unknown)\n"); } #else if (0) { #endif } else if (ip <= 0x800000000000) { dprintf("\a in userspace\n"); } else if (ip >= 0xffffffff80000000UL && ip <= (uintptr_t)&end) { /* Find symbol match */ char * name; uintptr_t addr = matching_symbol(ip, &name); if (!addr) { dprintf("\a (no match)\n"); } else { dprintf("\a %s+0x%zx\n", name, ip-addr); } } else { dprintf("\a (unknown)\n"); } if (!validate_pointer(bp, sizeof(uintptr_t)) || !validate_pointer(bp + sizeof(uintptr_t), sizeof(uintptr_t))) { break; } if (depth == 0) { ip = x30; } else { ip = *(uintptr_t*)(bp + sizeof(uintptr_t)); bp = *(uintptr_t*)(bp); } depth++; } } void aarch64_safe_dump_traceback(uintptr_t elr, struct regs * r) { dump_traceback(elr, r->x29, r->x30); } /* We need to pull the frame address from the caller or this isn't going work, but * gcc is going to warn that that is unsafe... */ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wframe-address" /** * @brief Display a traceback from the current call context. */ void arch_dump_traceback(void) { dump_traceback((uintptr_t)arch_dump_traceback+1, (uintptr_t)__builtin_frame_address(1), (uintptr_t)__builtin_return_address(0)); } #pragma GCC diagnostic pop ================================================ FILE: kernel/arch/aarch64/virtio.c ================================================ /** * @file kernel/arch/aarch64/virtio.c * @brief Rudimentary, hacky implementations of virtio input devices. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include static fs_node_t * mouse_pipe; static fs_node_t * vmmouse_pipe; static fs_node_t * keyboard_pipe; struct virtio_device_cfg { volatile uint8_t select; volatile uint8_t subsel; volatile uint8_t size; volatile uint8_t pad[5]; union { struct { volatile uint32_t min; volatile uint32_t max; volatile uint32_t fuzz; volatile uint32_t flat; volatile uint32_t res; } tablet_data; uint8_t str[128]; } data; }; struct virtio_common_cfg { volatile uint32_t dev_feature_select; volatile uint32_t dev_feature; volatile uint32_t guest_feature_select; volatile uint32_t guest_feature; volatile uint16_t msix; volatile uint16_t queues; volatile uint8_t device_status; volatile uint8_t config_generation; volatile uint16_t queue_select; volatile uint16_t queue_size; volatile uint16_t queue_msix_vector; volatile uint16_t queue_enable; volatile uint16_t queue_notify_off; /* queue stuff */ volatile uint64_t queue_desc; volatile uint64_t queue_avail; volatile uint64_t queue_used; }; struct virtio_buffer { uint64_t addr; uint32_t length; uint16_t flags; uint16_t next; }; struct virtio_avail { uint16_t flags; volatile uint16_t index; uint16_t ring[64]; uint16_t int_index; }; struct virtio_ring { uint32_t index; uint32_t length; }; struct virtio_used { uint16_t flags; volatile uint16_t index; struct virtio_ring ring[64]; uint16_t int_index; }; struct virtio_queue { struct virtio_buffer buffers[64]; struct virtio_avail available; struct virtio_used used; }; struct virtio_input_event { uint16_t type; uint16_t code; uint32_t value; }; int virtio_tablet_responder(process_t * this, int irq, void * data) { uint8_t cause = *(volatile uint8_t *)data; if (cause == 1) { make_process_ready(this); return 1; } return 0; } int virtio_keyboard_responder(process_t * this, int irq, void * data) { uint8_t cause = *(volatile uint8_t *)data; if (cause == 1) { make_process_ready(this); return 1; } return 0; } static void try_to_get_boot_processor(void) { /* We would prefer these virtio startup processes run on the boot CPU, but * it's not the end of the world if they don't. If we're not on the boot * CPU, yield for a bit to try to get on it... */ uint64_t expire = arch_perf_timer() + 100000UL * arch_cpu_mhz(); while (this_core->cpu_id != 0) { if (arch_perf_timer() >= expire) break; switch_task(1); } } static void virtio_tablet_thread(void * data) { try_to_get_boot_processor(); uint32_t device = (uintptr_t)data; uintptr_t t = 0x12000000; pci_write_field(device, PCI_BAR4, 4, t|8); pci_write_field(device, PCI_COMMAND, 2, 4|2|1); struct virtio_device_cfg * cfg = (void*)((char*)mmu_map_mmio_region(t + 0x2000, 0x1000)); cfg->select = 1; /* ask for name */ cfg->subsel = 0; asm volatile ("isb" ::: "memory"); dprintf("virtio: found '%s'\n", cfg->data.str); void * irq_region = mmu_map_mmio_region(t + 0x1000, 0x1000); int irq; gic_map_pci_interrupt("virtio-tablet", device, &irq, virtio_tablet_responder, irq_region); dprintf("virtio-tablet: irq is %d\n", irq); /* figure out range values */ cfg->select = 0x12; cfg->subsel = 0; /* X */ asm volatile ("isb" ::: "memory"); uint32_t max_x = cfg->data.tablet_data.max; cfg->select = 0x12; cfg->subsel = 1; /* X */ asm volatile ("isb" ::: "memory"); uint32_t max_y = cfg->data.tablet_data.max; dprintf("virtio: %d x %d max coordinates\n", max_x, max_y); cfg->select = 0; cfg->subsel = 0; asm volatile ("isb" ::: "memory"); struct virtio_common_cfg * common = (void*)((char*)mmu_map_mmio_region(t, 0x1000)); common->device_status = 0; asm volatile ("isb" ::: "memory"); int queue_size = common->queue_size; dprintf("virtio: queue size is %u\n", queue_size); /* get us one page */ size_t queue_phys = mmu_allocate_a_frame() << 12; struct virtio_queue * queue = mmu_map_mmio_region(queue_phys, 4096); asm volatile ("isb" ::: "memory"); memset(queue, 0, sizeof(struct virtio_queue)); asm volatile ("isb" ::: "memory"); common->queue_select = 0; common->queue_desc = queue_phys; common->queue_avail = (queue_phys) + offsetof(struct virtio_queue, available); common->queue_used = (queue_phys) + offsetof(struct virtio_queue, used); asm volatile ("isb" ::: "memory"); size_t buffers_base = mmu_allocate_a_frame() << 12; volatile struct virtio_input_event * buffers = mmu_map_mmio_region(buffers_base, 4096); mmu_get_page((uintptr_t)buffers, 0)->bits.attrindx = 2; for (int i = 0; i < queue_size; ++i) { queue->buffers[i].addr = buffers_base + i * sizeof(struct virtio_input_event); queue->buffers[i].length = sizeof(struct virtio_input_event); queue->buffers[i].flags = 2; queue->buffers[i].next = 0; queue->available.ring[i] = i; } queue->available.index = 0; asm volatile ("isb" ::: "memory"); common->queue_enable = 1; asm volatile ("isb" ::: "memory"); common->device_status = 4; asm volatile ("isb" ::: "memory"); uint16_t index = 0; uint32_t x = 0; uint32_t y = 0; int button_left = 0; int button_right = 0; int button_middle = 0; int button_scroll_down = 0; int button_scroll_up = 0; queue->available.index = queue_size-1; while (1) { /* Inform the device we have room */ while (queue->used.index == index) { switch_task(0); asm volatile ("dc ivac, %0\ndsb sy" :: "r"(&queue->used) : "memory"); } uint16_t them = queue->used.index; for (; index != them; index++) { asm volatile ("dc ivac, %0\ndsb sy" :: "r"(&buffers[index%queue_size]) : "memory"); struct virtio_input_event evt = buffers[index%queue_size]; while (evt.type == 0xFF) { evt = buffers[index%queue_size]; dprintf("virtio-tablet: bad packet %d (them=%d)\n", index, them); } buffers[index%queue_size].type = 0xFF; asm volatile ("isb\ndsb sy" :: "r"(buffers) : "memory"); if (evt.type == 3) { /* movement */ if (evt.code == 0) { x = (evt.value * lfb_resolution_x) / max_x; } else if (evt.code == 1) { y = (evt.value * lfb_resolution_y) / max_y; } } else if (evt.type == 1) { /* button */ if (evt.code == 0x110) { button_left = evt.value; } else if (evt.code == 0x111) { button_right = evt.value; } else if (evt.code == 0x112) { button_middle = evt.value; } else if (evt.code == 0x150) { button_scroll_down = 1; } else if (evt.code == 0x151) { button_scroll_up = 1; } } else if (evt.type == 0) { #define DISCARD_POINT 32 mouse_device_packet_t packet; packet.magic = MOUSE_MAGIC; packet.x_difference = x; packet.y_difference = y; packet.buttons = (button_left ? LEFT_CLICK : 0) | (button_right ? RIGHT_CLICK : 0) | (button_middle ? MIDDLE_CLICK : 0) | (button_scroll_down ? MOUSE_SCROLL_DOWN : 0) | (button_scroll_up ? MOUSE_SCROLL_UP : 0); button_scroll_down = 0; button_scroll_up = 0; mouse_device_packet_t bitbucket; while (pipe_size(vmmouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) { read_fs(vmmouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket); } write_fs(vmmouse_pipe, 0, sizeof(packet), (uint8_t *)&packet); } //buffers[index%queue_size].type = 0xFF; asm volatile ("isb" ::: "memory"); queue->available.index++; } } } static const uint8_t ext_key_map[256] = { [0x63] = 0x37, /* print screen */ [0x66] = 0x47, /* home */ [0x67] = 0x48, /* UP */ [0x68] = 0x49, /* page up */ [0x6c] = 0x50, /* DOWN */ [0x69] = 0x4B, /* LEFT */ [0x6a] = 0x4D, /* RIGHT */ [0x6b] = 0x4F, /* end */ [0x6d] = 0x51, /* page down */ [0x7d] = 0x5b, /* left super */ }; static void virtio_keyboard_thread(void * data) { try_to_get_boot_processor(); uint32_t device = (uintptr_t)data; uintptr_t t = 0x12100000; pci_write_field(device, PCI_BAR4, 4, t|8); pci_write_field(device, PCI_COMMAND, 2, 4|2|1); struct virtio_device_cfg * cfg = (void*)((char*)mmu_map_mmio_region(t + 0x2000, 0x1000)); cfg->select = 1; /* ask for name */ cfg->subsel = 0; asm volatile ("isb" ::: "memory"); dprintf("virtio: found '%s'\n", cfg->data.str); void * irq_region = mmu_map_mmio_region(t + 0x1000, 0x1000); int irq; gic_map_pci_interrupt("virtio-keyboard", device, &irq, virtio_keyboard_responder, irq_region); dprintf("virtio-keyboard: irq is %d\n", irq); cfg->select = 0; cfg->subsel = 0; asm volatile ("isb" ::: "memory"); struct virtio_common_cfg * common = (void*)((char*)mmu_map_mmio_region(t, 0x1000)); common->device_status = 0; asm volatile ("isb" ::: "memory"); int queue_size = common->queue_size; dprintf("virtio: queue size is %u\n", queue_size); /* get us one page */ size_t queue_phys = mmu_allocate_a_frame() << 12; struct virtio_queue * queue = mmu_map_mmio_region(queue_phys, 4096); asm volatile ("isb" ::: "memory"); memset(queue, 0, sizeof(struct virtio_queue)); asm volatile ("isb" ::: "memory"); common->queue_select = 0; common->queue_desc = queue_phys; common->queue_avail = (queue_phys) + offsetof(struct virtio_queue, available); common->queue_used = (queue_phys) + offsetof(struct virtio_queue, used); asm volatile ("isb" ::: "memory"); size_t buffers_base = mmu_allocate_a_frame() << 12; volatile struct virtio_input_event * buffers = mmu_map_mmio_region(buffers_base, 4096); mmu_get_page((uintptr_t)buffers, 0)->bits.attrindx = 2; for (int i = 0; i < queue_size; ++i) { queue->buffers[i].addr = buffers_base + i * sizeof(struct virtio_input_event); queue->buffers[i].length = sizeof(struct virtio_input_event); queue->buffers[i].flags = 2; queue->buffers[i].next = 0; queue->available.ring[i] = i; } queue->available.index = 0; asm volatile ("isb" ::: "memory"); common->queue_enable = 1; asm volatile ("isb" ::: "memory"); common->device_status = 4; asm volatile ("isb" ::: "memory"); uint16_t index = 0; queue->available.index = queue_size-1; while (1) { /* Inform the device we have room */ while (queue->used.index == index) { switch_task(0); asm volatile ("dc ivac, %0\ndsb sy" :: "r"(&queue->used) : "memory"); } uint16_t them = queue->used.index; for (; index != them; index++) { asm volatile ("dc ivac, %0\ndsb sy" :: "r"(&buffers[index%queue_size]) : "memory"); struct virtio_input_event evt = buffers[index%queue_size]; while (evt.type == 0xFF) { evt = buffers[index%queue_size]; dprintf("virtio-tablet: bad packet %d (them=%d)\n", index, them); } buffers[index%queue_size].type = 0xFF; asm volatile ("isb\ndsb sy" :: "r"(buffers) : "memory"); if (evt.type == 1) { /* need to back-convert which is a pain in the ass */ if (evt.code < 0x49) { uint8_t scancode = evt.code; if (evt.value == 0) { scancode |= 0x80; } uint8_t bitbucket; while (pipe_size(keyboard_pipe) > (int)(DISCARD_POINT)) { read_fs(keyboard_pipe, 0, 1, (uint8_t *)&bitbucket); } write_fs(keyboard_pipe, 0, 1, (uint8_t *)&scancode); } else if (ext_key_map[evt.code]) { uint8_t data[] = {0xE0, 0}; data[1] = ext_key_map[evt.code] | ((evt.value == 0) ? 0x80 : 0); uint8_t bitbucket; while (pipe_size(keyboard_pipe) > (int)(DISCARD_POINT)) { read_fs(keyboard_pipe, 0, 1, (uint8_t *)&bitbucket); } write_fs(keyboard_pipe, 0, 2, (uint8_t *)data); } else { dprintf("virtio: unmapped keycode %d\n", evt.code); } } asm volatile ("isb" ::: "memory"); queue->available.index++; } } } static void virtio_input_maybe(uint32_t device, uint16_t v, uint16_t d, void * extra) { if (v == 0x1af4 && d == 0x1052) { if (pci_find_type(device) == 0x0900) { spawn_worker_thread(virtio_keyboard_thread, "[virtio-keyboard]", (void*)(uintptr_t)device); } else if (pci_find_type(device) == 0x0980) { spawn_worker_thread(virtio_tablet_thread, "[virtio-tablet]", (void*)(uintptr_t)device); } } } void null_input(void) { mouse_pipe = make_pipe(128); mouse_pipe->flags = FS_CHARDEVICE; vfs_mount("/dev/mouse", mouse_pipe, "virtio-mouse", ""); vmmouse_pipe = make_pipe(4096); vmmouse_pipe->flags = FS_CHARDEVICE; vfs_mount("/dev/vmmouse", vmmouse_pipe, "virtio-tablet", ""); keyboard_pipe = make_pipe(128); keyboard_pipe->flags = FS_CHARDEVICE; vfs_mount("/dev/kbd", keyboard_pipe, "virtio-kbd", ""); } void virtio_input(void) { null_input(); /* setup pipes */ pci_scan(virtio_input_maybe, -1, NULL); } ================================================ FILE: kernel/arch/x86_64/bootstrap.S ================================================ /** * @file kernel/arch/x86_64/bootstrap.c * @brief x86-64 entrypoint from bootloader * * This is primarily adapted from Toaru's 32-bit mulitboot bootstrap. * Instead of jumping straight to our C entry point, however, we need * to (obviously) get ourselves set up for long mode first by setting * up initial page tables, etc. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ .section .multiboot .code32 .extern bss_start .extern end .extern phys /* Multiboot 1 header */ .set MB_MAGIC, 0x1BADB002 .set MB_FLAG_PAGE_ALIGN, 1 << 0 .set MB_FLAG_MEMORY_INFO, 1 << 1 .set MB_FLAG_GRAPHICS, 1 << 2 .set MB_FLAG_AOUT, 1 << 16 .set MB_FLAGS, MB_FLAG_PAGE_ALIGN | MB_FLAG_MEMORY_INFO | MB_FLAG_GRAPHICS | MB_FLAG_AOUT .set MB_CHECKSUM, -(MB_MAGIC + MB_FLAGS) /* Multiboot section */ .align 4 multiboot_header: .long MB_MAGIC .long MB_FLAGS .long MB_CHECKSUM .long multiboot_header /* header_addr */ .long phys /* load_addr */ .long bss_start /* load_end_addr */ .long end /* bss_end_addr */ .long start /* entry_addr */ /* Request linear graphics mode */ .long 0x00000000 .long 1024 .long 768 .long 32 /* Multiboot 2 header */ .set MB2_MAGIC, 0xe85250d6 .set MB2_ARCH, 0 .set MB2_LENGTH, (multiboot2_header_end - multiboot2_header) .set MB2_CHECKSUM, -(MB2_MAGIC + MB2_ARCH + MB2_LENGTH) .align 8 multiboot2_header: .long MB2_MAGIC .long MB2_ARCH .long MB2_LENGTH .long MB2_CHECKSUM /* Address tag */ .align 8 mb2_tag_addr: .word 2 .word 0 .long 24 .long multiboot2_header .long phys .long bss_start .long end /* Entry tag */ .align 8 mb2_tag_entry: .word 3 .word 0 .long 12 .long start_mbi2 /* Framebuffer tag */ .align 8 mb2_tag_fb: .word 5 .word 0 .long 20 .long 1024 .long 768 .long 32 .align 8 .word 4 .word 1 .long 12 .long 2 /* We support EGA text, but don't require it */ /* Modules must be aligned */ .align 8 .word 6 .word 0 .long 8 /* Relocatable tag */ .align 8 .word 10 .word 0 .long 24 .long 0x100000 /* Start */ .long 0x1000000 /* Maximum load address */ .long 4096 /* Request page alignment */ .long 1 /* Load at lowest available */ /* End tag */ .align 8 .word 0 .word 0 .long 8 multiboot2_header_end: /* .stack resides in .bss */ .section .stack, "aw", @nobits stack_bottom: .skip 16384 /* 16KiB */ .global stack_top stack_top: .section .bootstrap .code32 .align 4 .extern jmp_to_long .type jmp_to_long, @function .extern kmain .type kmain, @function .global start_mbi2 .type start_mbi2, @function start_mbi2: /* Use reserved 0 space of boot information struct to thunk eip */ movl %ebx, %ecx addl $8, %ecx jmp good_to_go .global start .type start, @function start: /* Use boot_drive as a temporary place to thunk eip */ movl %ebx, %ecx addl $16, %ecx good_to_go: mov %ecx, %esp call _forward _forward: popl %ecx subl $_forward, %ecx /* Setup our stack */ mov $stack_top, %esp addl %ecx, %esp /* Make sure our stack is 16-byte aligned */ and $-16, %esp pushl $0 pushl %esp pushl $0 pushl %eax /* Multiboot header magic */ pushl $0 pushl %ebx /* Multiboot header pointer */ jmp jmp_to_long .align 4 jmp_to_long: .extern init_page_region /* Set up initial page region, which was zero'd for us by the loader */ mov $init_page_region, %edi addl %ecx, %edi pushl %ecx /* PML4[0] = &PDP[0] | (PRESENT, WRITABLE, USER) */ mov $0x1007, %eax add %edi, %eax mov %eax, (%edi) /* PDP[0] = &PD[0] | (PRESENT, WRITABLE, USER) */ add $0x1000, %edi mov $0x1003, %eax add %edi, %eax mov %eax, (%edi) /* Set 32 2MiB pages to map 64MiB of low memory temporarily, which should be enough to get us through our C MMU initialization where we then use 2MiB pages to map all of the 4GiB standard memory space and map a much more restricted subset of the kernel in the lower address space. */ add $0x1000, %edi mov $0x87, %ebx mov $32, %ecx .set_entry: mov %ebx, (%edi) add $0x200000, %ebx add $8, %edi loop .set_entry mov $init_page_region, %edi popl %ecx addl %ecx, %edi pushl %ecx mov %edi, %cr3 /* Enable PAE */ mov %cr4, %eax or $32, %eax mov %eax, %cr4 /* EFER */ mov $0xC0000080, %ecx rdmsr or $256, %eax wrmsr /* Check PG */ mov %cr0, %eax /* If paging was enabled, assume we were already in long mode (eg. booted by EFI) */ test $0x80000000, %eax jnz .continue /* Otherwise enable paging */ or $0x80000000, %eax mov %eax, %cr0 super_duper_bullshit: popl %ecx lea (_lgdt_instr+3)(%ecx), %eax movl (%eax), %ebx addl %ecx, %ebx movl %ebx, (%eax) lea (gdtr+2)(%ecx), %eax movl (%eax), %ebx addl %ecx, %ebx movl %ebx, (%eax) lea (_ljmp_instr+1)(%ecx), %eax movl (%eax), %ebx addl %ecx, %ebx movl %ebx, (%eax) pushl $0 pushl %ecx _lgdt_instr: lgdt gdtr _ljmp_instr: ljmp $0x08,$realm64 .align 8 gdtr: .word gdt_end-gdt_base .quad gdt_base gdt_base: /* Null */ .quad 0 /* Code */ .word 0 .word 0 .byte 0 .byte 0x9a .byte 0x20 .byte 0 /* Data */ .word 0xffff .word 0 .byte 0 .byte 0x92 .byte 0 .byte 0 gdt_end: .code64 .align 8 .section .bootstrap realm64: cli mov $0x10, %ax mov %ax, %ds mov %ax, %es mov %ax, %fs mov %ax, %gs mov %ax, %ss .continue: cli pop %rcx pop %rdi pop %rsi pop %rdx callq kmain halt: cli hlt jmp halt ================================================ FILE: kernel/arch/x86_64/cmos.c ================================================ /** * @file kernel/arch/x86_64/cmos.c * @author K. Lange * @brief Real-time clock. * * Provides access to the CMOS RTC for initial boot time and * calibrates the TSC to use as a general timing source. IRQ 0 * handler is also in here because it updates the wall clock time * and triggers timeout-based wakeups. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include uint64_t arch_boot_time = 0; /**< Time (in seconds) according to the CMOS right before we examine the TSC */ uint64_t tsc_basis_time = 0; /**< Accumulated time (in microseconds) on the TSC, when we timed it; eg. how long did boot take */ uint64_t tsc_mhz = 3500; /**< MHz rating we determined for the TSC. Usually also the core speed? */ /* Crusty old CMOS code follows. */ #define from_bcd(val) ((val / 16) * 10 + (val & 0xf)) #define CMOS_ADDRESS 0x70 #define CMOS_DATA 0x71 enum { CMOS_SECOND = 0, CMOS_MINUTE = 2, CMOS_HOUR = 4, CMOS_DAY = 7, CMOS_MONTH = 8, CMOS_YEAR = 9 }; /** * @brief Read the contents of the RTC CMOS * * @param values (out) Where to stick the values read. */ static void cmos_dump(uint16_t * values) { for (uint16_t index = 0; index < 128; ++index) { outportb(CMOS_ADDRESS, index); values[index] = inportb(CMOS_DATA); } } /** * @brief Check if the CMOS is currently being updated. */ static int is_update_in_progress(void) { outportb(CMOS_ADDRESS, 0x0a); return inportb(CMOS_DATA) & 0x80; } /** * @brief Poorly convert years to Unix timestamps. * * @param years Years since 2000 * @returns Seconds since the Unix epoch, maybe... */ static uint64_t secs_of_years(int years) { uint64_t days = 0; years += 2000; while (years > 1969) { days += 365; if (years % 4 == 0) { if (years % 100 == 0) { if (years % 400 == 0) { days++; } } else { days++; } } years--; } return days * 86400; } /** * @brief How long was a month in a given year? * * Tries to do leap year stuff for February. * * @param months 1~12 calendar month * @param year Years since 2000 * @return Number of seconds in that month. */ static uint64_t secs_of_month(int months, int year) { year += 2000; uint64_t days = 0; switch(months) { case 11: days += 30; /* fallthrough */ case 10: days += 31; /* fallthrough */ case 9: days += 30; /* fallthrough */ case 8: days += 31; /* fallthrough */ case 7: days += 31; /* fallthrough */ case 6: days += 30; /* fallthrough */ case 5: days += 31; /* fallthrough */ case 4: days += 30; /* fallthrough */ case 3: days += 31; /* fallthrough */ case 2: days += 28; if ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))) { days++; } /* fallthrough */ case 1: days += 31; /* fallthrough */ default: break; } return days * 86400; } /** * @brief Convert the CMOS time to a Unix timestamp. * * Reads BCD data from the RTC CMOS and does some dumb * math to convert the display time to a Unix timestamp. * * @return Current Unix time */ uint64_t read_cmos(void) { uint16_t values[128]; uint16_t old_values[128]; while (is_update_in_progress()); cmos_dump(values); do { memcpy(old_values, values, 128); while (is_update_in_progress()); cmos_dump(values); } while ((old_values[CMOS_SECOND] != values[CMOS_SECOND]) || (old_values[CMOS_MINUTE] != values[CMOS_MINUTE]) || (old_values[CMOS_HOUR] != values[CMOS_HOUR]) || (old_values[CMOS_DAY] != values[CMOS_DAY]) || (old_values[CMOS_MONTH] != values[CMOS_MONTH]) || (old_values[CMOS_YEAR] != values[CMOS_YEAR])); /* Math Time */ uint64_t time = secs_of_years(from_bcd(values[CMOS_YEAR]) - 1) + secs_of_month(from_bcd(values[CMOS_MONTH]) - 1, from_bcd(values[CMOS_YEAR])) + (from_bcd(values[CMOS_DAY]) - 1) * 86400 + (from_bcd(values[CMOS_HOUR])) * 3600 + (from_bcd(values[CMOS_MINUTE])) * 60 + from_bcd(values[CMOS_SECOND]) + 0; return time; } /** * @brief Helper to read timestamp counter */ static inline uint64_t read_tsc(void) { uint32_t lo, hi; asm volatile ( "rdtsc" : "=a"(lo), "=d"(hi) ); return ((uint64_t)hi << 32UL) | (uint64_t)lo; } /** * @brief Exported interface to read timestamp counter. * * Used by various things in the kernel to get a quick performance * timer value. This is always scaled by @c arch_cpu_mhz when it * needs to be converted to something user-friendly. */ uint64_t arch_perf_timer(void) { return read_tsc(); } /** * @brief What to scale performance counter times by. * * I've called this "arch_cpu_mhz" but I don't know if that's * always going to be true, so this may need to be renamed at * some point... */ size_t arch_cpu_mhz(void) { return tsc_mhz; } /** * @brief Initializes boot time, system time, TSC rate, etc. * * We determine TSC rate with a one-shot PIT, which seems * to work fine... the PIT is the only thing with both reasonable * precision and actual known wall-clock configuration. * * In Bochs, this has a tendency to be 1) completely wrong (usually * about half the time that actual execution will run at, in my * experiences) and 2) loud, as despite the attempt to turn off * the speaker it still seems to beep it (the second channel of the * PIT controls the beeper). * * In QEMU, VirtualBox, VMware, and on all real hardware I've tested, * including everything from a ThinkPad T410 to a Surface Pro 6, this * has been surprisingly accurate and good enough to use the TSC as * our only wall clock source. */ void arch_clock_initialize(void) { dprintf("tsc: Calibrating system timestamp counter.\n"); arch_boot_time = read_cmos(); uintptr_t end_lo, end_hi; uint32_t start_lo, start_hi; asm volatile ( /* Disables and sets gating for channel 2 */ "inb $0x61, %%al\n" "andb $0xDD, %%al\n" "orb $0x01, %%al\n" "outb %%al, $0x61\n" /* Configure channel 2 to one-shot, next two bytes are low/high */ "movb $0xB2, %%al\n" /* 0b10110010 */ "outb %%al, $0x43\n" /* 0x__9b */ "movb $0x9B, %%al\n" "outb %%al, $0x42\n" "inb $0x60, %%al\n" /* 0x2e__ */ "movb $0x2E, %%al\n" "outb %%al, $0x42\n" /* Re-enable */ "inb $0x61, %%al\n" "andb $0xDE, %%al\n" "outb %%al, $0x61\n" /* Pulse high */ "orb $0x01, %%al\n" "outb %%al, $0x61\n" /* Read TSC and store in vars */ "rdtsc\n" "movl %%eax, %2\n" "movl %%edx, %3\n" /* In QEMU and VirtualBox, this seems to flip low. * On real hardware and VMware it flips high. */ "inb $0x61, %%al\n" "andb $0x20, %%al\n" "jz 2f\n" /* Loop until output goes low? */ "1:\n" "inb $0x61, %%al\n" "andb $0x20, %%al\n" "jnz 1b\n" "rdtsc\n" "jmp 3f\n" /* Loop until output goes high */ "2:\n" "inb $0x61, %%al\n" "andb $0x20, %%al\n" "jz 2b\n" "rdtsc\n" "3:\n" : "=a"(end_lo), "=d"(end_hi), "=r"(start_lo), "=r"(start_hi) ); uintptr_t end = ((end_hi & 0xFFFFffff) << 32) | (end_lo & 0xFFFFffff); uintptr_t start = ((uintptr_t)(start_hi & 0xFFFFffff) << 32) | (start_lo & 0xFFFFffff); tsc_mhz = (end - start) / 10000; if (tsc_mhz == 0) tsc_mhz = 2000; /* uh oh */ tsc_basis_time = start / tsc_mhz; dprintf("tsc: TSC timed at %lu MHz..\n", tsc_mhz); dprintf("tsc: Boot time is %lus.\n", arch_boot_time); dprintf("tsc: Initial TSC timestamp was %luus.\n", tsc_basis_time); } #define SUBSECONDS_PER_SECOND 1000000 /** * @brief Subdivide ticks into seconds in subticks. */ static void update_ticks(uint64_t ticks, uint64_t *timer_ticks, uint64_t *timer_subticks) { *timer_subticks = ticks - tsc_basis_time; *timer_ticks = *timer_subticks / SUBSECONDS_PER_SECOND; *timer_subticks = *timer_subticks % SUBSECONDS_PER_SECOND; } /** * @brief Exposed interface for wall clock time. * * Note that while the kernel version of this takes a *z option that is * supposed to have timezone information, we don't actually use it, * and I'm pretty sure it's NULL everywhere? * * We calculate wall time using the TSC, the calculate TSC tick rate, * and the boot time retrieved from the CMOS, subdivide the result * into seconds and "subseconds" (microseconds), and store that. */ int gettimeofday(struct timeval * t, void *z) { uint64_t tsc = read_tsc(); uint64_t timer_ticks, timer_subticks; update_ticks(tsc / tsc_mhz, &timer_ticks, &timer_subticks); t->tv_sec = arch_boot_time + timer_ticks; t->tv_usec = timer_subticks; return 0; } /** * @brief Dumb convenience function for things that just want a Unix timestamp. * * @return Wall clock time as a Unix timestamp (seconds since the epoch). */ uint64_t now(void) { struct timeval t; gettimeofday(&t, NULL); return t.tv_sec; } static spin_lock_t _time_set_lock; /** * Set the system clock time * * TODO: A lot of this time stuff needs to be made more generic, * it's shared pretty directly with aarch64... */ int settimeofday(struct timeval * t, void *z) { if (!t) return -EINVAL; if (t->tv_sec < 0 || t->tv_usec < 0 || t->tv_usec > 1000000) return -EINVAL; spin_lock(_time_set_lock); uint64_t clock_time = now(); arch_boot_time += t->tv_sec - clock_time; spin_unlock(_time_set_lock); return 0; } /** * @brief Calculate a time in the future. * * Takes the current raw TSC time and adds @p seconds seconds and @p subseconds microseconds to it. * Stores the result seconds and microseconds in @p out_seconds and @p out_subseconds. If * @p seconds and @p subseconds are both zero, this is effectively equivalent to @c update_ticks. * * This is an exposed interface used throughout the kernel, usually to calculate * timeouts and yielding delays. * * This uses raw TSC time, which is not adjusted for either boot time or wall clock time. */ void relative_time(unsigned long seconds, unsigned long subseconds, unsigned long * out_seconds, unsigned long * out_subseconds) { if (!arch_boot_time) { *out_seconds = 0; *out_subseconds = 0; return; } uint64_t tsc = read_tsc(); uint64_t timer_ticks, timer_subticks; update_ticks(tsc / tsc_mhz, &timer_ticks, &timer_subticks); if (subseconds + timer_subticks >= SUBSECONDS_PER_SECOND) { *out_seconds = timer_ticks + seconds + (subseconds + timer_subticks) / SUBSECONDS_PER_SECOND; *out_subseconds = (subseconds + timer_subticks) % SUBSECONDS_PER_SECOND; } else { *out_seconds = timer_ticks + seconds; *out_subseconds = timer_subticks + subseconds; } } static uint64_t time_slice_basis = 0; /**< When the last clock update happened */ static spin_lock_t clock_lock = { 0 }; /**< Allow only one core to do this */ /** * @brief Update the global timer tick values. * * Updates process CPU usage times and wakes up any timed sleepers * based on the current TSC time. */ void arch_update_clock(void) { spin_lock(clock_lock); /* Obtain TSC timestamp, in microseconds */ uint64_t clock_ticks = read_tsc() / tsc_mhz; /* Convert it to seconds and subseconds */ uint64_t timer_ticks, timer_subticks; update_ticks(clock_ticks, &timer_ticks, &timer_subticks); /** * Update per-process quarter-second usage statistics * * XXX I think this was a bad idea and it should be removed. * We store four quarter-second usage values in a sliding * array and update them for every process, so that we can * query CPU% without having to sample, but that's a lot * more work in the kernel than we need... */ if (time_slice_basis + SUBSECONDS_PER_SECOND/4 <= clock_ticks) { update_process_usage(clock_ticks - time_slice_basis, tsc_mhz); time_slice_basis = clock_ticks; } spin_unlock(clock_lock); /* Wake up any processes that have expired timeouts */ wakeup_sleepers(timer_ticks, timer_subticks); } ================================================ FILE: kernel/arch/x86_64/gdt.c ================================================ /** * @file kernel/arch/x86_64/gdt.c * @brief x86-64 GDT * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include /** * @brief 64-bit TSS */ typedef struct tss_entry { uint32_t reserved_0; uint64_t rsp[3]; uint64_t reserved_1; uint64_t ist[7]; uint64_t reserved_2; uint16_t reserved_3; uint16_t iomap_base; } __attribute__ ((packed)) tss_entry_t; typedef struct { uint16_t limit_low; uint16_t base_low; uint8_t base_middle; uint8_t access; uint8_t granularity; uint8_t base_high; } __attribute__((packed)) gdt_entry_t; typedef struct { uint32_t base_highest; uint32_t reserved0; } __attribute__((packed)) gdt_entry_high_t; typedef struct { uint16_t limit; uintptr_t base; } __attribute__((packed)) gdt_pointer_t; typedef struct { gdt_entry_t entries[7]; gdt_entry_high_t tss_extra; gdt_pointer_t pointer; tss_entry_t tss; } __attribute__((packed)) __attribute__((aligned(0x10))) FullGDT; FullGDT gdt[32] __attribute__((used)) = {{ { {0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00}, {0xFFFF, 0x0000, 0x00, 0x9A, (1 << 5) | (1 << 7) | 0x0F, 0x00}, {0xFFFF, 0x0000, 0x00, 0x92, (1 << 5) | (1 << 7) | 0x0F, 0x00}, {0xFFFF, 0x0000, 0x00, 0xFA, (1 << 5) | (1 << 7) | 0x0F, 0x00}, {0xFFFF, 0x0000, 0x00, 0xF2, (1 << 5) | (1 << 7) | 0x0F, 0x00}, {0xFFFF, 0x0000, 0x00, 0xFA, (1 << 5) | (1 << 7) | 0x0F, 0x00}, {0x0067, 0x0000, 0x00, 0xE9, 0x00, 0x00}, }, {0x00000000, 0x00000000}, {0x0000, 0x0000000000000000}, {0,{0,0,0},0,{0,0,0,0,0,0,0},0,0,0}, }}; void gdt_install(void) { for (int i = 1; i < 32; ++i) { memcpy(&gdt[i], &gdt[0], sizeof(*gdt)); } for (int i = 0; i < 32; ++i) { gdt[i].pointer.limit = sizeof(gdt[i].entries)+sizeof(gdt[i].tss_extra)-1; gdt[i].pointer.base = (uintptr_t)&gdt[i].entries; uintptr_t addr = (uintptr_t)&gdt[i].tss; gdt[i].entries[6].limit_low = sizeof(gdt[i].tss); gdt[i].entries[6].base_low = (addr & 0xFFFF); gdt[i].entries[6].base_middle = (addr >> 16) & 0xFF; gdt[i].entries[6].base_high = (addr >> 24) & 0xFF; gdt[i].tss_extra.base_highest = (addr >> 32) & 0xFFFFFFFF; } extern void * stack_top; gdt[0].tss.rsp[0] = (uintptr_t)&stack_top; asm volatile ( "lgdt %0\n" "mov $0x10, %%ax\n" "mov %%ax, %%ds\n" "mov %%ax, %%es\n" "mov %%ax, %%ss\n" "mov $0x33, %%ax\n" /* TSS offset */ "ltr %%ax\n" : : "m"(gdt[0].pointer) : "rax", "memory" ); } void gdt_copy_to_trampoline(int ap, char * trampoline) { memcpy(trampoline, &gdt[ap].pointer, sizeof(gdt[ap].pointer)); } void arch_set_kernel_stack(uintptr_t stack) { gdt[this_core->cpu_id].tss.rsp[0] = stack; this_core->syscall_stack = stack; } void arch_set_tls_base(uintptr_t tlsbase) { asm volatile ("wrmsr" : : "c"(0xc0000100), "d"((uint32_t)(tlsbase >> 32)), "a"((uint32_t)(tlsbase & 0xFFFFFFFF))); } ================================================ FILE: kernel/arch/x86_64/idt.c ================================================ /** * @file kernel/arch/x86_64/idt.c * @brief x86-64 Interrupt Descriptor Table management * * This is the C side of all interrupt handling. See * also @ref irq.S which has the assembly entrypoints. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static struct idt_pointer idtp; static idt_entry_t idt[256]; /** * @brief Initialize a gate, since there's some address swizzling involved... */ void idt_set_gate(uint8_t num, interrupt_handler_t handler, uint16_t selector, uint8_t flags, int userspace) { uintptr_t base = (uintptr_t)handler; idt[num].base_low = (base & 0xFFFF); idt[num].base_mid = (base >> 16) & 0xFFFF; idt[num].base_high = (base >> 32) & 0xFFFFFFFF; idt[num].selector = selector; idt[num].zero = 0; idt[num].pad = 0; idt[num].flags = flags | (userspace ? 0x60 : 0); } /** * @brief Initializes the IDT and sets up gates for all interrupts. */ void idt_install(void) { idtp.limit = sizeof(idt); idtp.base = (uintptr_t)&idt; /** ISRs */ idt_set_gate(0, _isr0, 0x08, 0x8E, 0); idt_set_gate(1, _isr1, 0x08, 0x8E, 0); idt_set_gate(2, _isr2, 0x08, 0x8E, 0); idt_set_gate(3, _isr3, 0x08, 0x8E, 0); idt_set_gate(4, _isr4, 0x08, 0x8E, 0); idt_set_gate(5, _isr5, 0x08, 0x8E, 0); idt_set_gate(6, _isr6, 0x08, 0x8E, 0); idt_set_gate(7, _isr7, 0x08, 0x8E, 0); idt_set_gate(8, _isr8, 0x08, 0x8E, 0); idt_set_gate(9, _isr9, 0x08, 0x8E, 0); idt_set_gate(10, _isr10, 0x08, 0x8E, 0); idt_set_gate(11, _isr11, 0x08, 0x8E, 0); idt_set_gate(12, _isr12, 0x08, 0x8E, 0); idt_set_gate(13, _isr13, 0x08, 0x8E, 0); idt_set_gate(14, _isr14, 0x08, 0x8E, 0); idt_set_gate(15, _isr15, 0x08, 0x8E, 0); idt_set_gate(16, _isr16, 0x08, 0x8E, 0); idt_set_gate(17, _isr17, 0x08, 0x8E, 0); idt_set_gate(18, _isr18, 0x08, 0x8E, 0); idt_set_gate(19, _isr19, 0x08, 0x8E, 0); idt_set_gate(20, _isr20, 0x08, 0x8E, 0); idt_set_gate(21, _isr21, 0x08, 0x8E, 0); idt_set_gate(22, _isr22, 0x08, 0x8E, 0); idt_set_gate(23, _isr23, 0x08, 0x8E, 0); idt_set_gate(24, _isr24, 0x08, 0x8E, 0); idt_set_gate(25, _isr25, 0x08, 0x8E, 0); idt_set_gate(26, _isr26, 0x08, 0x8E, 0); idt_set_gate(27, _isr27, 0x08, 0x8E, 0); idt_set_gate(28, _isr28, 0x08, 0x8E, 0); idt_set_gate(29, _isr29, 0x08, 0x8E, 0); idt_set_gate(30, _isr30, 0x08, 0x8E, 0); idt_set_gate(31, _isr31, 0x08, 0x8E, 0); idt_set_gate(32, _irq0, 0x08, 0x8E, 0); idt_set_gate(33, _irq1, 0x08, 0x8E, 0); idt_set_gate(34, _irq2, 0x08, 0x8E, 0); idt_set_gate(35, _irq3, 0x08, 0x8E, 0); idt_set_gate(36, _irq4, 0x08, 0x8E, 0); idt_set_gate(37, _irq5, 0x08, 0x8E, 0); idt_set_gate(38, _irq6, 0x08, 0x8E, 0); idt_set_gate(39, _irq7, 0x08, 0x8E, 0); idt_set_gate(40, _irq8, 0x08, 0x8E, 0); idt_set_gate(41, _irq9, 0x08, 0x8E, 0); idt_set_gate(42, _irq10, 0x08, 0x8E, 0); idt_set_gate(43, _irq11, 0x08, 0x8E, 0); idt_set_gate(44, _irq12, 0x08, 0x8E, 0); idt_set_gate(45, _irq13, 0x08, 0x8E, 0); idt_set_gate(46, _irq14, 0x08, 0x8E, 0); idt_set_gate(47, _irq15, 0x08, 0x8E, 0); idt_set_gate(123, _isr123, 0x08, 0x8E, 0); /* Clock interrupt for other processors */ idt_set_gate(124, _isr124, 0x08, 0x8E, 0); /* Bad TLB shootdown. */ idt_set_gate(125, _isr125, 0x08, 0x8E, 0); /* Halts everyone. */ idt_set_gate(126, _isr126, 0x08, 0x8E, 0); /* Does nothing, used to exit wait-for-interrupt sleep. */ idt_set_gate(127, _isr127, 0x08, 0x8E, 1); /* Legacy system call entry point, called by userspace. */ asm volatile ( "lidt %0" : : "m"(idtp) ); } /** * @brief Quicker call to lidt for APs, when the IDT is already set up. * * We use the same idt in all cores, so there's not much to do here. */ void idt_ap_install(void) { idtp.limit = sizeof(idt); idtp.base = (uintptr_t)&idt; asm volatile ( "lidt %0" : : "m"(idtp) ); } /** External IRQ management */ #define IRQ_CHAIN_SIZE 16 #define IRQ_CHAIN_DEPTH 4 static irq_handler_chain_t irq_routines[IRQ_CHAIN_SIZE * IRQ_CHAIN_DEPTH] = { NULL }; static const char * _irq_handler_descriptions[IRQ_CHAIN_SIZE * IRQ_CHAIN_DEPTH] = { NULL }; /** * @brief Examine the IRQ handler chain to see what handles an IRQ. * * This is a debug function used by the procfs /proc/irq callback. * Can be called with different @p chain values to get all of the * handlers when there is more than one. * * @param irq The interrupt number (0~15) * @param chain Handler chain depth (0~4) * @return The name of the handler. */ const char * get_irq_handler(int irq, int chain) { if (irq >= IRQ_CHAIN_SIZE) return NULL; if (chain >= IRQ_CHAIN_DEPTH) return NULL; return _irq_handler_descriptions[IRQ_CHAIN_SIZE * chain + irq]; } /** * @brief Install an IRQ handler. * * TODO Shouldn't this return a status code? What if we have too many * IRQs installed? What if @p irq is invalid (>16)? * * TODO Should we provide callers with a unique reference to their IRQ vector * so it can be removed later? * * @param irq The IRQ number to handle (0~15) * @param handler Function to install as a callback for this IRQ * @param desc Textual description for debugging. */ void irq_install_handler(size_t irq, irq_handler_chain_t handler, const char * desc) { for (size_t i = 0; i < IRQ_CHAIN_DEPTH; i++) { if (irq_routines[i * IRQ_CHAIN_SIZE + irq]) continue; irq_routines[i * IRQ_CHAIN_SIZE + irq] = handler; _irq_handler_descriptions[i * IRQ_CHAIN_SIZE + irq ] = desc; break; } } /* We used to have a function here that incorrectly uninstalled IRQ handlers... */ /** * @brief Examine the module table to find which module owns an address. * * Looks through the loaded module list to find what module * owns @p addr, setting @p name and returning the corresponding module * entry. Since we know how big modules are in memory, we can also know * if an address doesn't belong to any module, in which case we return NULL. * * @param addr Address to look for * @param name (out) Name of the matching module * @return Pointer to LoadedModule for the matched module, or NULL. */ static struct LoadedModule * find_module(uintptr_t addr, char ** name) { hashmap_t * modules = modules_get_list(); for (size_t i = 0; i < modules->size; ++i) { hashmap_entry_t * x = modules->entries[i]; while (x) { struct LoadedModule * info = x->value; if (info->baseAddress <= addr && addr <= info->baseAddress + info->loadedSize) { *name = (char*)x->key; return info; } x = x->next; } } return NULL; } /** * @brief Use brute force to determine if an address is mapped. * * Examines the current page table to see if @p base and up to @p size * is a valid region of memory. Useful for determining if a stack entry * is a valid base pointer to a calling frame. * * @param base Address to validate * @param size How many bytes after @p base are going to be examined. * @return 1 if the range is mapped, 0 otherwise. */ static int validate_pointer(uintptr_t base, size_t size) { uintptr_t end = size ? (base + (size - 1)) : base; uintptr_t page_base = base >> 12; uintptr_t page_end = end >> 12; for (uintptr_t page = page_base; page <= page_end; ++page) { if ((page & 0xffff800000000) != 0 && (page & 0xffff800000000) != 0xffff800000000) return 0; union PML * page_entry = mmu_get_page_other(this_core->current_process->thread.page_directory->directory, page << 12); if (!page_entry) return 0; if (!page_entry->bits.present) return 0; } return 1; } extern char end[]; /** * @brief Find the closest preceding symbol to an address. * * Scans the kernel symbol table to find the closest preceding * symbol to the address @p ip and stores its name in @p name, * returning the actual address of the symbol. * * As this uses the kernel symbol linkage table, it is only aware * of exported functions and objects, and can not provide any * information on static functions. * * @param ip Address to scan for * @param name (out) Name of matching symbol * @return Address of matching symbol */ static uintptr_t matching_symbol(uintptr_t ip, char ** name) { hashmap_t * symbols = ksym_get_map(); uintptr_t best_match = 0; for (size_t i = 0; i < symbols->size; ++i) { hashmap_entry_t * x = symbols->entries[i]; while (x) { void* sym_addr = x->value; char* sym_name = x->key; if ((uintptr_t)sym_addr < ip && (uintptr_t)sym_addr > best_match) { best_match = (uintptr_t)sym_addr; *name = sym_name; } x = x->next; } } return best_match; } /** * @brief Display a traceback from the given ip and stack base. * * Walks the stack referenced by @p bp and attempts to find * kernel symbol names or module names. Stops when it reaches * a return address that looks invalid. * * You probably want to @see arch_fatal_prepare before calling * this to make sure you get a readable output. * * Note that symbol names are the closest symbol before the * given address, and will only ever be exported symbols, * so static functions will give the wrong name. * * We don't track symbols from modules at all at the moment, * so for addresses in module space the best we can do is * provide the name of the model and the offset into the loaded * file, so that's what we do. * * @param ip IP address to assume is the top of the backtrace. * @param bp Stack frame pointer. */ static void dump_traceback(uintptr_t ip, uintptr_t bp) { int depth = 0; int max_depth = 20; while (bp && ip && depth < max_depth) { dprintf(" 0x%016zx ", ip); if (ip >= 0xffffffff80000000UL) { char * name = NULL; struct LoadedModule * mod = find_module(ip, &name); if (mod) { dprintf("\a in module '%s', base address %#zx (offset %#zx)\n", name, mod->baseAddress, ip - mod->baseAddress); } else { dprintf("\a (unknown)\n"); } } else if (ip >= (uintptr_t)&end && ip <= 0x800000000000) { dprintf("\a in userspace\n"); } else if (ip <= (uintptr_t)&end) { /* Find symbol match */ char * name; uintptr_t addr = matching_symbol(ip, &name); if (!addr) { dprintf("\a (no match)\n"); } else { dprintf("\a %s+0x%zx\n", name, ip-addr); } } else { dprintf("\a (unknown)\n"); } if (!validate_pointer(bp, sizeof(uintptr_t)) || !validate_pointer(bp + sizeof(uintptr_t), sizeof(uintptr_t))) { break; } ip = *(uintptr_t*)(bp + sizeof(uintptr_t)); bp = *(uintptr_t*)(bp); depth++; } } /** * @brief Display a traceback from the rip and rbp of a register state. * * Primarily used to dump tracebacks that led to unexpected interrupts. * * @param r Interrupt register context */ static void safe_dump_traceback(struct regs * r) { dump_traceback(r->rip, r->rbp); } /** * @brief Display a traceback from the current call context. */ void arch_dump_traceback(void) { dump_traceback((uintptr_t)arch_dump_traceback+1, (uintptr_t)__builtin_frame_address(0)); } /** * @brief Map in more pages for a userspace stack. * * Allows for soft expansion of the stack downards on a page fault. * * @param fromAddr The low address to map, should be page aligned. */ static int map_more_stack(uintptr_t fromAddr) { volatile process_t * volatile proc = this_core->current_process; /* Is this thread the process leader? */ if (proc->group != 0) { proc = process_from_pid(proc->group); } if (!proc) return 0; /* Make sure nothing else is going to mess with this process's page tables */ spin_lock(proc->image.lock); /* Map more stack! */ for (uintptr_t i = fromAddr; i < proc->image.userstack; i += 0x1000) { union PML * page = mmu_get_page(i, MMU_GET_MAKE); mmu_frame_allocate(page, MMU_FLAG_WRITABLE); } /* Update the saved stack address */ proc->image.userstack = fromAddr; spin_unlock(proc->image.lock); return 1; } /** * @brief Handle fatal exceptions. * * Prepares for a fatal event, prints information on the running * process and the cause of the panic, dumps the register state, * prints a backtrace, and then hard loops. * * @param desc Textual description of the panic cause. * @param r Interrupt register context * @param faulting_address When available, the address that was accessed leading to this fault. */ static void panic(const char * desc, struct regs * r, uintptr_t faulting_address) { /* Stop all other cores */ arch_fatal_prepare(); /* Print the description, current process, cause */ dprintf("\033[31mPanic!\033[0m %s pid=%d (%s) at %#zx\n", desc, this_core->current_process ? (int)this_core->current_process->id : 0, this_core->current_process ? this_core->current_process->name : "kernel", faulting_address ); /* Dump register state */ dprintf( "Registers at interrupt:\n" " $rip=0x%016lx\n" " $rsi=0x%016lx,$rdi=0x%016lx,$rbp=0x%016lx,$rsp=0x%016lx\n" " $rax=0x%016lx,$rbx=0x%016lx,$rcx=0x%016lx,$rdx=0x%016lx\n" " $r8= 0x%016lx,$r9= 0x%016lx,$r10=0x%016lx,$r11=0x%016lx\n" " $r12=0x%016lx,$r13=0x%016lx,$r14=0x%016lx,$r15=0x%016lx\n" " cs=0x%016lx ss=0x%016lx rflags=0x%016lx int=0x%02lx err=0x%02lx\n", r->rip, r->rsi, r->rdi, r->rbp, r->rsp, r->rax, r->rbx, r->rcx, r->rdx, r->r8, r->r9, r->r10, r->r11, r->r12, r->r13, r->r14, r->r15, r->cs, r->ss, r->rflags, r->int_no, r->err_code ); /* Dump GS segment register information */ uint32_t gs_base_low, gs_base_high; asm volatile ( "rdmsr" : "=a" (gs_base_low), "=d" (gs_base_high): "c" (0xc0000101) ); uint32_t kgs_base_low, kgs_base_high; asm volatile ( "rdmsr" : "=a" (kgs_base_low), "=d" (kgs_base_high): "c" (0xc0000102) ); dprintf(" gs=0x%08x%08x kgs=0x%08x%08x\n", gs_base_high, gs_base_low, kgs_base_high, kgs_base_low); /* Walk the call stack from before the interrupt */ safe_dump_traceback(r); /* Stop this core */ arch_fatal(); } /** * @brief Debug interrupt * * Called when a CPU is single-stepping. We need to reset * the single-step flag in RFLAGS and if we were actually * debugging the current process we need to trigger a ptrace * SINGLESTEP event. This should also return immediately * from the syscall handler. * * @param r Interrupt register context * @return Register context, which should be unmodified. */ static void _debug_int(struct regs * r) { /* Unset the debug flag */ r->rflags &= ~(1 << 8); /* If the current process was debugging, trigger a SINGLESTEP event. */ if (this_core->current_process->flags & PROC_FLAG_TRACE_SIGNALS) { ptrace_signal(SIGTRAP, PTRACE_EVENT_SINGLESTEP); } } /** * @brief Double fault should always panic. */ static void _double_fault(struct regs * r) { panic("Double fault", r, 0); } /** * @brief GPF handler. * * Mostly this is separated from other exceptions because * GPF should cause SIGSEGV rather than SIGILL? I think? * * @param r Interrupt register context */ static void _general_protection_fault(struct regs * r) { /* Were we in the kernel? */ if (!this_core->current_process || r->cs == 0x08) { /* Then that's a panic. */ panic("GPF in kernel", r, 0); } /* Else, segfault the current process. */ send_signal(this_core->current_process->id, SIGSEGV, 1); } /** * @brief Page fault handler. * * Handles magic return addresses, stack expansions, maybe * later will handle COW or mmap'd filed... otherwise, * mostly segfaults. * * @param r Interrupt register context */ static void _page_fault(struct regs * r) { /* Obtain the "cause" address */ uintptr_t faulting_address; asm volatile("mov %%cr2, %0" : "=r"(faulting_address)); /* magic ret-from-sig address */ if (faulting_address == 0x516) { return_from_signal_handler(r); return; } if ((r->err_code & 3) == 3) { /* This is probably a COW page? */ extern int mmu_copy_on_write(uintptr_t address); if (!mmu_copy_on_write(faulting_address)) return; } /* Was this a kernel page fault? Those are always a panic. */ if (!this_core->current_process || r->cs == 0x08) { panic("Page fault in kernel", r, faulting_address); } /* Page was present but not writable */ /* Quietly map more stack if it was a viable stack address. */ if (faulting_address < 0x800000000000 && faulting_address > 0x700000000000) { if (map_more_stack(faulting_address & 0xFFFFffffFFFFf000)) return; } /* Otherwise, segfault the current process. */ send_signal(this_core->current_process->id, SIGSEGV, 1); } /** * @brief AP-local timer signal. * * Update clocks and switch task gracefully. * * @param r Interrupt register context * @return Register state after resume from task task switch. */ static void _local_timer(struct regs * r) { extern void arch_update_clock(void); arch_update_clock(); if (r->cs != 0x08) switch_task(1); } /** * @brief Handle an exception interrupt. * * @param r Interrupt register context * @param description Textual description of the exception, for panic messages. */ static void _exception(struct regs * r, const char * description, int signum) { /* If we were in kernel space, this is a panic */ if (!this_core->current_process || r->cs == 0x08) { panic(description, r, r->int_no); } /* Otherwise, these interrupts should trigger a signal */ send_signal(this_core->current_process->id, signum, 1); } /** * @brief Handle an installable interrupt. This handles PIC IRQs * that need to be acknowledged. * * @param r Interrupt register context * @param irq Translated IRQ number */ static void _handle_irq(struct regs * r, int irq) { for (size_t i = 0; i < IRQ_CHAIN_DEPTH; i++) { irq_handler_chain_t handler = irq_routines[i * IRQ_CHAIN_SIZE + irq]; if (!handler) break; if (handler(r)) return; } /* Unhandled */ irq_ack(irq); } #define EXC(i,n,s) case i: _exception(r, n, s); break; #define IRQ(i) case i: _handle_irq(r,i-32); break; void isr_handler_inner(struct regs * r) { switch (r->int_no) { EXC(0,"divide-by-zero",SIGFPE); case 1: _debug_int(r); return; /* NMI doesn't reach here, we use it as a panic signal. */ EXC(3,"breakpoint",SIGTRAP); /* TODO: This should map to a ptrace event */ EXC(4,"overflow",SIGFPE); EXC(5,"bound range exceeded",SIGBUS); EXC(6,"invalid opcode",SIGILL); EXC(7,"device not available",SIGBUS); case 8: _double_fault(r); break; /* 9 is a legacy exception that shouldn't happen */ EXC(10,"invalid TSS",SIGBUS); EXC(11,"segment not present",SIGBUS); EXC(12,"stack-segment fault",SIGBUS); case 13: _general_protection_fault(r); break; case 14: _page_fault(r); break; /* 15 is reserved */ EXC(16,"floating point exception",SIGFPE); EXC(17,"alignment check",SIGBUS); EXC(18,"machine check",SIGBUS); EXC(19,"SIMD floating-point exception",SIGFPE); EXC(20,"virtualization exception",SIGBUS); EXC(21,"control protection exception",SIGBUS); /* 22 through 27 are reserved */ EXC(28,"hypervisor injection exception",SIGBUS); EXC(29,"VMM communication exception",SIGBUS); EXC(30,"security exception",SIGBUS); /* 31 is reserved */ /* 16 IRQs that go to the general IRQ chain */ IRQ(32); IRQ(33); IRQ(34); IRQ(35); IRQ(36); IRQ(37); IRQ(38); case 39: break; /* Except the spurious IRQ, just ignore that */ IRQ(40); IRQ(41); IRQ(42); IRQ(43); IRQ(44); IRQ(45); IRQ(46); IRQ(47); /* Local interrupts that make it here. */ case 123: _local_timer(r); return; case 127: syscall_handler(r); return; /* Other interrupts that don't make it here: * 124: TLB shootdown, we just reload CR3 in the handler. * 125: Fatal signal, jumps straight to a cli/hlt loop, though I think this just yields an NMI instead? * 126: Quiet wakeup, do we even use this anymore? */ default: panic("Unexpected interrupt",r,0); } if (this_core->current_process == this_core->kernel_idle_task && process_queue && process_queue->head) { /* If this is kidle and we got here, instead of finishing the interrupt * we can just switch task and there will probably be something else * to run that was awoken by the interrupt. */ switch_next(); } } void isr_handler(struct regs * r) { int from_userspace = r->cs != 0x08; if (from_userspace && this_core->current_process) { this_core->current_process->time_switch = arch_perf_timer(); } isr_handler_inner(r); if (from_userspace && this_core->current_process) { process_check_signals(r); update_process_times_on_exit(); } } void syscall_centry(struct regs * r) { this_core->current_process->time_switch = arch_perf_timer(); syscall_handler(r); process_check_signals(r); update_process_times_on_exit(); } ================================================ FILE: kernel/arch/x86_64/irq.S ================================================ /** * @file kernel/arch/x86_64/irq.S * @brief x86-64 interrupt entry points * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ .section .text .align 8 .macro IRQ index byte .global _irq\index .type _irq\index, @function _irq\index: pushq $0x00 pushq $\byte jmp isr_common .endm .macro ISR_NOERR index .global _isr\index .type _isr\index, @function _isr\index: pushq $0x00 pushq $\index jmp isr_common .endm .macro ISR_ERR index .global _isr\index .type _isr\index, @function _isr\index: pushq $\index jmp isr_common .endm /* Interrupt Requests */ ISR_NOERR 0 ISR_NOERR 1 //ISR_NOERR 2 ISR_NOERR 3 ISR_NOERR 4 ISR_NOERR 5 ISR_NOERR 6 ISR_NOERR 7 ISR_ERR 8 ISR_NOERR 9 ISR_ERR 10 ISR_ERR 11 ISR_ERR 12 ISR_ERR 13 ISR_ERR 14 ISR_NOERR 15 ISR_NOERR 16 ISR_ERR 17 ISR_NOERR 18 ISR_NOERR 19 ISR_NOERR 20 ISR_ERR 21 ISR_NOERR 22 ISR_NOERR 23 ISR_NOERR 24 ISR_NOERR 25 ISR_NOERR 26 ISR_NOERR 27 ISR_NOERR 28 ISR_ERR 29 ISR_ERR 30 ISR_NOERR 31 IRQ 0, 32 IRQ 1, 33 IRQ 2, 34 IRQ 3, 35 IRQ 4, 36 IRQ 5, 37 IRQ 6, 38 IRQ 7, 39 IRQ 8, 40 IRQ 9, 41 IRQ 10, 42 IRQ 11, 43 IRQ 12, 44 IRQ 13, 45 IRQ 14, 46 IRQ 15, 47 /* syscall entry point */ ISR_NOERR 127 .global _isr123 .type _isr123, @function _isr123: /* Acknowledge IPI */ pushq %r12 mov (lapic_final)(%rip), %r12 add $0xb0, %r12 movl $0, (%r12) popq %r12 /* Then we can proceed! */ pushq $0x00 pushq $123 jmp isr_common .global _isr124 .type _isr124, @function _isr124: pushq %r12 mov %cr3, %r12 mov %r12, %cr3 mov (lapic_final)(%rip), %r12 add $0xb0, %r12 movl $0, (%r12) popq %r12 iretq /* No op, used to signal sleeping processor to wake and check the queue. */ .extern lapic_final .global _isr126 .type _isr126, @function _isr126: pushq %r12 mov (lapic_final)(%rip), %r12 add $0xb0, %r12 movl $0, (%r12) popq %r12 iretq /* Fatal signal, stop everything. */ .global _isr125 .type _isr125, @function _isr125: cli 1: hlt jmp 1b /* Fatal signal, stop everything. */ .global _isr2 .type _isr2, @function _isr2: cli 1: hlt jmp 1b .macro _swapgs cmpq $8, 24(%rsp) je 1f swapgs 1: .endm .extern isr_handler .type isr_handler, @function .global isr_common isr_common: /* Save all registers */ _swapgs push %rax push %rbx push %rcx push %rdx push %rsi push %rdi push %rbp push %r8 push %r9 push %r10 push %r11 push %r12 push %r13 push %r14 push %r15 cld /* Call interrupt handler */ mov %rsp, %rdi call isr_handler /* Restore all registers */ pop %r15 pop %r14 pop %r13 pop %r12 pop %r11 pop %r10 pop %r9 pop %r8 pop %rbp pop %rdi pop %rsi pop %rdx pop %rcx pop %rbx pop %rax _swapgs /* Cleanup error code and interrupt # */ add $16, %rsp /* Return from interrupt */ iretq .global arch_save_context .type arch_save_context, @function arch_save_context: leaq 8(%rsp), %rax movq %rax, 0(%rdi) movq %rbp, 8(%rdi) movq (%rsp), %rax movq %rax, 16(%rdi) movq $0xc0000100, %rcx rdmsr movl %eax, 24(%rdi) movl %edx, 28(%rdi) movq %rbx, 32(%rdi) movq %r12, 40(%rdi) movq %r13, 48(%rdi) movq %r14, 56(%rdi) movq %r15, 64(%rdi) xor %rax, %rax retq .global arch_restore_context .type arch_restore_context, @function arch_restore_context: mov %gs:0x10,%rax cmp %gs:0x0,%rax je 1f lock andl $0xFFFFfff7,0x14(%rax) 1: movq 0(%rdi), %rsp movq 8(%rdi), %rbp movl 24(%rdi), %eax movl 28(%rdi), %edx movq $0xc0000100, %rcx wrmsr movq 32(%rdi), %rbx movq 40(%rdi), %r12 movq 48(%rdi), %r13 movq 56(%rdi), %r14 movq 64(%rdi), %r15 movq $1, %rax jmpq *16(%rdi) .global arch_enter_tasklet .type arch_enter_tasklet, @function arch_enter_tasklet: popq %rdi popq %rsi jmpq *%rsi .extern syscall_centry .global syscall_entry .type syscall_entry, @function syscall_entry: swapgs /* SYSCALL only happens from userspace, so we must always swap gs */ mov %rsp, %gs:0x78 /* Store user RSP temporarily */ mov %gs:0x70, %rsp /* Restore kernel stack for this thread */ /* Normal `struct regs` layout, same as what we'd get on an interrupt */ pushq $0x23 /* SS */ pushq %gs:0x78 /* RSP */ push %r11 /* RFLAGS - SYSCALL stores in r11 */ pushq $0x2b /* CS */ push %rcx /* RIP - SYSCALL stores in rcx */ pushq $0 /* Dummy error code */ pushq $0 /* Dummy interrupt number */ push %rax push %rbx pushq $0 /* rcx is not valid, set to zero */ push %rdx push %rsi push %rdi push %rbp push %r8 push %r9 push %r10 pushq $0 /* r11 is not valid, set to zero */ push %r12 push %r13 push %r14 push %r15 mov %rsp, %rdi call syscall_centry pop %r15 pop %r14 pop %r13 pop %r12 add $8, %rsp pop %r10 pop %r9 pop %r8 pop %rbp pop %rdi pop %rsi pop %rdx add $8, %rsp pop %rbx pop %rax add $16, %rsp pop %rcx add $8, %rsp pop %r11 pop %rsp swapgs sysretq ================================================ FILE: kernel/arch/x86_64/link.ld ================================================ OUTPUT_FORMAT(elf64-x86-64) ENTRY(start) SECTIONS { . = 1M; phys = .; .text BLOCK(4K) : ALIGN(4K) { *(.multiboot) *(.bootstrap) code = .; *(.text) *(.shit) } .rodata BLOCK(4K) : ALIGN(4K) { *(.rodata) } .rela.dyn BLOCK(4K) : ALIGN(4K) { _rela_start = .; *(.rela) *(.rela.text) *(.rela.got) *(.rela.plt) *(.rela.bss) *(.rela.ifunc) *(.rela.text.*) *(.rela.data) *(.rela.data.*) *(.rela.rodata) *(.rela.rodata*) *(.rela.dyn) _rela_end = .; } .data BLOCK(4K) : ALIGN(4K) { data = .; *(.data) *(.symbols) PROVIDE(kernel_symbols_start = .); PROVIDE(kernel_symbols_end = .); } .bss BLOCK(4K) : ALIGN(4K) { PROVIDE(bss_start = .); bss = .; *(COMMON) *(.bss) *(.stack) } end = .; /DISCARD/ : { *(.comment) *(.eh_frame) *(.note.gnu.build-id) } } ================================================ FILE: kernel/arch/x86_64/main.c ================================================ /** * @file kernel/arch/x86_64/main.c * @brief Intel/AMD x86-64 (IA64/amd64) architecture-specific startup. * * Parses multiboot data, sets up GDT/IDT/TSS, initializes PML4 paging, * and sets up PC device drivers (PS/2, port I/O, serial). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern void arch_clock_initialize(void); extern char end[]; extern unsigned long tsc_mhz; extern void gdt_install(void); extern void idt_install(void); extern void pic_initialize(void); extern void pit_initialize(void); extern void smp_initialize(void); extern void portio_initialize(void); extern void ps2hid_install(void); extern void serial_initialize(void); extern void fbterm_initialize(void); extern void pci_remap(void); extern void mmu_init(size_t memsize, uintptr_t firstFreePage); struct multiboot * mboot_struct = NULL; int mboot_is_2 = 0; static int _serial_debug = 1; #define EARLY_LOG_DEVICE 0x3F8 static size_t _early_log_write(size_t size, uint8_t * buffer) { if (!_serial_debug) return size; for (unsigned int i = 0; i < size; ++i) { outportb(EARLY_LOG_DEVICE, buffer[i]); } return size; } static void early_log_initialize(void) { outportb(EARLY_LOG_DEVICE + 3, 0x03); /* Disable divisor mode, set parity */ printf_output = &_early_log_write; } static uintptr_t highest_valid_address = 0; static uintptr_t highest_kernel_address = (uintptr_t)&end; struct MB2_TagHeader { uint32_t type; uint32_t size; }; void * mboot2_find_next(char * current, uint32_t type) { char * header = current; while ((uintptr_t)header & 7) header++; struct MB2_TagHeader * tag = (void*)header; while (1) { if (tag->type == 0) return NULL; if (tag->type == type) return tag; /* Next tag */ header += tag->size; while ((uintptr_t)header & 7) header++; tag = (void*)header; } } void * mboot2_find_tag(void * fromStruct, uint32_t type) { char * header = (void*)fromStruct; header += 8; return mboot2_find_next(header, type); } struct MB2_MemoryMap { struct MB2_TagHeader head; uint32_t entry_size; uint32_t entry_version; char entries[]; }; struct MB2_MemoryMap_Entry { uint64_t base_addr; uint64_t length; uint32_t type; uint32_t reserved; }; struct MB2_Framebuffer { struct MB2_TagHeader head; uint64_t addr; uint32_t pitch; uint32_t width; uint32_t height; uint8_t bpp; uint8_t fb_type; }; struct MB2_Module { struct MB2_TagHeader head; uint32_t mod_start; uint32_t mod_end; uint8_t cmdline[]; }; static void multiboot2_initialize(void * mboot) { mboot_is_2 = 1; dprintf("multiboot: Started with a Multiboot 2 loader\n"); struct MB2_MemoryMap * mmap = mboot2_find_tag(mboot, 6); if (!mmap) { printf("fatal: unable to boot without memory map from loader\n"); arch_fatal(); } char * entry = mmap->entries; while ((uintptr_t)entry < (uintptr_t)mmap + mmap->head.size) { struct MB2_MemoryMap_Entry * this = (void*)entry; if (this->type == 1 && this->length && this->base_addr + this->length - 1> highest_valid_address) { highest_valid_address = this->base_addr + this->length - 1; } entry += mmap->entry_size; } struct MB2_Module * mod = mboot2_find_tag(mboot, 3); while (mod) { uintptr_t addr = (uintptr_t)mod->mod_end; if (addr > highest_kernel_address) highest_kernel_address = addr; mod = mboot2_find_next((char*)mod + mod->head.size, 3); } /* Round the max address up a page */ highest_kernel_address = (highest_kernel_address + 0xFFF) & 0xFFFFffffFFFFf000; } static void multiboot_initialize(struct multiboot * mboot) { dprintf("multiboot: Started with a Multiboot 1 loader\n"); if (!(mboot->flags & MULTIBOOT_FLAG_MMAP)) { printf("fatal: unable to boot without memory map from loader\n"); arch_fatal(); } mboot_memmap_t * mmap = (void *)(uintptr_t)mboot->mmap_addr; if ((uintptr_t)mmap + mboot->mmap_length > highest_kernel_address) { highest_kernel_address = (uintptr_t)mmap + mboot->mmap_length; } while ((uintptr_t)mmap < mboot->mmap_addr + mboot->mmap_length) { if (mmap->type == 1 && mmap->length && mmap->base_addr + mmap->length - 1> highest_valid_address) { highest_valid_address = mmap->base_addr + mmap->length - 1; } mmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uint32_t)); } if (mboot->flags & MULTIBOOT_FLAG_MODS) { mboot_mod_t * mods = (mboot_mod_t *)(uintptr_t)mboot->mods_addr; for (unsigned int i = 0; i < mboot->mods_count; ++i) { uintptr_t addr = (uintptr_t)mods[i].mod_end; if (addr > highest_kernel_address) { highest_kernel_address = addr; } } } /* Round the max address up a page */ highest_kernel_address = (highest_kernel_address + 0xFFF) & 0xFFFFffffFFFFf000; } void mboot_unmark_valid_memory(void) { size_t frames_marked = 0; if (mboot_is_2) { struct MB2_MemoryMap * mmap = mboot2_find_tag(mboot_struct, 6); char * entry = mmap->entries; while ((uintptr_t)entry < (uintptr_t)mmap + mmap->head.size) { struct MB2_MemoryMap_Entry * this = (void*)entry; if (this->type == 1) { for (uintptr_t base = this->base_addr; base < this->base_addr + (this->length & 0xFFFFffffFFFFf000); base += 0x1000) { mmu_frame_clear(base); frames_marked++; } } entry += mmap->entry_size; } } else { mboot_memmap_t * mmap = mmu_map_from_physical((uintptr_t)mboot_struct->mmap_addr); while ((uintptr_t)mmap < (uintptr_t)mmu_map_from_physical(mboot_struct->mmap_addr + mboot_struct->mmap_length)) { if (mmap->type == 1) { for (uintptr_t base = mmap->base_addr; base < mmap->base_addr + (mmap->length & 0xFFFFffffFFFFf000); base += 0x1000) { mmu_frame_clear(base); frames_marked++; } } mmap = (mboot_memmap_t *) ((uintptr_t)mmap + mmap->size + sizeof(uint32_t)); } } } static void symbols_install(uint64_t base) { ksym_install(); kernel_symbol_t * k = (kernel_symbol_t *)&kernel_symbols_start; while ((uintptr_t)k < (uintptr_t)&kernel_symbols_end) { ksym_bind(k->name, (void*)(k->addr + base)); k = (kernel_symbol_t *)((uintptr_t)k + sizeof *k + strlen(k->name) + 1); } } /** * @brief Initializes the page attribute table. * FIXME: This seems to be assuming the lower entries are * already sane - we should probably initialize all * of the entries ourselves. */ void pat_initialize(void) { asm volatile ( "mov $0x277, %%ecx\n" /* IA32_MSR_PAT */ "rdmsr\n" "or $0x1000000, %%edx\n" /* set bit 56 */ "and $0xf9ffffff, %%edx\n" /* unset bits 57, 58 */ "wrmsr\n" : : : "ecx", "edx", "eax" ); } /** * @brief Turns on the floating-point unit. * * Enables a few bits so we can get SSE. * * We don't do any fancy lazy FPU reload as x86-64 assumes a wide * variety of FPU-provided registers are available so most userspace * code will be messing with the FPU anyway and we'd probably just * waste time with all the interrupts turning it off and on... */ void fpu_initialize(void) { asm volatile ( "clts\n" "mov %%cr0, %%rax\n" "and $0xFFFD, %%ax\n" "or $0x10, %%ax\n" "mov %%rax, %%cr0\n" "fninit\n" "mov %%cr0, %%rax\n" "and $0xfffb, %%ax\n" "or $0x0002, %%ax\n" "mov %%rax, %%cr0\n" "mov %%cr4, %%rax\n" "or $0x600, %%rax\n" "mov %%rax, %%cr4\n" "push $0x1F80\n" "ldmxcsr (%%rsp)\n" "addq $8, %%rsp\n" : : : "rax"); } static void mount_ramdisk(uintptr_t addr, size_t len) { uint8_t * data = mmu_map_from_physical(addr); if (data[0] == 0x1F && data[1] == 0x8B) { /* Yes - decompress it first */ dprintf("multiboot: Decompressing initial ramdisk...\n"); uint32_t decompressedSize = *(uint32_t*)mmu_map_from_physical(addr + len - sizeof(uint32_t)); size_t pageCount = (((size_t)decompressedSize + 0xFFF) & ~(0xFFF)) >> 12; uintptr_t physicalAddress = mmu_allocate_n_frames(pageCount) << 12; if (physicalAddress == (uintptr_t)-1) { dprintf("gzip: failed to allocate pages\n"); return; } gzip_inputPtr = (void*)data; gzip_outputPtr = mmu_map_from_physical(physicalAddress); /* Do the deed */ if (gzip_decompress()) { dprintf("gzip: failed to decompress payload\n"); return; } ramdisk_mount(physicalAddress, decompressedSize); dprintf("multiboot: Decompressed %lu kB to %u kB.\n", (len) / 1024, (decompressedSize) / 1024); /* Free the pages from the original mod */ for (size_t j = addr; j < addr + len; j += 0x1000) { mmu_frame_clear(j); } } else { /* No, or it doesn't look like one - mount it directly */ dprintf("multiboot: Mounting uncompressed ramdisk.\n"); ramdisk_mount(addr, len); } } /** * @brief Decompress compressed ramdisks and hand them to the ramdisk driver. * * Reads through the list of modules passed by a multiboot-compatible loader * and determines if they are gzip-compressed, decompresses if they are, and * finally hands them to the VFS driver. The VFS ramdisk driver takes control * of linear sets of physical pages, and handles mapping them somewhere to * provide reads in userspace, as well as freeing them if requested. */ void mount_multiboot_ramdisks(struct multiboot * mboot) { /* ramdisk_mount takes physical pages, it will map them itself. */ if (mboot_is_2) { struct MB2_Module * mod = mboot2_find_tag(mboot_struct, 3); while (mod) { uintptr_t address = mod->mod_start; size_t length = mod->mod_end - mod->mod_start; mount_ramdisk(address, length); mod = mboot2_find_next((char*)mod + mod->head.size, 3); } } else { mboot_mod_t * mods = mmu_map_from_physical(mboot->mods_addr); for (unsigned int i = 0; i < mboot->mods_count; ++i) { uint64_t address = mods[i].mod_start; uint64_t length = mods[i].mod_end - mods[i].mod_start; mount_ramdisk(address, length); } } } /** * x86-64: The kernel commandline is retrieved from the multiboot struct. */ const char * arch_get_cmdline(void) { if (mboot_is_2) { struct loader { uint32_t type; uint32_t size; char name[]; } * loader = mboot2_find_tag(mboot_struct, 1); if (loader) { return loader->name; } return ""; } else { return mmu_map_from_physical(mboot_struct->cmdline); } } /** * x86-64: The bootloader name is retrieved from the multiboot struct. */ const char * arch_get_loader(void) { if (mboot_is_2) { struct loader { uint32_t type; uint32_t size; char name[]; } * loader = mboot2_find_tag(mboot_struct, 2); if (loader) { return loader->name; } } else if (mboot_struct->flags & MULTIBOOT_FLAG_LOADER) { return mmu_map_from_physical(mboot_struct->boot_loader_name); } return "(unknown)"; } /** * x86-64: The GS register, which is set by a pair of MSRs, tracks per-CPU kernel state. */ void arch_set_core_base(uintptr_t base) { asm volatile ("wrmsr" : : "c"(0xc0000101), "d"((uint32_t)(base >> 32)), "a"((uint32_t)(base & 0xFFFFFFFF))); asm volatile ("wrmsr" : : "c"(0xc0000102), "d"((uint32_t)(base >> 32)), "a"((uint32_t)(base & 0xFFFFFFFF))); asm volatile ("swapgs"); } void arch_framebuffer_initialize(void) { extern uint8_t * lfb_vid_memory; extern uint16_t lfb_resolution_x; extern uint16_t lfb_resolution_y; extern uint32_t lfb_resolution_s; extern uint16_t lfb_resolution_b; if (!mboot_is_2) { lfb_vid_memory = mmu_map_from_physical(mboot_struct->framebuffer_addr); lfb_resolution_x = mboot_struct->framebuffer_width; lfb_resolution_y = mboot_struct->framebuffer_height; lfb_resolution_s = mboot_struct->framebuffer_pitch; lfb_resolution_b = mboot_struct->framebuffer_bpp; } else { struct MB2_Framebuffer * fb = mboot2_find_tag(mboot_struct, 8); if (fb) { lfb_vid_memory = mmu_map_from_physical(fb->addr); lfb_resolution_x = fb->width; lfb_resolution_y = fb->height; lfb_resolution_s = fb->pitch; lfb_resolution_b = fb->bpp; } } } /** * @brief x86-64 multiboot C entrypoint. * * Called by the x86-64 longmode bootstrap. */ int kmain(struct multiboot * mboot, uint32_t mboot_mag, void* esp, uint64_t base) { extern Elf64_Rela _rela_start[], _rela_end[]; for (Elf64_Rela * rela = _rela_start; rela < _rela_end; ++rela) { switch (ELF64_R_TYPE(rela->r_info)) { case R_X86_64_RELATIVE: *(uint64_t*)(rela->r_offset + base) = base + rela->r_addend; break; } } /* The debug log is over /dev/ttyS0, but skips the PTY interface; it's available * as soon as we can call printf(), which is as soon as we get to long mode. */ early_log_initialize(); dprintf("%s %d.%d.%d-%s %s %s\n", __kernel_name, __kernel_version_major, __kernel_version_minor, __kernel_version_lower, __kernel_version_suffix, __kernel_version_codename, __kernel_arch); /* Initialize GS base */ arch_set_core_base((uintptr_t)&processor_local_data[0]); /* Time the TSC and get the initial boot time from the RTC. */ arch_clock_initialize(); /* Parse multiboot data so we can get memory map, modules, command line, etc. */ if (mboot_mag == 0x36d76289) { multiboot2_initialize(mboot); } else { multiboot_initialize(mboot); } /* multiboot memory is now mapped high, if you want it. */ mboot_struct = mmu_map_from_physical((uintptr_t)mboot); /* memCount and maxAddress come from multiboot data */ mmu_init(highest_valid_address, highest_kernel_address); /* With the MMU initialized, set up things required for the scheduler. */ pat_initialize(); symbols_install(base); gdt_install(); idt_install(); fpu_initialize(); pic_initialize(); /* Early generic stuff */ generic_startup(); /* Should we override the TSC timing? */ if (args_present("tsc_mhz")) { tsc_mhz = atoi(args_value("tsc_mhz")); } if (!args_present("debug")) { _serial_debug = 0; } /* Scheduler is running and we have parsed the kcmdline, initialize video. */ framebuffer_initialize(); fbterm_initialize(); /* Start up other cores and enable an appropriate preempt source. */ smp_initialize(); /* Decompress and mount all initial ramdisks. */ mount_multiboot_ramdisks(mboot_struct); /* Install generic PC device drivers. */ ps2hid_install(); serial_initialize(); portio_initialize(); /* Yield to the generic main, which starts /bin/init */ return generic_main(); } ================================================ FILE: kernel/arch/x86_64/mmu.c ================================================ /** * @file kernel/arch/x86_64/mmu.c * @brief Memory management facilities for x86-64 * * Frame allocation and mapping routines for x86-64. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include extern void arch_tlb_shootdown(uintptr_t); /** * bitmap page allocator for 4KiB pages */ static volatile uint32_t *frames; static size_t nframes; static size_t total_memory = 0; static size_t unavailable_memory = 0; static uint8_t * mem_refcounts = NULL; #define PAGE_SHIFT 12 #define PAGE_SIZE 0x1000UL #define PAGE_SIZE_MASK 0xFFFFffffFFFFf000UL #define PAGE_LOW_MASK 0x0000000000000FFFUL #define LARGE_PAGE_SIZE 0x200000UL #define USER_PML_ACCESS 0x07 #define KERNEL_PML_ACCESS 0x03 #define LARGE_PAGE_BIT 0x80 #define PDP_MASK 0x3fffffffUL #define PD_MASK 0x1fffffUL #define PT_MASK PAGE_LOW_MASK #define ENTRY_MASK 0x1FF #define PHYS_MASK 0x7fffffffffUL #define CANONICAL_MASK 0xFFFFffffFFFFUL #define INDEX_FROM_BIT(b) ((b) >> 5) #define OFFSET_FROM_BIT(b) ((b) & 0x1F) /** * @brief Mark a physical page frame as in use. * * Sets the bitmap allocator bit for a frame. * * @param frame_addr Address of the frame (not index!) */ void mmu_frame_set(uintptr_t frame_addr) { /* If the frame is within bounds... */ if (frame_addr < nframes * PAGE_SIZE) { uint64_t frame = frame_addr >> 12; uint64_t index = INDEX_FROM_BIT(frame); uint32_t offset = OFFSET_FROM_BIT(frame); frames[index] |= ((uint32_t)1 << offset); asm ("" ::: "memory"); } } static uintptr_t lowest_available = 0; /** * @brief Mark a physical page frame as available. * * Clears the bitmap allocator bit for a frame. * * @param frame_addr Address of the frame (not index!) */ void mmu_frame_clear(uintptr_t frame_addr) { /* If the frame is within bounds... */ if (frame_addr < nframes * PAGE_SIZE) { uint64_t frame = frame_addr >> PAGE_SHIFT; uint64_t index = INDEX_FROM_BIT(frame); uint32_t offset = OFFSET_FROM_BIT(frame); frames[index] &= ~((uint32_t)1 << offset); asm ("" ::: "memory"); if (frame < lowest_available) lowest_available = frame; } } /** * @brief Determine if a physical page is available for use. * * @param frame_addr Address of the frame (not index!) * @returns 0 if available, 1 otherwise. */ int mmu_frame_test(uintptr_t frame_addr) { if (!(frame_addr < nframes * PAGE_SIZE)) return 1; uint64_t frame = frame_addr >> PAGE_SHIFT; uint64_t index = INDEX_FROM_BIT(frame); uint32_t offset = OFFSET_FROM_BIT(frame); asm ("" ::: "memory"); return !!(frames[index] & ((uint32_t)1 << offset)); } static spin_lock_t frame_alloc_lock = { 0 }; static spin_lock_t kheap_lock = { 0 }; static spin_lock_t mmio_space_lock = { 0 }; static spin_lock_t module_space_lock = { 0 }; void mmu_frame_release(uintptr_t frame_addr) { spin_lock(frame_alloc_lock); mmu_frame_clear(frame_addr); spin_unlock(frame_alloc_lock); } /** * @brief Find the first range of @p n contiguous frames. * * If a large enough region could not be found, results are fatal. */ uintptr_t mmu_first_n_frames(int n) { for (uint64_t i = 0; i < nframes * PAGE_SIZE; i += PAGE_SIZE) { int bad = 0; for (int j = 0; j < n; ++j) { if (mmu_frame_test(i + PAGE_SIZE * j)) { bad = j + 1; } } if (!bad) { return i / PAGE_SIZE; } } arch_fatal_prepare(); dprintf("Failed to allocate %d contiguous frames.\n", n); arch_dump_traceback(); arch_fatal(); return (uintptr_t)-1; } /** * @brief Find the first available frame from the bitmap. */ uintptr_t mmu_first_frame(void) { uintptr_t i, j; for (i = INDEX_FROM_BIT(lowest_available); i < INDEX_FROM_BIT(nframes); ++i) { if (frames[i] != (uint32_t)-1) { for (j = 0; j < (sizeof(uint32_t)*8); ++j) { uint32_t testFrame = (uint32_t)1 << j; if (!(frames[i] & testFrame)) { uintptr_t out = (i << 5) + j; lowest_available = out + 1; return out; } } } } arch_fatal_prepare(); dprintf("Out of memory.\n"); arch_dump_traceback(); arch_fatal(); return (uintptr_t)-1; } /** * @brief Set the flags for a page, and allocate a frame for it if needed. * * Sets the page bits based on the the value of @p flags. * If @p page->bits.page is unset, a new frame will be allocated. */ void mmu_frame_allocate(union PML * page, unsigned int flags) { if (page->bits.page == 0) { spin_lock(frame_alloc_lock); uintptr_t index = mmu_first_frame(); mmu_frame_set(index << PAGE_SHIFT); page->bits.page = index; spin_unlock(frame_alloc_lock); } page->bits.size = 0; page->bits.present = 1; page->bits.writable = (flags & MMU_FLAG_WRITABLE) ? 1 : 0; page->bits.user = (flags & MMU_FLAG_KERNEL) ? 0 : 1; page->bits.nocache = (flags & MMU_FLAG_NOCACHE) ? 1 : 0; page->bits.writethrough = (flags & MMU_FLAG_WRITETHROUGH) ? 1 : 0; page->bits.size = (flags & MMU_FLAG_SPEC) ? 1 : 0; page->bits.nx = (flags & MMU_FLAG_NOEXECUTE) ? 1 : 0; } /** * @brief Map the given page to the requested physical address. */ void mmu_frame_map_address(union PML * page, unsigned int flags, uintptr_t physAddr) { mmu_frame_set(physAddr); page->bits.page = physAddr >> PAGE_SHIFT; mmu_frame_allocate(page, flags); } /* Initial memory maps loaded by boostrap */ #define _pagemap __attribute__((aligned(PAGE_SIZE))) = {0} union PML init_page_region[3][512] _pagemap; union PML high_base_pml[512] _pagemap; union PML heap_base_pml[512] _pagemap; union PML heap_base_pd[512] _pagemap; union PML heap_base_pt[512*3] _pagemap; union PML low_base_pmls[34][512] _pagemap; union PML twom_high_pds[64][512] _pagemap; /** * @brief Maps a frame address to a virtual address. * * Returns the virtual address within the general-purpose * identity mapping region for the given physical frame address. * This address is not suitable for some operations, such as MMIO. */ void * mmu_map_from_physical(uintptr_t frameaddress) { return (void*)(frameaddress | HIGH_MAP_REGION); } union PML * mmu_get_page_other(union PML * root, uintptr_t virtAddr) { uintptr_t realBits = virtAddr & CANONICAL_MASK; uintptr_t pageAddr = realBits >> PAGE_SHIFT; unsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK; unsigned int pdp_entry = (pageAddr >> 18) & ENTRY_MASK; unsigned int pd_entry = (pageAddr >> 9) & ENTRY_MASK; unsigned int pt_entry = (pageAddr) & ENTRY_MASK; /* Get the PML4 entry for this address */ if (!root[pml4_entry].bits.present) { return NULL; } union PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT); if (!pdp[pdp_entry].bits.present) { return NULL; } if (pdp[pdp_entry].bits.size) { return NULL; } union PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT); if (!pd[pd_entry].bits.present) { return NULL; } if (pd[pd_entry].bits.size) { return NULL; } union PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT); return (union PML *)&pt[pt_entry]; } /** * @brief Find the physical address at a given virtual address. * * Calculates the physical address of the page backing the virtual * address @p virtAddr. If no page is mapped, a negative value * is returned indicating which level of the page directory is * unmapped from -1 (no PDP) to -4 (page not present in table). */ uintptr_t mmu_map_to_physical(union PML * root, uintptr_t virtAddr) { uintptr_t realBits = virtAddr & CANONICAL_MASK; uintptr_t pageAddr = realBits >> PAGE_SHIFT; unsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK; unsigned int pdp_entry = (pageAddr >> 18) & ENTRY_MASK; unsigned int pd_entry = (pageAddr >> 9) & ENTRY_MASK; unsigned int pt_entry = (pageAddr) & ENTRY_MASK; /* Get the PML4 entry for this address */ if (!root[pml4_entry].bits.present) return (uintptr_t)-1; union PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT); if (!pdp[pdp_entry].bits.present) return (uintptr_t)-2; if (pdp[pdp_entry].bits.size) return ((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT) | (virtAddr & PDP_MASK); union PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT); if (!pd[pd_entry].bits.present) return (uintptr_t)-3; if (pd[pd_entry].bits.size) return ((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT) | (virtAddr & PD_MASK); union PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT); if (!pt[pt_entry].bits.present) return (uintptr_t)-4; return ((uintptr_t)pt[pt_entry].bits.page << PAGE_SHIFT) | (virtAddr & PT_MASK); } /** * @brief Obtain the page entry for a virtual address. * * Digs into the current page directory to obtain the page entry * for a requested address @p virtAddr. If new intermediary directories * need to be allocated and @p flags has @c MMU_GET_MAKE set, they * will be allocated with the user access bits set. Otherwise, * NULL will be returned. If the requested virtual address is within * a large page, NULL will be returned. * * @param virtAddr Canonical virtual address offset. * @param flags See @c MMU_GET_MAKE * @returns the requested page entry, or NULL if doing so required allocating * an intermediary paging level and @p flags did not have @c MMU_GET_MAKE set. */ union PML * mmu_get_page(uintptr_t virtAddr, int flags) { uintptr_t realBits = virtAddr & CANONICAL_MASK; uintptr_t pageAddr = realBits >> PAGE_SHIFT; unsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK; unsigned int pdp_entry = (pageAddr >> 18) & ENTRY_MASK; unsigned int pd_entry = (pageAddr >> 9) & ENTRY_MASK; unsigned int pt_entry = (pageAddr) & ENTRY_MASK; union PML * root = this_core->current_pml; /* Get the PML4 entry for this address */ if (!root[pml4_entry].bits.present) { if (!(flags & MMU_GET_MAKE)) goto _noentry; spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); /* zero it */ memset(mmu_map_from_physical(newPage), 0, PAGE_SIZE); root[pml4_entry].raw = (newPage) | USER_PML_ACCESS; } union PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT); if (!pdp[pdp_entry].bits.present) { if (!(flags & MMU_GET_MAKE)) goto _noentry; spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); /* zero it */ memset(mmu_map_from_physical(newPage), 0, PAGE_SIZE); pdp[pdp_entry].raw = (newPage) | USER_PML_ACCESS; } if (pdp[pdp_entry].bits.size) { printf("Warning: Tried to get page for a 1GiB page!\n"); return NULL; } union PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT); if (!pd[pd_entry].bits.present) { if (!(flags & MMU_GET_MAKE)) goto _noentry; spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); /* zero it */ memset(mmu_map_from_physical(newPage), 0, PAGE_SIZE); pd[pd_entry].raw = (newPage) | USER_PML_ACCESS; } if (pd[pd_entry].bits.size) { printf("Warning: Tried to get page for a 2MiB page!\n"); return NULL; } union PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT); return (union PML *)&pt[pt_entry]; _noentry: printf("no entry for requested page\n"); return NULL; } /** * @brief Increment the reference count for a physical page of memory. * * We allow up to 255 references to a page, so that we can track individual * page reference counts in a big @c uint8_t array. If there are already * that many references (that's a lot of forks!) we give up and do a regular * copy of the page and the new copy is writable. * * @param frame Physical page index * @returns 1 if there are already too many references to this page, 0 otherwise. */ int refcount_inc(uintptr_t frame) { if (frame >= nframes) { arch_fatal_prepare(); dprintf("%zu (inc, bad frame)\n", frame); arch_dump_traceback(); arch_fatal(); } if (mem_refcounts[frame] == 255) return 1; mem_refcounts[frame]++; return 0; } /** * @brief Decrement the reference count for a physical page of memory. * * Panics if @p frame is invalid or has a zero reference count. * * @param frame Physical page index * @returns the resulting reference count. */ uint8_t refcount_dec(uintptr_t frame) { if (frame >= nframes) { arch_fatal_prepare(); dprintf("%zu (dec, bad frame)\n", frame); arch_dump_traceback(); arch_fatal(); } if (mem_refcounts[frame] == 0) { arch_fatal_prepare(); dprintf("%zu (dec, frame has no references)\n", frame); arch_dump_traceback(); arch_fatal(); } mem_refcounts[frame]--; return mem_refcounts[frame]; } /** * @brief Handle user pages in mmu_clone * * Copies and updates reference counts for pages across forks. * If a page was writable in the source directory, it will be marked * read-only and have reference counts initialized for COW. * * If a page was already read-only, its reference count will * be incremented for the new directory. * * @param pt_in Existing page table. * @param pt_out New directory's page table. * @param l Index into both page tables for this page. * @param address Virtual address being referenced. * @returns 0, generally */ int copy_page_maybe(union PML * pt_in, union PML * pt_out, size_t l, uintptr_t address) { /* Can we cow the current page? */ spin_lock(frame_alloc_lock); /* Is the page writable? */ if (pt_in[l].bits.writable) { /* Then we need to initialize the refcounts */ if (mem_refcounts[pt_in[l].bits.page] != 0) { arch_fatal_prepare(); dprintf("%#zx (page=%u) refcount = %u\n", address, pt_in[l].bits.page, mem_refcounts[pt_in[l].bits.page]); arch_dump_traceback(); arch_fatal(); return 1; } mem_refcounts[pt_in[l].bits.page] = 2; pt_in[l].bits.writable = 0; pt_in[l].bits.cow_pending = 1; pt_out[l].raw = pt_in[l].raw; asm ("" ::: "memory"); mmu_invalidate(address); spin_unlock(frame_alloc_lock); return 0; } /* Can we make a new reference? */ if (refcount_inc(pt_in[l].bits.page)) { /* There are too many references to fit in our refcount table, so just make a new page. */ char * page_in = mmu_map_from_physical((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; char * page_out = mmu_map_from_physical(newPage); memcpy(page_out,page_in,PAGE_SIZE); pt_out[l].raw = 0; pt_out[l].bits.present = 1; pt_out[l].bits.user = 1; pt_out[l].bits.page = newPage >> PAGE_SHIFT; pt_out[l].bits.writable = 1; pt_out[l].bits.cow_pending = 0; asm ("" ::: "memory"); } else { pt_out[l].raw = pt_in[l].raw; } spin_unlock(frame_alloc_lock); return 0; } /** * @brief When freeing a directory, handle individual user pages. * * If @p pt_in references a writable user page, we know we can * free it immediately as it is the only reference to that page. * * Otherwise, we need to decrement the reference counts for read-only * pages, as they are shared COW entries. Only if this was the last * reference (refcount drops to 0) can we then proceed to free the * underlying page. * * @param pt_in Start of page table * @param l Offset into page table for this page * @param address Virtual address being freed (was used for debugging) * @returns 0, generally */ int free_page_maybe(union PML * pt_in, size_t l, uintptr_t address) { if (pt_in[l].bits.writable) { assert(mem_refcounts[pt_in[l].bits.page] == 0); mmu_frame_clear((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT); return 0; } /* No more references */ if (refcount_dec(pt_in[l].bits.page) == 0) { mmu_frame_clear((uintptr_t)pt_in[l].bits.page << PAGE_SHIFT); } return 0; } /** * @brief Create a new address space with the same contents of an existing one. * * Allocates all of the necessary intermediary directory levels for a new address space * and also copies data from the existing address space. * * TODO: This doesn't do any CoW and it's kinda complicated. * * @param from The directory to clone, or NULL to clone the kernel map. * @returns a pointer to the new page directory, suitable for mapping to a physical address. */ union PML * mmu_clone(union PML * from) { /* Clone the current PMLs... */ if (!from) from = this_core->current_pml; /* First get a page for ourselves. */ spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); union PML * pml4_out = mmu_map_from_physical(newPage); /* Zero bottom half */ memset(&pml4_out[0], 0, 256 * sizeof(union PML)); /* Copy top half */ memcpy(&pml4_out[256], &from[256], 256 * sizeof(union PML)); /* Copy PDPs */ for (size_t i = 0; i < 256; ++i) { if (from[i].bits.present) { union PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT); spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); union PML * pdp_out = mmu_map_from_physical(newPage); memset(pdp_out, 0, 512 * sizeof(union PML)); pml4_out[i].raw = (newPage) | USER_PML_ACCESS; /* Copy the PDs */ for (size_t j = 0; j < 512; ++j) { if (pdp_in[j].bits.present) { union PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); union PML * pd_out = mmu_map_from_physical(newPage); memset(pd_out, 0, 512 * sizeof(union PML)); pdp_out[j].raw = (newPage) | USER_PML_ACCESS; /* Now copy the PTs */ for (size_t k = 0; k < 512; ++k) { if (pd_in[k].bits.present) { union PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); spin_lock(frame_alloc_lock); uintptr_t newPage = mmu_first_frame() << PAGE_SHIFT; mmu_frame_set(newPage); spin_unlock(frame_alloc_lock); union PML * pt_out = mmu_map_from_physical(newPage); memset(pt_out, 0, 512 * sizeof(union PML)); pd_out[k].raw = (newPage) | USER_PML_ACCESS; /* Now, finally, copy pages */ for (size_t l = 0; l < 512; ++l) { uintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT)); if (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue; if (pt_in[l].bits.present) { if (pt_in[l].bits.user) { copy_page_maybe(pt_in, pt_out, l, address); } else { /* If it's not a user page, just copy directly */ pt_out[l].raw = pt_in[l].raw; } } /* Else, mmap'd files? */ } } } } } } } return pml4_out; } /** * @brief Allocate one physical page. * * @returns a frame index, not an address */ uintptr_t mmu_allocate_a_frame(void) { spin_lock(frame_alloc_lock); uintptr_t index = mmu_first_frame(); mmu_frame_set(index << PAGE_SHIFT); spin_unlock(frame_alloc_lock); return index; } /** * @brief Allocate a number of contiguous physical pages. * * @returns a frame index, not an address */ uintptr_t mmu_allocate_n_frames(int n) { spin_lock(frame_alloc_lock); uintptr_t index = mmu_first_n_frames(n); for (int i = 0; i < n; ++i) { mmu_frame_set((index+i) << PAGE_SHIFT); } spin_unlock(frame_alloc_lock); return index; } /** * @brief Scans a directory to calculate how many user pages are in use. * * Calculates how many pages a userspace application has mapped, between * its general memory space and stack. Excludes shared mappings, such * as SHM or mapped devices. * * TODO: This can probably be reduced to check a smaller range, but as we * currently stick the user stack at the top of the low half of the * address space we just scan everything and exclude shared memory... * * @param from Top-level page directory to scan. */ size_t mmu_count_user(union PML * from) { size_t out = 0; for (size_t i = 0; i < 256; ++i) { if (from[i].bits.present) { out++; union PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT); for (size_t j = 0; j < 512; ++j) { if (pdp_in[j].bits.present) { out++; union PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); for (size_t k = 0; k < 512; ++k) { if (pd_in[k].bits.present) { out++; union PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); for (size_t l = 0; l < 512; ++l) { /* Calculate final address to skip SHM */ uintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT)); if (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue; if (pt_in[l].bits.present) { if (pt_in[l].bits.user) { out++; } } } } } } } } } return out; } /** * @brief Scans a directory to calculate how many shared memory pages are in use. * * At the moment, we only ever map shared pages to a specific region, so we just figure * out how many present pages are in that region and that's the answer. * * @param from Top-level page directory to scan. */ size_t mmu_count_shm(union PML * from) { size_t out = 0; for (size_t i = 0; i < 256; ++i) { if (from[i].bits.present) { union PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT); for (size_t j = 0; j < 512; ++j) { if (pdp_in[j].bits.present) { union PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); for (size_t k = 0; k < 512; ++k) { if (pd_in[k].bits.present) { union PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); for (size_t l = 0; l < 512; ++l) { /* Calculate final address to skip SHM */ uintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT)); if (address < USER_DEVICE_MAP || address > USER_SHM_HIGH) continue; if (pt_in[l].bits.present) { if (pt_in[l].bits.user) { out++; } } } } } } } } } return out; } /** * @brief Return the total amount of usable memory. * * @returns the total amount of usable memory in KiB. */ size_t mmu_total_memory(void) { return total_memory; } /** * @brief Return the amount of used memory. * * Calculates the number of pages currently marked as allocated. * Multiplies it by 4 because pages are 4KiB. * * @returns the amount of memory in use in KiB. */ size_t mmu_used_memory(void) { size_t ret = 0; size_t i, j; for (i = 0; i < INDEX_FROM_BIT(nframes); ++i) { for (j = 0; j < 32; ++j) { uint32_t testFrame = (uint32_t)0x1 << j; if (frames[i] & testFrame) { ret++; } } } return ret * 4 - unavailable_memory; } /** * @brief Relinquish pages owned by a top-level directory. * * Frees the underlying pages for a page directory within the lower (user) region. * Does not free kernel pages, as those are generally shared in the lower region. * * @param from Virtual pointer to top-level directory. */ void mmu_free(union PML * from) { if (!from) { printf("can't clear NULL directory\n"); return; } spin_lock(frame_alloc_lock); for (size_t i = 0; i < 256; ++i) { if (from[i].bits.present) { union PML * pdp_in = mmu_map_from_physical((uintptr_t)from[i].bits.page << PAGE_SHIFT); for (size_t j = 0; j < 512; ++j) { if (pdp_in[j].bits.present) { union PML * pd_in = mmu_map_from_physical((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); for (size_t k = 0; k < 512; ++k) { if (pd_in[k].bits.present) { union PML * pt_in = mmu_map_from_physical((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); for (size_t l = 0; l < 512; ++l) { uintptr_t address = ((i << (9 * 3 + 12)) | (j << (9*2 + 12)) | (k << (9 + 12)) | (l << PAGE_SHIFT)); /* Do not free shared mappings; SHM subsystem does that for SHM, devices don't need it. */ if (address >= USER_DEVICE_MAP && address <= USER_SHM_HIGH) continue; if (pt_in[l].bits.present) { /* Free only user pages */ if (pt_in[l].bits.user) { free_page_maybe(pt_in,l,address); } } } mmu_frame_clear((uintptr_t)pd_in[k].bits.page << PAGE_SHIFT); } } mmu_frame_clear((uintptr_t)pdp_in[j].bits.page << PAGE_SHIFT); } } mmu_frame_clear((uintptr_t)from[i].bits.page << PAGE_SHIFT); } } mmu_frame_clear((((uintptr_t)from) & PHYS_MASK)); spin_unlock(frame_alloc_lock); } union PML * mmu_get_kernel_directory(void) { return mmu_map_from_physical((uintptr_t)&init_page_region[0]); } /** * @brief Switch the active page directory for this core. * * Generally called during task creation and switching to change * the active page directory of a core. Updates @c this_core->current_pml. * * x86-64: Loads a given PML into CR3. * * @param new_pml Either the physical address or the shadow mapping virtual address * of the new PML4 directory to switch into, general obtained from * a process struct; if NULL is passed, the initial kernel directory * will be used and no userspace mappings will be present. */ void mmu_set_directory(union PML * new_pml) { if (!new_pml) new_pml = mmu_map_from_physical((uintptr_t)&init_page_region[0]); this_core->current_pml = new_pml; asm volatile ( "movq %0, %%cr3" : : "r"((uintptr_t)new_pml & PHYS_MASK)); } /** * @brief Mark a virtual address's mappings as invalid in the TLB. * * Generally should be called when a mapping is relinquished, as this is what * the TLB caches, but is also called in a bunch of places where we're just mapping * new pages... * * @param addr Virtual address in the current address space to invalidate. */ void mmu_invalidate(uintptr_t addr) { asm volatile ( "invlpg (%0)" : : "r"(addr)); arch_tlb_shootdown(addr); } int mmu_get_page_deep(uintptr_t virtAddr, union PML ** pml4_out, union PML ** pdp_out, union PML ** pd_out, union PML ** pt_out) { /* This is all the same as x86, thankfully? */ uintptr_t realBits = virtAddr & CANONICAL_MASK; uintptr_t pageAddr = realBits >> PAGE_SHIFT; unsigned int pml4_entry = (pageAddr >> 27) & ENTRY_MASK; unsigned int pdp_entry = (pageAddr >> 18) & ENTRY_MASK; unsigned int pd_entry = (pageAddr >> 9) & ENTRY_MASK; unsigned int pt_entry = (pageAddr) & ENTRY_MASK; /* Zero all the outputs */ *pdp_out = NULL; *pd_out = NULL; *pt_out = NULL; spin_lock(frame_alloc_lock); union PML * root = this_core->current_pml; *pml4_out = (union PML *)&root[pml4_entry]; if (!root[pml4_entry].bits.present) goto _noentry; union PML * pdp = mmu_map_from_physical((uintptr_t)root[pml4_entry].bits.page << PAGE_SHIFT); *pdp_out = (union PML *)&pdp[pdp_entry]; if (!pdp[pdp_entry].bits.present) goto _noentry; union PML * pd = mmu_map_from_physical((uintptr_t)pdp[pdp_entry].bits.page << PAGE_SHIFT); *pd_out = (union PML *)&pd[pd_entry]; if (!pd[pd_entry].bits.present) goto _noentry; union PML * pt = mmu_map_from_physical((uintptr_t)pd[pd_entry].bits.page << PAGE_SHIFT); *pt_out = (union PML *)&pt[pt_entry]; spin_unlock(frame_alloc_lock); return 0; _noentry: spin_unlock(frame_alloc_lock); return 1; } static int maybe_release_directory(union PML * parent, union PML * child) { /* child points to one entry, to get the base, we can page align it */ union PML * table = (union PML *)((uintptr_t)child & PAGE_SIZE_MASK); /* Is everything in the table free? */ for (int i = 0; i < 512; ++i) { if (table[i].bits.present) return 0; } uintptr_t old_page = (parent->bits.page << PAGE_SHIFT); /* Then we can mark 'parent' as freed, clear the whole thing. */ parent->raw = 0; mmu_frame_clear(old_page); return 1; } void mmu_unmap_user(uintptr_t addr, size_t size) { for (uintptr_t a = addr; a < addr + size; a += PAGE_SIZE) { union PML * pml4, * pdp, * pd, * pt; if (a >= USER_DEVICE_MAP && a <= USER_SHM_HIGH) continue; if (mmu_get_page_deep(a, &pml4, &pdp, &pd, &pt)) continue; spin_lock(frame_alloc_lock); if (pt && pt->bits.present && pt->bits.user) { if (pt->bits.writable) { assert(mem_refcounts[pt->bits.page] == 0); mmu_frame_clear((uintptr_t)pt->bits.page << PAGE_SHIFT); } else if (refcount_dec(pt->bits.page) == 0) { mmu_frame_clear((uintptr_t)pt->bits.page << PAGE_SHIFT); } pt->bits.present = 0; pt->bits.writable = 0; if (maybe_release_directory(pd, pt)) { if (maybe_release_directory(pdp, pd)) { maybe_release_directory(pml4, pdp); } } mmu_invalidate(a); } spin_unlock(frame_alloc_lock); } } static char * heapStart = NULL; extern char end[]; /** * @brief Prepare virtual page mappings for use by the kernel. * * Called during early boot to switch from the loader/bootstrap mappings * to ones suitable for general use. Sets up the bitmap allocator, high * identity mapping, kernel heap, and various mid-level structures to * ensure that future kernelspace mappings apply to all kernel threads. * * @param memsize The maximum accessible physical address. * @param firstFreePage The address of the first frame the kernel may use for new allocations. */ void mmu_init(size_t memsize, uintptr_t firstFreePage) { this_core->current_pml = (union PML *)&init_page_region[0]; /** * Enable WP bit, which will cause kernel writes to * non-writable pages to trigger page faults. We use * this to perform COW mappings for user processes if * they passed an unmapped region to a system call, though * this should be handled by @see mmu_validate_user_pointer * before we get to that point... */ asm volatile ( "movq %%cr0, %%rax\n" "orq $0x10000, %%rax\n" "movq %%rax, %%cr0\n" : : : "rax"); /* Map the high base PDP */ init_page_region[0][511].raw = (uintptr_t)&high_base_pml | KERNEL_PML_ACCESS; init_page_region[0][510].raw = (uintptr_t)&heap_base_pml | KERNEL_PML_ACCESS; /* Identity map from -128GB in the boot PML using 2MiB pages */ for (size_t i = 0; i < 64; ++i) { high_base_pml[i].raw = (uintptr_t)&twom_high_pds[i] | KERNEL_PML_ACCESS; for (uintptr_t j = 0; j < 512; ++j) { twom_high_pds[i][j].raw = ((i << 30) + (j << 21)) | LARGE_PAGE_BIT | KERNEL_PML_ACCESS; } } /* Map low base PDP */ low_base_pmls[0][0].raw = (uintptr_t)&low_base_pmls[1] | USER_PML_ACCESS; /* How much memory do we need to map low for our *kernel* to fit? */ uintptr_t endPtr = ((uintptr_t)&end + PAGE_LOW_MASK) & PAGE_SIZE_MASK; /* How many pages does that need? */ size_t lowPages = endPtr >> PAGE_SHIFT; /* And how many 512-page blocks does that fit in? */ size_t pdCount = (lowPages + ENTRY_MASK) >> 9; for (size_t j = 0; j < pdCount; ++j) { low_base_pmls[1][j].raw = (uintptr_t)&low_base_pmls[2+j] | KERNEL_PML_ACCESS; for (int i = 0; i < 512; ++i) { low_base_pmls[2+j][i].raw = (uintptr_t)(LARGE_PAGE_SIZE * j + PAGE_SIZE * i) | KERNEL_PML_ACCESS; } } /* Unmap null */ low_base_pmls[2][0].raw = 0; /* Now map our new low base */ init_page_region[0][0].raw = (uintptr_t)&low_base_pmls[0] | USER_PML_ACCESS; /* Set up the page allocator bitmap... */ nframes = (memsize >> 12); size_t bytesOfFrames = INDEX_FROM_BIT(nframes * 8); bytesOfFrames = (bytesOfFrames + PAGE_LOW_MASK) & PAGE_SIZE_MASK; firstFreePage = (firstFreePage + PAGE_LOW_MASK) & PAGE_SIZE_MASK; size_t pagesOfFrames = bytesOfFrames >> 12; /* Set up heap map for that... */ heap_base_pml[0].raw = (uintptr_t)&heap_base_pd | KERNEL_PML_ACCESS; heap_base_pd[0].raw = (uintptr_t)&heap_base_pt[0] | KERNEL_PML_ACCESS; heap_base_pd[1].raw = (uintptr_t)&heap_base_pt[512] | KERNEL_PML_ACCESS; heap_base_pd[2].raw = (uintptr_t)&heap_base_pt[1024] | KERNEL_PML_ACCESS; if (pagesOfFrames > 512*3) { printf("Warning: Too much available memory for current setup. Need %zu pages to represent allocation bitmap.\n", pagesOfFrames); } for (size_t i = 0; i < pagesOfFrames; i++) { heap_base_pt[i].raw = (firstFreePage + (i << 12)) | KERNEL_PML_ACCESS; } asm volatile ("" : : : "memory"); this_core->current_pml = mmu_map_from_physical((uintptr_t)this_core->current_pml); asm volatile ("" : : : "memory"); /* We are now in the new stuff. */ frames = (void*)((uintptr_t)KERNEL_HEAP_START); memset((void*)frames, 0xFF, bytesOfFrames); extern void mboot_unmark_valid_memory(void); mboot_unmark_valid_memory(); /* Don't trust anything but our own bitmap... */ size_t unavail = 0, avail = 0; for (size_t i = 0; i < INDEX_FROM_BIT(nframes); ++i) { for (size_t j = 0; j < 32; ++j) { uint32_t testFrame = (uint32_t)0x1 << j; if (frames[i] & testFrame) { unavail++; } else { avail++; } } } total_memory = avail * 4; unavailable_memory = unavail * 4; /* Now mark everything up to (firstFreePage + bytesOfFrames) as in use */ for (uintptr_t i = 0; i < firstFreePage + bytesOfFrames; i += PAGE_SIZE) { mmu_frame_set(i); } heapStart = (char*)KERNEL_HEAP_START + bytesOfFrames; /* Then, uh, make a bunch of space for page counts? */ size_t size_of_refcounts = (nframes & PAGE_LOW_MASK) ? (nframes + PAGE_SIZE - (nframes & PAGE_LOW_MASK)) : nframes; mem_refcounts = sbrk(size_of_refcounts); memset(mem_refcounts, 0, size_of_refcounts); } /** * @brief Allocate space in the kernel virtual heap. * * Called by the kernel heap allocator to obtain space for new heap allocations. * * @warning Not to be confused with sys_sbrk * * @param bytes Bytes to allocate. Must be a multiple of PAGE_SIZE. * @returns The previous address of the break point, after which @p bytes may now be used. */ void * sbrk(size_t bytes) { if (!heapStart) { arch_fatal_prepare(); printf("sbrk: Called before heap was ready.\n"); arch_dump_traceback(); arch_fatal(); } if (!bytes) { /* Skip lock acquisition if we just wanted to know where the break was. */ return heapStart; } if (bytes & PAGE_LOW_MASK) { arch_fatal_prepare(); printf("sbrk: Size must be multiple of 4096, was %#zx\n", bytes); arch_dump_traceback(); arch_fatal(); } if (bytes > 0x1F00000) { arch_fatal_prepare(); printf("sbrk: Size must be within a reasonable bound, was %#zx\n", bytes); arch_dump_traceback(); arch_fatal(); } spin_lock(kheap_lock); void * out = heapStart; for (uintptr_t p = (uintptr_t)out; p < (uintptr_t)out + bytes; p += PAGE_SIZE) { union PML * page = mmu_get_page(p, MMU_GET_MAKE); mmu_frame_allocate(page, MMU_FLAG_WRITABLE | MMU_FLAG_KERNEL); } //memset(out, 0xAA, bytes); heapStart += bytes; spin_unlock(kheap_lock); return out; } static uintptr_t mmio_base_address = MMIO_BASE_START; /** * @brief Obtain a writethrough region mapped to the given physical address. * * For use by device drivers to obtain mappings suitable for MMIO accesses. Note that the * virtual address space for these mappings can not be reclaimed, so drivers should keep * them around or use the other MMU facilities to repurpose them. * * @param physical_address Physical memory offset of the destination MMIO space. * @param size Size of the requested space, which must be a multiple of PAGE_SIZE. * @returns a virtual address suitable for MMIO accesses. */ void * mmu_map_mmio_region(uintptr_t physical_address, size_t size) { if (size & PAGE_LOW_MASK) { arch_fatal_prepare(); printf("mmu_map_mmio_region: MMIO region size must be multiple of 4096 bytes, was %#zx.\n", size); arch_dump_traceback(); arch_fatal(); } spin_lock(mmio_space_lock); void * out = (void*)mmio_base_address; for (size_t i = 0; i < size; i += PAGE_SIZE) { union PML * p = mmu_get_page(mmio_base_address + i, MMU_GET_MAKE); mmu_frame_map_address(p, MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE | MMU_FLAG_NOCACHE | MMU_FLAG_WRITETHROUGH, physical_address + i); } mmio_base_address += size; spin_unlock(mmio_space_lock); return out; } static uintptr_t module_base_address = MODULE_BASE_START; /** * @brief Obtain space to load a module in the -2GiB region. * * This should really start immediately after the kernel, but we don't * yet load the kernel in the -2GiB region... it might also be worthwhile * to implement some ASLR here, especially given that we're loading * relocatable ELF object files and can stick them anywhere. * * @param size How much space to allocate, will be rounded up to page size. * @returns Start of the allocated address space. */ void * mmu_map_module(size_t size) { if (size & PAGE_LOW_MASK) { size += (PAGE_LOW_MASK + 1) - (size & PAGE_LOW_MASK); } spin_lock(module_space_lock); void * out = (void*)module_base_address; for (size_t i = 0; i < size; i += PAGE_SIZE) { union PML * p = mmu_get_page(module_base_address + i, MMU_GET_MAKE); mmu_frame_allocate(p, MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE); } module_base_address += size; spin_unlock(module_space_lock); return out; } /** * @brief Free pages allocated for kernel modules. * * This rather blindly unmaps pages. * * @param start_address Start of mapping to unmap. * @param size Size of mapping to unmap. */ void mmu_unmap_module(uintptr_t start_address, size_t size) { if ((size & PAGE_LOW_MASK) || (start_address & PAGE_LOW_MASK)) { arch_fatal_prepare(); printf("mmu_unmap_module start and size must be multiple of page size %#zx:%#zx.\n", start_address, size); arch_dump_traceback(); arch_fatal(); } spin_lock(module_space_lock); uintptr_t end_address = start_address + size; /* Unmap all pages we just allocated */ for (uintptr_t i = start_address; i < end_address; i += 0x1000) { union PML * p = mmu_get_page(i, 0); mmu_frame_clear(p->bits.page << 12); } /* Reset module base address if it was at the end, to avoid wasting address space */ if (end_address == module_base_address) { module_base_address = start_address; } spin_unlock(module_space_lock); } /** * @brief Swap a COW page for a writable copy. * * Examines @p address to determine if it is a pending * COW page that has been marked read-only. If it is, * it will be exchanged for a writable page. If it is * the last read-only reference to a page, it will be * marked writable without introducing a new backing page. * * @param address Virtual address that triggered the fault. * @returns 0 if this was a valid and completed COW operation, 1 otherwise. */ int mmu_copy_on_write(uintptr_t address) { union PML * page = mmu_get_page(address,0); /* Was this address pending a cow? */ if (!page->bits.cow_pending) { /* No, go back and trigger and a SIGSEGV */ return 1; } spin_lock(frame_alloc_lock); /* Is this the last reference to this page? */ uint8_t refs = refcount_dec(page->bits.page); if (refs == 0) { /* Then we can just mark it writable. */ page->bits.writable = 1; page->bits.cow_pending = 0; asm ("" ::: "memory"); mmu_invalidate(address); spin_unlock(frame_alloc_lock); return 0; } /* Allocate a new writable page */ uintptr_t faulting_frame = page->bits.page; uintptr_t fresh_frame = mmu_first_frame(); mmu_frame_set(fresh_frame << PAGE_SHIFT); /* Copy the read-only page into the new writable page */ char * page_in = mmu_map_from_physical(faulting_frame << PAGE_SHIFT); char * page_out = mmu_map_from_physical(fresh_frame << PAGE_SHIFT); memcpy(page_out, page_in, 4096); /* And swap out the page table entry. */ page->bits.page = fresh_frame; page->bits.writable = 1; page->bits.cow_pending = 0; spin_unlock(frame_alloc_lock); asm ("" ::: "memory"); mmu_invalidate(address); return 0; } /** * @brief Check if the current user process can access address space. * * Thoroughly examines page table entries to determine if a user process * can access the memory at @p addr through @p size bytes. * * @p flags can be set to @c MMU_PTR_NULL if @c NULL address should trigger * a failure, @c MMU_PTR_WRITE if the process must have write access. * * @param addr Address to start checking from. * @param size Size after @p addr to check. * @param flags Control what constitutes a failure. * @returns 0 on failure, 1 if process has access. */ int mmu_validate_user_pointer(const void * addr, size_t size, int flags) { if (addr == NULL && !(flags & MMU_PTR_NULL)) return 0; if (size > 0x800000000000) return 0; uintptr_t base = (uintptr_t)addr; uintptr_t end = size ? (base + (size - 1)) : base; /* Get start page, end page */ uintptr_t page_base = base >> 12; uintptr_t page_end = end >> 12; for (uintptr_t page = page_base; page <= page_end; ++page) { if ((page & 0xffff800000000) != 0 && (page & 0xffff800000000) != 0xffff800000000) return 0; union PML * page_entry = mmu_get_page_other(this_core->current_process->thread.page_directory->directory, page << 12); if (!page_entry) return 0; if (!page_entry->bits.present) return 0; if (!page_entry->bits.user) return 0; if (!page_entry->bits.writable && (flags & MMU_PTR_WRITE)) { if (mmu_copy_on_write((uintptr_t)(page << 12))) return 0; } } return 1; } ================================================ FILE: kernel/arch/x86_64/pic.c ================================================ /** * @file kernel/arch/x86_64/pic.c * @brief Legacy PIC support. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include /* Programmable interrupt controller */ #define PIC1 0x20 #define PIC1_COMMAND PIC1 #define PIC1_OFFSET 0x20 #define PIC1_DATA (PIC1+1) #define PIC2 0xA0 #define PIC2_COMMAND PIC2 #define PIC2_OFFSET 0x28 #define PIC2_DATA (PIC2+1) #define PIC_EOI 0x20 #define ICW1_ICW4 0x01 #define ICW1_INIT 0x10 #define PIC_WAIT() \ do { \ /* May be fragile */ \ asm volatile("jmp 1f\n\t" \ "1:\n\t" \ " jmp 2f\n\t" \ "2:"); \ } while (0) static void irq_remap(void) { /* Cascade initialization */ outportb(PIC1_COMMAND, ICW1_INIT|ICW1_ICW4); PIC_WAIT(); outportb(PIC2_COMMAND, ICW1_INIT|ICW1_ICW4); PIC_WAIT(); /* Remap */ outportb(PIC1_DATA, PIC1_OFFSET); PIC_WAIT(); outportb(PIC2_DATA, PIC2_OFFSET); PIC_WAIT(); /* Cascade identity with slave PIC at IRQ2 */ outportb(PIC1_DATA, 0x04); PIC_WAIT(); outportb(PIC2_DATA, 0x02); PIC_WAIT(); /* Request 8086 mode on each PIC */ outportb(PIC1_DATA, 0x01); PIC_WAIT(); outportb(PIC2_DATA, 0x01); PIC_WAIT(); } void irq_ack(size_t irq_no) { if (irq_no >= 8) { outportb(PIC2_COMMAND, PIC_EOI); } outportb(PIC1_COMMAND, PIC_EOI); } void pic_initialize(void) { irq_remap(); } ================================================ FILE: kernel/arch/x86_64/pit.c ================================================ /** * @file kernel/arch/x86_64/pit.c * @author K. Lange * @brief Legacy x86 Programmable Interrupt Timer * * The PIT is used as a fallback preempt source if the LAPIC can * not be configured. The preempt signal is 100Hz. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2024 K. Lange */ #include #include #include #include #include /* Programmable interval timer */ #define PIT_A 0x40 #define PIT_B 0x41 #define PIT_C 0x42 #define PIT_CONTROL 0x43 #define PIT_MASK 0xFF #define PIT_SCALE 1193180 #define PIT_SET 0x34 #define TIMER_IRQ 0 #define RESYNC_TIME 1 /** * @brief Set the phase of the PIT in Hz. * * @param hz Ticks per second. */ static void pit_set_timer_phase(long hz) { long divisor = PIT_SCALE / hz; outportb(PIT_CONTROL, PIT_SET); outportb(PIT_A, divisor & PIT_MASK); outportb(PIT_A, (divisor >> 8) & PIT_MASK); } /** * @brief Interrupt handler for the PIT. */ int pit_interrupt(struct regs *r) { extern void arch_update_clock(void); arch_update_clock(); irq_ack(0); if (r->cs == 0x08) return 1; switch_task(1); return 1; } /** * @brief Install an interrupt handler for, and turn on, the PIT. */ void pit_initialize(void) { irq_install_handler(TIMER_IRQ, pit_interrupt, "pit timer"); /* ELCR? */ uint8_t val = inportb(0x4D1); outportb(0x4D1, val | (1 << (10-8)) | (1 << (11-8))); /* Enable PIT */ pit_set_timer_phase(100); } ================================================ FILE: kernel/arch/x86_64/ports.c ================================================ /** * @file kernel/arch/x86_64/ports.c * @brief Port I/O methods for x86-64. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include unsigned short inports(unsigned short _port) { unsigned short rv; asm volatile ("inw %1, %0" : "=a" (rv) : "dN" (_port)); return rv; } void outports(unsigned short _port, unsigned short _data) { asm volatile ("outw %1, %0" : : "dN" (_port), "a" (_data)); } unsigned int inportl(unsigned short _port) { unsigned int rv; asm volatile ("inl %%dx, %%eax" : "=a" (rv) : "dN" (_port)); return rv; } void outportl(unsigned short _port, unsigned int _data) { asm volatile ("outl %%eax, %%dx" : : "dN" (_port), "a" (_data)); } unsigned char inportb(unsigned short _port) { unsigned char rv; asm volatile ("inb %1, %0" : "=a" (rv) : "dN" (_port)); return rv; } void outportb(unsigned short _port, unsigned char _data) { asm volatile ("outb %1, %0" : : "dN" (_port), "a" (_data)); } void outportsm(unsigned short port, unsigned char * data, unsigned long size) { asm volatile ("rep outsw" : "+S" (data), "+c" (size) : "d" (port)); } void inportsm(unsigned short port, unsigned char * data, unsigned long size) { asm volatile ("rep insw" : "+D" (data), "+c" (size) : "d" (port) : "memory"); } ================================================ FILE: kernel/arch/x86_64/ps2hid.c ================================================ /** * @file kernel/arch/x86_64/ps2mouse.c * @brief PC PS/2 input device driver * * This is the slightly less terrible merged PS/2 mouse+keyboard driver. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #define PACKETS_IN_PIPE 1024 #define DISCARD_POINT 32 #define KEYBOARD_IRQ 1 #define MOUSE_IRQ 12 #define PS2_DATA 0x60 #define PS2_STATUS 0x64 #define PS2_COMMAND 0x64 #define MOUSE_WRITE 0xD4 #define MOUSE_V_BIT 0x08 #define PS2_PORT1_IRQ 0x01 #define PS2_PORT2_IRQ 0x02 #define PS2_PORT1_TLATE 0x40 #define PS2_READ_CONFIG 0x20 #define PS2_WRITE_CONFIG 0x60 #define PS2_DISABLE_PORT2 0xA7 #define PS2_ENABLE_PORT2 0xA8 #define PS2_DISABLE_PORT1 0xAD #define PS2_ENABLE_PORT1 0xAE #define MOUSE_SET_REMOTE 0xF0 #define MOUSE_DEVICE_ID 0xF2 #define MOUSE_SAMPLE_RATE 0xF3 #define MOUSE_DATA_ON 0xF4 #define MOUSE_DATA_OFF 0xF5 #define MOUSE_SET_DEFAULTS 0xF6 #define MOUSE_DEFAULT 0 #define MOUSE_SCROLLWHEEL 1 #define MOUSE_BUTTONS 2 #define KBD_SET_SCANCODE 0xF0 static uint8_t mouse_cycle = 0; static uint8_t mouse_byte[4]; static int8_t mouse_mode = MOUSE_DEFAULT; static fs_node_t * mouse_pipe; static fs_node_t * keyboard_pipe; void (*ps2_mouse_alternate)(uint8_t) = NULL; /** * @brief Wait until the PS/2 controller's input buffer is clear. * * Use this before WRITING to the controller. */ static int ps2_wait_input(void) { uint64_t timeout = 100000UL; while (--timeout) { if (!(inportb(PS2_STATUS) & (1 << 1))) return 0; } return 1; } /** * @brief Wait until the PS/2 controller's output buffer is filled. * * Use this before READING from the controller. */ static int ps2_wait_output(void) { uint64_t timeout = 100000UL; while (--timeout) { if (inportb(PS2_STATUS) & (1 << 0)) return 0; } return 1; } /** * @brief Send a command with no response or argument. */ static void ps2_command(uint8_t cmdbyte) { ps2_wait_input(); outportb(PS2_COMMAND, cmdbyte); } /** * @brief Send a command and get the reply. */ static uint8_t ps2_command_response(uint8_t cmdbyte) { ps2_wait_input(); outportb(PS2_COMMAND, cmdbyte); ps2_wait_output(); return inportb(PS2_DATA); } /** * @brief Send a command with an argument and no reply. */ static void ps2_command_arg(uint8_t cmdbyte, uint8_t arg) { ps2_wait_input(); outportb(PS2_COMMAND, cmdbyte); ps2_wait_input(); outportb(PS2_DATA, arg); } /** * @brief Write to the aux port. */ static uint8_t mouse_write(uint8_t write) { ps2_command_arg(MOUSE_WRITE, write); ps2_wait_output(); return inportb(PS2_DATA); } /** * @brief Read generic response byte */ static uint8_t ps2_read_byte(void) { ps2_wait_output(); return inportb(PS2_DATA); } /** * @brief Write to the primary port. */ static uint8_t kbd_write(uint8_t write) { ps2_wait_input(); outportb(PS2_DATA, write); ps2_wait_output(); return inportb(PS2_DATA); } /** * @brief Process a completed mouse packet. * * Assembles a mouse_device_packet_t from the data we got from * the PS/2 device and forwards it to the pipe to be read by * userspace; if the pipe is full we discard old bytes first. */ static void finish_packet(void) { mouse_cycle = 0; /* We now have a full mouse packet ready to use */ mouse_device_packet_t packet; packet.magic = MOUSE_MAGIC; int x = mouse_byte[1]; int y = mouse_byte[2]; if (x && mouse_byte[0] & (1 << 4)) { /* Sign bit */ x = x - 0x100; } if (y && mouse_byte[0] & (1 << 5)) { /* Sign bit */ y = y - 0x100; } if (mouse_byte[0] & (1 << 6) || mouse_byte[0] & (1 << 7)) { /* Overflow */ x = 0; y = 0; } packet.x_difference = x; packet.y_difference = y; packet.buttons = 0; if (mouse_byte[0] & 0x01) { packet.buttons |= LEFT_CLICK; } if (mouse_byte[0] & 0x02) { packet.buttons |= RIGHT_CLICK; } if (mouse_byte[0] & 0x04) { packet.buttons |= MIDDLE_CLICK; } if (mouse_mode == MOUSE_SCROLLWHEEL && mouse_byte[3]) { if ((int8_t)mouse_byte[3] > 0) { packet.buttons |= MOUSE_SCROLL_DOWN; } else if ((int8_t)mouse_byte[3] < 0) { packet.buttons |= MOUSE_SCROLL_UP; } } mouse_device_packet_t bitbucket; while (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) { read_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket); } write_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet); } /** * @brief Read one byte from the mouse. */ static void ps2_mouse_handle(uint8_t data_byte) { if (ps2_mouse_alternate) { ps2_mouse_alternate(data_byte); } else { int8_t mouse_in = data_byte; switch (mouse_cycle) { case 0: mouse_byte[0] = mouse_in; if (!(mouse_in & MOUSE_V_BIT)) break; ++mouse_cycle; break; case 1: mouse_byte[1] = mouse_in; ++mouse_cycle; break; case 2: mouse_byte[2] = mouse_in; if (mouse_mode == MOUSE_SCROLLWHEEL || mouse_mode == MOUSE_BUTTONS) { ++mouse_cycle; break; } finish_packet(); break; case 3: mouse_byte[3] = mouse_in; finish_packet(); break; } } } static int ioctl_mouse(fs_node_t * node, unsigned long request, void * argp) { if (request == 1) { mouse_cycle = 0; return 0; } return -1; } /** * @brief Read one byte from the keyboard. * * We give userspace the keyboard scancodes directly, and libtoaru_kbd * handles translation to a more usable format. This is probably not * the best way to do this... */ static void ps2_kbd_handle(uint8_t data_byte) { write_fs(keyboard_pipe, 0, 1, (uint8_t []){data_byte}); } /** * @brief Shared handler that does some magic that probably only works in QEMU. * * The general idea behind this shared handler is that QEMU is "broken" * and introduces a race that shouldn't be possible on real hardware? * We can get an interrupt but the byte we get out of the port is * for the other device. This makes playing Quake very hard because * our keyboard our mouse devices get garbage when we're doing both * at once! Thankfully, QEMU supports the status bit for checking * if there is mouse data, and if we prevent any data from coming * in from either port (by disabling both) while checking both * the status and the data port, we can use that as a lock and get * an "atomic" read that tells us which thing the data come from. */ static int shared_handler(struct regs * r) { /* Disable both ports */ ps2_command(PS2_DISABLE_PORT1); ps2_command(PS2_DISABLE_PORT2); /* Read the status and data */ uint8_t status = inportb(PS2_STATUS); uint8_t data_byte = inportb(PS2_DATA); /* Re-enable both */ ps2_command(PS2_ENABLE_PORT1); ps2_command(PS2_ENABLE_PORT2); irq_ack(r->int_no-32); if (!(status & 0x01)) return 1; if (!(status & 0x20)) { ps2_kbd_handle(data_byte); } else if (status & 0x21) { ps2_mouse_handle(data_byte); } return 1; } /** * @brief IRQ1 handler. */ static int keyboard_handler(struct regs *r) { uint8_t data_byte = inportb(PS2_DATA); irq_ack(KEYBOARD_IRQ); ps2_kbd_handle(data_byte); return 1; } /** * @brief IRQ12 handler. */ static int mouse_handler(struct regs *r) { uint8_t data_byte = inportb(PS2_DATA); irq_ack(MOUSE_IRQ); ps2_mouse_handle(data_byte); return 1; } /** * @brief Initialze i8042/AIP PS/2 controller. */ void ps2hid_install(void) { uint8_t status, result; mouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE); mouse_pipe->flags = FS_CHARDEVICE; mouse_pipe->ioctl = ioctl_mouse; vfs_mount("/dev/mouse", mouse_pipe, "ps2-mouse", ""); keyboard_pipe = make_pipe(128); keyboard_pipe->flags = FS_CHARDEVICE; vfs_mount("/dev/kbd", keyboard_pipe, "ps2-kbd", ""); /* Disable both ports. */ ps2_command(PS2_DISABLE_PORT1); ps2_command(PS2_DISABLE_PORT2); /* Clear the input buffer. */ size_t timeout = 1024; /* Can't imagine a buffer with more than that being full... */ while ((inportb(PS2_STATUS) & 1) && timeout > 0) { timeout--; inportb(PS2_DATA); } if (timeout == 0) { printf("ps2hid: probably don't actually have PS/2.\n"); return; } /* Enable interrupt lines, enable translation. */ status = ps2_command_response(PS2_READ_CONFIG); status |= (PS2_PORT1_IRQ | PS2_PORT2_IRQ | PS2_PORT1_TLATE); ps2_command_arg(PS2_WRITE_CONFIG, status); /* Re-enable ports */ ps2_command(PS2_ENABLE_PORT1); ps2_command(PS2_ENABLE_PORT2); /* Set scancode mode to 2... which then gives us 1 with translation... */ kbd_write(KBD_SET_SCANCODE); kbd_write(2); /* Now we'll configure the mouse... */ mouse_write(MOUSE_SET_DEFAULTS); mouse_write(MOUSE_DATA_ON); /* Try to enable scroll wheel (but not buttons) */ if (!args_present("nomousescroll")) { mouse_write(MOUSE_DEVICE_ID); ps2_read_byte(); /* Ignore response */ mouse_write(MOUSE_SAMPLE_RATE); mouse_write(200); mouse_write(MOUSE_SAMPLE_RATE); mouse_write(100); mouse_write(MOUSE_SAMPLE_RATE); mouse_write(80); mouse_write(MOUSE_DEVICE_ID); result = ps2_read_byte(); if (result == 3) { mouse_mode = MOUSE_SCROLLWHEEL; } } if (args_present("sharedps2")) { irq_install_handler(KEYBOARD_IRQ, shared_handler, "ps2hid"); irq_install_handler(MOUSE_IRQ, shared_handler, "ps2hid"); } else { irq_install_handler(KEYBOARD_IRQ, keyboard_handler, "ps2hid"); irq_install_handler(MOUSE_IRQ, mouse_handler, "ps2hid"); } } ================================================ FILE: kernel/arch/x86_64/serial.c ================================================ /** * @file kernel/arch/x86_64/serial.c * @brief PC serial port driver. * * Attaches serial ports to TTY interfaces. Serial input processing * happens in a kernel tasklet so that blocking is handled smoothly. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #define SERIAL_PORT_A 0x3F8 #define SERIAL_PORT_B 0x2F8 #define SERIAL_PORT_C 0x3E8 #define SERIAL_PORT_D 0x2E8 #define SERIAL_IRQ_AC 4 #define SERIAL_IRQ_BD 3 struct serial_port_map { int port; pty_t * pty; int index; tcflag_t cflags; }; static struct serial_port_map serial_ports[4] = { { SERIAL_PORT_A, NULL, 0, 0 }, { SERIAL_PORT_B, NULL, 1, 0 }, { SERIAL_PORT_C, NULL, 2, 0 }, { SERIAL_PORT_D, NULL, 3, 0 }, }; static struct serial_port_map * map_entry_for_port(int port) { switch (port) { case SERIAL_PORT_A: return &serial_ports[0]; case SERIAL_PORT_B: return &serial_ports[1]; case SERIAL_PORT_C: return &serial_ports[2]; case SERIAL_PORT_D: return &serial_ports[3]; } __builtin_unreachable(); } static pty_t ** pty_for_port(int port) { return &map_entry_for_port(port)->pty; } static int serial_rcvd(int device) { return inportb(device + 5) & 1; } static char serial_recv(int device) { while (serial_rcvd(device) == 0) switch_task(1); return inportb(device); } static int serial_transmit_empty(int device) { return inportb(device + 5) & 0x20; } static void serial_send(int device, char out) { while (serial_transmit_empty(device) == 0) switch_task(1); outportb(device, out); } static process_t * serial_ac_handler = NULL; static process_t * serial_bd_handler = NULL; static void process_serial(void * argp) { int portBase = (uintptr_t)argp; int _did_something; while (1) { unsigned long s, ss; relative_time(1, 0, &s, &ss); sleep_until((process_t *)this_core->current_process, s, ss); switch_task(0); _did_something = 1; while (_did_something) { _did_something = 0; int port_a_status = inportb(portBase + 5); int port_c_status = inportb(portBase - 0x10 + 5); if (port_a_status != 0xFF && (port_a_status & 1)) { char ch = inportb(portBase); pty_t * pty = *pty_for_port(portBase); tty_input_process(pty, ch); _did_something = 1; } if (port_c_status != 0xFF && (port_c_status & 1)) { char ch = inportb(portBase - 0x10); pty_t * pty = *pty_for_port(portBase - 0x10); tty_input_process(pty, ch); _did_something = 1; } } } } int serial_handler_ac(struct regs *r) { irq_ack(SERIAL_IRQ_AC); make_process_ready(serial_ac_handler); return 1; } int serial_handler_bd(struct regs *r) { irq_ack(SERIAL_IRQ_BD); make_process_ready(serial_bd_handler); return 1; } #define BASE 115200 #define D(n) { B ## n, BASE / n } static struct divisor { speed_t baud; uint16_t div; } divisors[] = { { B0, 0 }, D(50), D(75), D(110), { B134, BASE * 10 / 1345 }, D(150), D(200), D(300), D(600), D(1200), D(1800), D(2400), D(4800), D(9600), D(19200), D(38400), D(57600), D(115200), }; #undef D static void serial_enable(int port, tcflag_t cflags) { outportb(port + 1, 0x00); /* Disable interrupts */ outportb(port + 3, 0x80); /* Enable divisor mode */ uint16_t divisor = 0; for (size_t i = 0; i < sizeof(divisors) / sizeof(*divisors); ++i) { if ((cflags & CBAUD) == divisors[i].baud) { divisor = divisors[i].div; break; } } outportb(port + 0, divisor & 0xFF); /* Div Low */ outportb(port + 1, divisor >> 8); /* Div High */ uint8_t line_ctl = 0; if (cflags & PARENB) { line_ctl |= (1 << 3); /* Enable parity */ if (!(cflags & PARODD)) line_ctl |= (1 << 4); /* Even parity */ } /* Size */ switch (cflags & CSIZE) { case CS5: line_ctl |= 0; break; case CS6: line_ctl |= 1; break; case CS7: line_ctl |= 2; break; case CS8: line_ctl |= 3; break; } outportb(port + 3, line_ctl); /* set line mode */ outportb(port + 2, 0xC7); /* Enable FIFO and clear */ outportb(port + 4, 0x0B); /* Enable interrupts */ outportb(port + 1, 0x01); /* Enable interrupts */ } static int have_installed_ac = 0; static int have_installed_bd = 0; static void serial_write_out(pty_t * pty, uint8_t c) { struct serial_port_map * me = pty->_private; if (pty->tios.c_cflag != me->cflags) { me->cflags = pty->tios.c_cflag; serial_enable(me->port, pty->tios.c_cflag); } serial_send(me->port, c); } #define DEV_PATH "/dev/ttyS" static void serial_fill_name(pty_t * pty, char * name) { snprintf(name, 100, DEV_PATH "%d", ((struct serial_port_map *)pty->_private)->index); } static fs_node_t * serial_device_create(int port) { pty_t * pty = pty_new(NULL, 0); map_entry_for_port(port)->pty = pty; pty->_private = map_entry_for_port(port); pty->write_out = serial_write_out; pty->fill_name = serial_fill_name; serial_enable(port, pty->tios.c_cflag); if (port == SERIAL_PORT_A || port == SERIAL_PORT_C) { if (!have_installed_ac) { irq_install_handler(SERIAL_IRQ_AC, serial_handler_ac, "serial ac"); have_installed_ac = 1; } } else { if (!have_installed_bd) { irq_install_handler(SERIAL_IRQ_BD, serial_handler_bd, "serial bd"); have_installed_bd = 1; } } pty->slave->gid = 2; /* dialout group */ pty->slave->mask = 0660; return pty->slave; } void serial_initialize(void) { serial_ac_handler = spawn_worker_thread(process_serial, "[serial ac]", (void*)(uintptr_t)SERIAL_PORT_A); serial_bd_handler = spawn_worker_thread(process_serial, "[serial bd]", (void*)(uintptr_t)SERIAL_PORT_B); fs_node_t * ttyS0 = serial_device_create(SERIAL_PORT_A); vfs_mount(DEV_PATH "0", ttyS0, "pc-serial", "a"); fs_node_t * ttyS1 = serial_device_create(SERIAL_PORT_B); vfs_mount(DEV_PATH "1", ttyS1, "pc-serial", "b"); fs_node_t * ttyS2 = serial_device_create(SERIAL_PORT_C); vfs_mount(DEV_PATH "2", ttyS2, "pc-serial", "c"); fs_node_t * ttyS3 = serial_device_create(SERIAL_PORT_D); vfs_mount(DEV_PATH "3", ttyS3, "pc-serial", "d"); } ================================================ FILE: kernel/arch/x86_64/smp.c ================================================ /** * @file kernel/arch/x86_64/smp.c * @brief Multi-processor Support for x86-64. * * Locates and bootstraps APs using ACPI MADT tables. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include __attribute__((used)) __attribute__((naked)) static void __ap_bootstrap(void) { asm volatile ( ".section .shit\n" ".code16\n" ".org 0x0\n" ".global _ap_bootstrap_start\n" "_ap_bootstrap_start:\n" /* Enable PAE, paging */ "mov $0xA0, %%eax\n" "mov %%eax, %%cr4\n" /* Kernel base PML4 */ "mov $0x77777777, %%edx\n" "mov %%edx, %%cr3\n" /* Set LME */ "mov $0xc0000080, %%ecx\n" "rdmsr\n" "or $0x100, %%eax\n" "wrmsr\n" /* Enable long mode */ "mov $0x80000011, %%ebx\n" "mov %%ebx, %%cr0\n" /* Set up basic GDT */ "addr32 lgdtl %%cs:_ap_bootstrap_gdtp-_ap_bootstrap_start\n" /* Jump... */ "data32 jmp $0x08,$0x5A5A5A5A\n" ".global _ap_bootstrap_gdtp\n" ".align 16\n" "_ap_bootstrap_gdtp:\n" ".word 0\n" ".quad 0\n" ".global _ap_bootstrap_end\n" "_ap_bootstrap_end:\n" ".section .text\n" : : : "memory" ); } __attribute__((used)) __attribute__((naked)) static void __ap_bootstrap_landing(void) { asm volatile ( ".code64\n" ".align 16\n" ".global _ap_premain\n" "_ap_premain:\n" "mov $0x10, %%ax\n" "mov %%ax, %%ds\n" "mov %%ax, %%ss\n" "mov $0x33, %%ax\n" /* TSS offset in gdt */ "ltr %%ax\n" ".extern _ap_stack_base\n" "mov _ap_stack_base(%%rip),%%rsp\n" ".extern ap_main\n" "callq ap_main\n" : : : "memory" ); } extern char _ap_bootstrap_start[]; extern char _ap_bootstrap_end[]; extern char _ap_bootstrap_gdtp[]; extern char _ap_premain[]; extern size_t arch_cpu_mhz(void); extern void gdt_copy_to_trampoline(int ap, char * trampoline); extern void arch_set_core_base(uintptr_t base); extern void fpu_initialize(void); extern void idt_ap_install(void); extern void pat_initialize(void); extern process_t * spawn_kidle(int); extern union PML init_page_region[]; /** * @brief Read the timestamp counter. * * This is duplicated in a couple of places as it's a quick * inline wrapper for 'rdtsc'. */ static inline uint64_t read_tsc(void) { uint32_t lo, hi; asm volatile ( "rdtsc" : "=a"(lo), "=d"(hi) ); return ((uint64_t)hi << 32) | (uint64_t)lo; } /** * @brief Pause by looping on TSC. * * Used for AP startup. */ static void short_delay(unsigned long amount) { uint64_t clock = read_tsc(); while (read_tsc() < clock + amount * arch_cpu_mhz()); } static volatile int _ap_current = 0; /**< The AP we're currently starting up; shared between @c ap_main and @c smp_initialize */ static volatile int _ap_startup_flag = 0; /**< Simple lock, shared between @c ap_main and @c smp_initialize */ uintptr_t _ap_stack_base = 0; /**< Stack address for this AP to use on startup; used by @c __ap_boostrap */ uintptr_t lapic_final = 0; /**< MMIO region to use for APIC access. */ #define cpuid(in,a,b,c,d) do { asm volatile ("cpuid" : "=a"(a),"=b"(b),"=c"(c),"=d"(d) : "a"(in)); } while(0) /** * @brief Obtains processor name strings from cpuid * * We store the processor names for each core (they might be different...) * so we can display them nicely in /proc/cpuinfo */ void load_processor_info(void) { unsigned long a, b, unused; cpuid(0,unused,b,unused,unused); this_core->cpu_manufacturer = "Unknown"; if (b == 0x756e6547) { cpuid(1, a, b, unused, unused); this_core->cpu_manufacturer = "Intel"; this_core->cpu_model = (a >> 4) & 0x0F; this_core->cpu_family = (a >> 8) & 0x0F; } else if (b == 0x68747541) { cpuid(1, a, unused, unused, unused); this_core->cpu_manufacturer = "AMD"; this_core->cpu_model = (a >> 4) & 0x0F; this_core->cpu_family = (a >> 8) & 0x0F; } snprintf(processor_local_data[this_core->cpu_id].cpu_model_name, 20, "(unknown)"); /* See if we can get a long manufacturer strings */ cpuid(0x80000000, a, unused, unused, unused); if (a >= 0x80000004) { uint32_t brand[12]; cpuid(0x80000002, brand[0], brand[1], brand[2], brand[3]); cpuid(0x80000003, brand[4], brand[5], brand[6], brand[7]); cpuid(0x80000004, brand[8], brand[9], brand[10], brand[11]); memcpy(processor_local_data[this_core->cpu_id].cpu_model_name, brand, 48); } extern void syscall_entry(void); uint32_t efer_hi, efer_lo; asm volatile ("rdmsr" : "=d"(efer_hi), "=a"(efer_lo) : "c"(0xc0000080)); /* Read current EFER */ asm volatile ("wrmsr" : : "c"(0xc0000080), "d"(efer_hi), "a"(efer_lo | 1)); /* Enable SYSCALL/SYSRET in EFER */ asm volatile ("wrmsr" : : "c"(0xC0000081), "d"(0x1b0008), "a"(0)); /* Set segment bases in STAR */ asm volatile ("wrmsr" : : "c"(0xC0000082), /* Set SYSCALL entry point in LSTAR */ "d"((uintptr_t)&syscall_entry >> 32), "a"((uintptr_t)&syscall_entry & 0xFFFFffff)); asm volatile ("wrmsr" : : "c"(0xC0000084), "d"(0), "a"(0x700)); /* SFMASK: Direction flag, interrupt flag, trap flag are all cleared */ } static void lapic_timer_initialize(void) { /* Enable our spurious vector register */ *((volatile uint32_t*)(lapic_final + 0x0F0)) = 0x127; *((volatile uint32_t*)(lapic_final + 0x320)) = 0x7b; *((volatile uint32_t*)(lapic_final + 0x3e0)) = 1; /* Time our APIC timer against the TSC */ uint64_t before = arch_perf_timer(); *((volatile uint32_t*)(lapic_final + 0x380)) = 1000000; while (*((volatile uint32_t*)(lapic_final + 0x390))); uint64_t after = arch_perf_timer(); uint64_t ms = (after-before)/arch_cpu_mhz(); uint64_t target = 10000000000UL / ms; /* Enable our APIC timer to send periodic wakeup signals */ *((volatile uint32_t*)(lapic_final + 0x3e0)) = 1; *((volatile uint32_t*)(lapic_final + 0x320)) = 0x7b | 0x20000; *((volatile uint32_t*)(lapic_final + 0x380)) = target; } /** * @brief C entrypoint for APs, called by the bootstrap. * * After an AP has entered long mode, it jumps here, where * we do the rest of the core setup. */ void ap_main(void) { /* Set the GS base to point to our 'this_core' struct. */ arch_set_core_base((uintptr_t)&processor_local_data[_ap_current]); /* Safety check... * Make sure we're actually the core we think we are... */ uint32_t ebx, _unused; cpuid(0x1,_unused,ebx,_unused,_unused); if (this_core->lapic_id != (int)(ebx >> 24)) { printf("smp: lapic id does not match\n"); } /* lidt, initialize local FPU, set up page attributes */ idt_ap_install(); fpu_initialize(); pat_initialize(); /* Set our pml pointers */ this_core->current_pml = &init_page_region[0]; /* Spawn our kidle, make it our current process. */ this_core->kernel_idle_task = spawn_kidle(0); this_core->current_process = this_core->kernel_idle_task; /* Collect CPU name strings. */ load_processor_info(); /* Inform BSP it can continue. */ _ap_startup_flag = 1; lapic_timer_initialize(); /* Enter scheduler */ switch_next(); } /** * @brief MMIO write for LAPIC * @param addr Register address to access * @param value DWORD to write */ void lapic_write(size_t addr, uint32_t value) { *((volatile uint32_t*)(lapic_final + addr)) = value; asm volatile ("":::"memory"); } /** * @brief MMIO read for LAPIC * @param addr Register address to access * @return DWORD */ uint32_t lapic_read(size_t addr) { return *((volatile uint32_t*)(lapic_final + addr)); } /** * @brief Send an inter-processor interrupt. * * Sends an IPI and waits for the LAPIC to signal the IPI was sent. * * @param int The interrupt to send. * @param val Flags to control how the IPI should be delivered */ void lapic_send_ipi(int i, uint32_t val) { lapic_write(0x310, i << 24); lapic_write(0x300, val); do { asm volatile ("pause" : : : "memory"); } while (lapic_read(0x300) & (1 << 12)); } /** * @brief Quick dumb hex parser. * * Just to support acpi= command line flag for overriding * the scan address for ACPI tables... * * @param c String of hexadecimal characters, optionally prefixed with '0x' * @return Unsigned integer interpretation of @p c */ uintptr_t xtoi(const char * c) { uintptr_t out = 0; if (c[0] == '0' && c[1] == 'x') { c += 2; } while (*c) { out *= 0x10; if (*c >= '0' && *c <= '9') { out += (*c - '0'); } else if (*c >= 'a' && *c <= 'f') { out += (*c - 'a' + 0xa); } else if (*c >= 'A' && *c <= 'F') { out += (*c - 'A' + 0xa); } c++; } return out; } /** * @brief Called on main startup to initialize other cores. * * We always do this ourselves. We support a few different * bootloader conventions, and most of them don't support * starting up APs for us. */ void smp_initialize(void) { /* Locate ACPI tables */ uintptr_t scan = 0xE0000; uintptr_t scan_top = 0x100000; int good = 0; extern struct multiboot * mboot_struct; extern int mboot_is_2; if (mboot_is_2) { /* A multiboot2 loader should give us a "firmware table" address * that should allow us to find the RSDP. */ extern void * mboot2_find_tag(void * fromStruct, uint32_t type); /* First try for an RSDPv1 */ scan = (uintptr_t)mboot2_find_tag(mboot_struct, 14); /* If we didn't get one of those, try for an RSDPv2 */ if (!scan) scan = (uintptr_t)mboot2_find_tag(mboot_struct, 15); /* If we didn't get one of _those_, we should really be bailing here... */ /* Account for the tag header. */ scan += 8; scan_top = scan + 0x100000; } else if (mboot_struct->config_table) { /* * @warning This is specific to ToaruOS's native loader. * We steal the config_table entry in our EFI loader to pass the RSDP, * just like a multiboot2 loader would... */ scan = mboot_struct->config_table; scan_top = scan + 0x100000; } else if (args_present("acpi")) { /* If all else fails, you can provide the address yourself on the command line */ scan = xtoi(args_value("acpi")); scan_top = scan + 0x100000; } /* Look for it the RSDP */ for (; scan < scan_top; scan += 16) { char * _scan = mmu_map_from_physical(scan); if (_scan[0] == 'R' && _scan[1] == 'S' && _scan[2] == 'D' && _scan[3] == ' ' && _scan[4] == 'P' && _scan[5] == 'T' && _scan[6] == 'R') { good = 1; break; } } /* I don't know why we do this here... */ load_processor_info(); /* Did we still not find our table? */ if (!good) { dprintf("smp: No RSD PTR found\n"); goto _pit_fallback; } /* Map the ACPI RSDP */ struct rsdp_descriptor * rsdp = (struct rsdp_descriptor *)mmu_map_from_physical(scan); /* Validate the checksum */ uint8_t check = 0; uint8_t * tmp; for (tmp = (uint8_t *)rsdp; (uintptr_t)tmp < (uintptr_t)rsdp + sizeof(struct rsdp_descriptor); tmp++) { check += *tmp; } /* Did the checksum fail? */ if (check != 0 && !args_present("noacpichecksum")) { dprintf("smp: Bad checksum on RSDP (add 'noacpichecksum' to ignore this)\n"); goto _pit_fallback; /* bad checksum */ } /* Was SMP disabled by a commandline flag? */ if (args_present("nosmp")) goto _pit_fallback; /* Map the RSDT from the address given by the RSDP */ struct rsdt * rsdt = mmu_map_from_physical(rsdp->rsdt_address); int cores = 0; uintptr_t lapic_base = 0x0; for (unsigned int i = 0; i < (rsdt->header.length - 36) / 4; ++i) { uint8_t * table = mmu_map_from_physical(rsdt->pointers[i]); if (table[0] == 'A' && table[1] == 'P' && table[2] == 'I' && table[3] == 'C') { /* APIC table! Let's find some CPUs! */ struct madt * madt = (void*)table; lapic_base = madt->lapic_addr; for (uint8_t * entry = madt->entries; entry < table + madt->header.length; entry += entry[1]) { switch (entry[0]) { case 0: if (entry[4] & 0x01) { if (cores == 32) { /* TODO define this somewhere better */ printf("smp: too many cores\n"); goto _toomany; } processor_local_data[cores].cpu_id = cores; processor_local_data[cores].lapic_id = entry[3]; cores++; } break; /* TODO: Other entries */ } } } } _toomany: if (!lapic_base) goto _pit_fallback; /* Allocate a virtual address with which we can poke the lapic */ lapic_final = (uintptr_t)mmu_map_mmio_region(lapic_base, 0x1000); lapic_timer_initialize(); if (cores <= 1) return; /* Get a page we can backup the previous contents of the bootstrap target page to, as it probably has mmap crap in multiboot2 */ uintptr_t tmp_space = mmu_allocate_a_frame() << 12; memcpy(mmu_map_from_physical(tmp_space), mmu_map_from_physical(0x1000), 0x1000); *(uint32_t*)(&_ap_bootstrap_start[0xb]) = (uintptr_t)&init_page_region; *(uint32_t*)(&_ap_bootstrap_start[0x37]) = (uintptr_t)&_ap_premain; /* Map the bootstrap code */ memcpy(mmu_map_from_physical(0x1000), &_ap_bootstrap_start, (uintptr_t)&_ap_bootstrap_end - (uintptr_t)&_ap_bootstrap_start); for (int i = 1; i < cores; ++i) { _ap_startup_flag = 0; /* Set gdt pointer value */ gdt_copy_to_trampoline(i, (char*)mmu_map_from_physical(0x1000) + ((uintptr_t)&_ap_bootstrap_gdtp - (uintptr_t)&_ap_bootstrap_start)); /* Make an initial stack for this AP */ _ap_stack_base = (uintptr_t)valloc(KERNEL_STACK_SIZE)+ KERNEL_STACK_SIZE; _ap_current = i; /* Send INIT */ lapic_send_ipi(processor_local_data[i].lapic_id, 0x4500); short_delay(5000UL); /* Send SIPI */ lapic_send_ipi(processor_local_data[i].lapic_id, 0x4601); /* Wait for AP to signal it is ready before starting next AP */ do { asm volatile ("pause" : : : "memory"); } while (!_ap_startup_flag); processor_count++; } /* Copy data back */ memcpy(mmu_map_from_physical(0x1000), mmu_map_from_physical(tmp_space), 0x1000); mmu_frame_clear(tmp_space); dprintf("smp: enabled with %d cores\n", cores); return; _pit_fallback: dprintf("pit: falling back to pit as preempt source\n"); extern void pit_initialize(void); pit_initialize(); } /** * @brief Send a soft IPI to all other cores. * * This is called by the scheduler when a process enters the ready queue, * to give other CPUs a chance to pick it up before their timer interrupt * fires. This is a soft interrupt: It should be ignored by the receiving * cores if they are busy with other things - we only want it to wake up * the HLT in the kernel idle task. * * TODO We could make this more fine-grained and deliver only to processors * we think are ready, or to specific processors to aid in affinity? */ void arch_wakeup_others(void) { if (!lapic_final || processor_count < 2) return; /* Send broadcast IPI to others; this is a soft interrupt * that just nudges idle cores out of their HLT states. * It should be gentle enough that busy cores dont't care. */ lapic_send_ipi(0, 0x7E | (3 << 18)); } /** * @brief Trigger a TLB shootdown on other cores. * * XXX This is really dumb; we just send an IPI to everyone else * and they reload CR3... * * @param vaddr Should have the address to flush, but not actually used. */ void arch_tlb_shootdown(uintptr_t vaddr) { if (!lapic_final || processor_count < 2) return; /* * We should be checking if this address can be sensibly * mapped somewhere else before IPIing everyone... */ lapic_send_ipi(0, 0x7C | (3 << 18)); } ================================================ FILE: kernel/arch/x86_64/user.c ================================================ /** * @file kernel/arch/x86_64/user.c * @brief Various assembly snippets for jumping to usermode and back. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include /** * @brief Enter userspace. * * Called by process startup. * Does not return. * * @param entrypoint Address to "return" to in userspace. * @param argc Number of arguments to provide to the new process. * @param argv Argument array to pass to the new process; make sure this is user-accessible! * @param envp Environment strings array * @param stack Userspace stack address. */ void arch_enter_user(uintptr_t entrypoint, int argc, char * argv[], char * envp[], uintptr_t stack) { struct regs ret; ret.cs = 0x28 | 0x03; ret.ss = 0x20 | 0x03; ret.rip = entrypoint; ret.rflags = (1 << 21) | (1 << 9); ret.rsp = stack; update_process_times_on_exit(); asm volatile ( "pushq %0\n" "pushq %1\n" "pushq %2\n" "pushq %3\n" "pushq %4\n" "swapgs\n" "iretq" : : "m"(ret.ss), "m"(ret.rsp), "m"(ret.rflags), "m"(ret.cs), "m"(ret.rip), "D"(argc), "S"(argv), "d"(envp)); } static void _kill_it(void) { dprintf("core %d (pid=%d %s): invalid stack for signal return\n", this_core->cpu_id, this_core->current_process->id, this_core->current_process->name); task_exit(((128 + SIGSEGV) << 8) | SIGSEGV); } #define PUSH(stack, type, item) do { \ stack -= sizeof(type); \ if (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),MMU_PTR_WRITE)) \ _kill_it(); \ *((volatile type *) stack) = item; \ } while (0) #define POP(stack, type, item) do { \ if (!mmu_validate_user_pointer((void*)(uintptr_t)stack,sizeof(type),0)) \ _kill_it(); \ item = *((volatile type *) stack); \ stack += sizeof(type); \ } while (0) int arch_return_from_signal_handler(struct regs *r) { for (int i = 0; i < 64; ++i) { POP(r->rsp, uint64_t, this_core->current_process->thread.fp_regs[63-i]); } arch_restore_floating((process_t*)this_core->current_process); POP(r->rsp, sigset_t, this_core->current_process->blocked_signals); long originalSignal; POP(r->rsp, long, originalSignal); POP(r->rsp, long, this_core->current_process->interrupted_system_call); struct regs out; POP(r->rsp, struct regs, out); #define R(n) r-> n = out. n R(r15); R(r14); R(r13); R(r12); R(r11); R(r10); R(r9); R(r8); R(rbp); R(rdi); R(rsi); R(rdx); R(rcx); R(rbx); R(rax); R(rip); R(rsp); r->rflags = (out.rflags & 0xcd5) | (1 << 21) | (1 << 9) | ((r->rflags & (1 << 8)) ? (1 << 8) : 0); return originalSignal; } /** * @brief Enter a userspace signal handler. * * Similar to @c arch_enter_user but also setups up magic return addresses. * * Since signal handlers do to take complicated argument arrays, this only * supplies a @p signum argument. * * Does not return. * * @param entrypoint Userspace address of the signal handler, set by the process. * @param signum Signal number that caused this entry. */ void arch_enter_signal_handler(uintptr_t entrypoint, int signum, struct regs *r) { struct regs ret; ret.cs = 0x28 | 0x03; ret.ss = 0x20 | 0x03; ret.rip = entrypoint; ret.rflags = (1 << 21) | (1 << 9); ret.rsp = (r->rsp - 128) & 0xFFFFFFFFFFFFFFF0; /* ensure considerable alignment */ PUSH(ret.rsp, struct regs, *r); PUSH(ret.rsp, long, this_core->current_process->interrupted_system_call); this_core->current_process->interrupted_system_call = 0; PUSH(ret.rsp, long, signum); PUSH(ret.rsp, sigset_t, this_core->current_process->blocked_signals); struct signal_config * config = (struct signal_config*)&this_core->current_process->signals[signum]; this_core->current_process->blocked_signals |= config->mask | (config->flags & SA_NODEFER ? 0 : (1UL << signum)); arch_save_floating((process_t*)this_core->current_process); for (int i = 0; i < 64; ++i) { PUSH(ret.rsp, uint64_t, this_core->current_process->thread.fp_regs[i]); } PUSH(ret.rsp, uintptr_t, 0x516); update_process_times_on_exit(); asm volatile( "pushq %0\n" "pushq %1\n" "pushq %2\n" "pushq %3\n" "pushq %4\n" "swapgs\n" "iretq" : : "m"(ret.ss), "m"(ret.rsp), "m"(ret.rflags), "m"(ret.cs), "m"(ret.rip), "D"(signum)); __builtin_unreachable(); } /** * @brief Return from fork or clone. * * This is what we inject as the stored rip for a new thread, * so that it immediately returns from the system call. * * This is never called as a function, its address is stored * in the thread context of a new @c process_t. */ __attribute__((naked)) void arch_resume_user(void) { asm volatile ( "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" "add $16, %rsp\n" "swapgs\n" "iretq\n" ); __builtin_unreachable(); } /** * @brief Save FPU registers for this thread. */ void arch_restore_floating(process_t * proc) { asm volatile ("fxrstor (%0)" :: "r"(&proc->thread.fp_regs)); } /** * @brief Restore FPU registers for this thread. */ void arch_save_floating(process_t * proc) { asm volatile ("fxsave (%0)" :: "r"(&proc->thread.fp_regs)); } /** * @brief Called in a loop by kernel idle tasks. * * Turns on and waits for interrupts. * There is room for improvement here with other power states, * but HLT is "good enough" for us. */ void arch_pause(void) { asm volatile ( "sti\n" "hlt\n" "cli\n" ); } extern void lapic_send_ipi(int i, uint32_t val); /** * @brief Prepare for a fatal event by stopping all other cores. * * Sends an IPI to all other CPUs to tell them to immediately stop. * This causes an NMI (isr2), which disables interrupts and loops * on a hlt instruction. * * Ensures that we can then print tracebacks and do other complicated * things without having to mess with locks, and without other * processors causing further damage in the case of a fatal error. */ void arch_fatal_prepare(void) { for (int i = 0; i < processor_count; ++i) { if (i == this_core->cpu_id) continue; lapic_send_ipi(processor_local_data[i].lapic_id, 0x447D); } } /** * @brief Halt all processors, including this one. * @see arch_fatal_prepare */ void arch_fatal(void) { arch_fatal_prepare(); while (1) { asm volatile ( "cli\n" "hlt\n" ); } } /** * @brief Reboot the computer. * * This tries to do a "keyboard reset". We clear out the IDT * so that we can maybe triple fault, and then we try to use * the keyboard reset vector... if that doesn't work, * then returning from this and letting anything else happen * almost certainly will. */ long arch_reboot(void) { /* load a null page as an IDT */ uintptr_t frame = mmu_allocate_a_frame(); uintptr_t * idt = mmu_map_from_physical(frame << 12); memset(idt, 0, 0x1000); asm volatile ( "lidt (%0)" : : "r"(idt) ); uint8_t out = 0x02; while ((out & 0x02) != 0) { out = inportb(0x64); } outportb(0x64, 0xFE); /* Reset */ return 0; } /* Syscall parameter accessors */ void arch_syscall_return(struct regs * r, long retval) { r->rax = retval; } long arch_syscall_number(struct regs * r) { return (unsigned long)r->rax; } long arch_syscall_arg0(struct regs * r) { return r->rdi; } long arch_syscall_arg1(struct regs * r) { return r->rsi; } long arch_syscall_arg2(struct regs * r) { return r->rdx; } long arch_syscall_arg3(struct regs * r) { return r->r10; } long arch_syscall_arg4(struct regs * r) { return r->r8; } long arch_stack_pointer(struct regs * r) { return r->rsp; } long arch_user_ip(struct regs * r) { return r->rip; } ================================================ FILE: kernel/audio/snd.c ================================================ /** * @file kernel/audio/snd.c * @brief Gerow's Audio Subsystem for ToaruOS * * Simple generic mixer interface. Allows userspace to pipe audio data * to the kernel audio drivers and control volume knobs. * * Currently has the ability to mix several sound sources together. Could use * a /dev/mixer device to allow changing of audio settings. Also could use * the ability to change frequency and format for audio samples. Also doesn't * really support multiple devices despite the interface suggesting it might... * * @copyright * 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-2021 K. Lange * Copyright (C) 2015 Mike Gerow */ #include #include #include #include #include #include #include #include /* Utility macros */ #define N_ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0])) #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define SND_BUF_SIZE 0x4000 static ssize_t snd_dsp_write(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer); static int snd_dsp_ioctl(fs_node_t * node, unsigned long request, void * argp); static void snd_dsp_open(fs_node_t * node, unsigned int flags); static void snd_dsp_close(fs_node_t * node); static int snd_mixer_ioctl(fs_node_t * node, unsigned long request, void * argp); static void snd_mixer_open(fs_node_t * node, unsigned int flags); static void snd_mixer_close(fs_node_t * node); static spin_lock_t _devices_lock; static list_t _devices; static fs_node_t _dsp_fnode = { .name = "dsp", .device = &_devices, .mask = 0666, .flags = FS_CHARDEVICE, .ioctl = snd_dsp_ioctl, .write = snd_dsp_write, .open = snd_dsp_open, .close = snd_dsp_close, }; static fs_node_t _mixer_fnode = { .name = "mixer", .mask = 0666, .flags = FS_CHARDEVICE, .ioctl = snd_mixer_ioctl, .open = snd_mixer_open, .close = snd_mixer_close, }; static spin_lock_t _buffers_lock; static list_t _buffers; static uint32_t _next_device_id = SND_DEVICE_MAIN; struct dsp_node { ring_buffer_t * rb; size_t samples; size_t written; int realtime; }; int snd_register(snd_device_t * device) { int rv = 0; spin_lock(_devices_lock); device->id = _next_device_id; _next_device_id++; if (list_find(&_devices, device)) { rv = -1; goto snd_register_cleanup; } list_insert(&_devices, device); snd_register_cleanup: spin_unlock(_devices_lock); return rv; } int snd_unregister(snd_device_t * device) { int rv = 0; node_t * node = list_find(&_devices, device); if (!node) { printf("attempted to unregister unknown audio sink: %s\n", device->name); goto snd_unregister_cleanup; } list_delete(&_devices, node); snd_unregister_cleanup: spin_unlock(_devices_lock); return rv; } static ssize_t snd_dsp_write(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { if (!_devices.length) return -1; /* No sink available. */ struct dsp_node * dsp = node->device; size_t s = ring_buffer_available(dsp->rb); size_t out; if (size > s && dsp->realtime) { out = ring_buffer_write(dsp->rb, s & ~0x3, buffer); } else { out = ring_buffer_write(dsp->rb, size, buffer); } dsp->written += out / 4; return out; } static int snd_dsp_ioctl(fs_node_t * node, unsigned long request, void * argp) { /* Potentially use this to set sample rates in the future */ struct dsp_node * dsp = node->device; if (request == 4) { dsp->realtime = 1; } else if (request == 5) { return dsp->samples; } return -1; } static void snd_dsp_open(fs_node_t * node, unsigned int flags) { /* * XXX(gerow): A process could take the memory of the entire system by opening * too many of these... */ /* Allocate a buffer for the node and keep a reference for ourselves */ struct dsp_node * dsp = malloc(sizeof(struct dsp_node)); dsp->rb = ring_buffer_create(SND_BUF_SIZE); dsp->samples = 0; dsp->written = 0; dsp->realtime = 0; node->device = dsp; spin_lock(_buffers_lock); list_insert(&_buffers, node->device); spin_unlock(_buffers_lock); } static void snd_dsp_close(fs_node_t * node) { struct dsp_node * dsp = node->device; spin_lock(_buffers_lock); list_delete(&_buffers, list_find(&_buffers, dsp)); spin_unlock(_buffers_lock); ring_buffer_destroy(dsp->rb); free(dsp->rb); free(dsp); } static snd_device_t * snd_device_by_id(uint32_t device_id) { spin_lock(_devices_lock); snd_device_t * out = NULL; snd_device_t * cur = NULL; foreach(node, &_devices) { cur = node->value; if (cur->id == device_id) { out = cur; } } spin_unlock(_devices_lock); return out; } static int snd_mixer_ioctl(fs_node_t * node, unsigned long request, void * argp) { switch (request) { case SND_MIXER_GET_KNOBS: { snd_knob_list_t * list = argp; snd_device_t * device = snd_device_by_id(list->device); if (!device) { return -EINVAL; } list->num = device->num_knobs; for (uint32_t i = 0; i < device->num_knobs; i++) { list->ids[i] = device->knobs[i].id; } return 0; } case SND_MIXER_GET_KNOB_INFO: { snd_knob_info_t * info = argp; snd_device_t * device = snd_device_by_id(info->device); if (!device) { return -EINVAL; } for (uint32_t i = 0; i < device->num_knobs; i++) { if (device->knobs[i].id == info->id) { memcpy(info->name, device->knobs[i].name, sizeof(info->name)); return 0; } } return -EINVAL; } case SND_MIXER_READ_KNOB: { snd_knob_value_t * value = argp; snd_device_t * device = snd_device_by_id(value->device); if (!device) { return -EINVAL; } return device->mixer_read(value->id, &value->val); } case SND_MIXER_WRITE_KNOB: { snd_knob_value_t * value = argp; snd_device_t * device = snd_device_by_id(value->device); if (!device) { return -EINVAL; } return device->mixer_write(value->id, value->val); } default: { return -EINVAL; } } } static void snd_mixer_open(fs_node_t * node, unsigned int flags) { return; } static void snd_mixer_close(fs_node_t * node) { return; } int snd_request_buf(snd_device_t * device, uint32_t size, uint8_t *buffer) { static int16_t tmp_buf[0x100]; memset(buffer, 0, size); spin_lock(_buffers_lock); foreach(buf_node, &_buffers) { struct dsp_node * dsp = buf_node->value; ring_buffer_t * buf = dsp->rb; /* ~0x3 is to ensure we don't read partial samples or just a single channel */ size_t bytes_left = MIN(ring_buffer_unread(buf) & ~0x3, size); int16_t * adding_ptr = (int16_t *) buffer; while (bytes_left) { size_t this_read_size = MIN(bytes_left, sizeof(tmp_buf)); ring_buffer_read(buf, this_read_size, (uint8_t *)tmp_buf); dsp->samples += this_read_size / 4; /* 16 bits, 2 channels */ /* * Reduce the sample by a half so that multiple sources won't immediately * cause awful clipping. This is kind of a hack since it would probably be * better to just use some kind of compressor. */ for (size_t i = 0; i < N_ELEMENTS(tmp_buf); i++) { tmp_buf[i] /= 2; } for (size_t i = 0; i < this_read_size / sizeof(*adding_ptr); i++) { adding_ptr[i] += tmp_buf[i]; } adding_ptr += this_read_size / sizeof(*adding_ptr); bytes_left -= this_read_size; } } spin_unlock(_buffers_lock); return size; } static snd_device_t * snd_main_device(void) { spin_lock(_devices_lock); foreach(node, &_devices) { spin_unlock(_devices_lock); return node->value; } spin_unlock(_devices_lock); return NULL; } void snd_install(void) { vfs_mount("/dev/dsp", &_dsp_fnode, "dsp", ""); vfs_mount("/dev/mixer", &_mixer_fnode, "mixer", ""); } ================================================ FILE: kernel/binfmt.c ================================================ /** * @file kernel/binfmt.c * @brief Top-level executable parsing. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include extern int elf_exec(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp); int exec(const char * path, int argc, char *const argv[], char *const env[], int interp_depth); /** * @brief hash-exclamation parser * * Tries to safely read the first line of a script file to find an appropriate loader. */ int exec_shebang(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp) { if (interp > 4) { /* If an interpreter calls an interpreter too many times, bail. */ return -ELOOP; } /* Read MAX_LINE... */ char tmp[100]; read_fs(file, 0, 100, (unsigned char *)tmp); close_fs(file); char * cmd = (char *)&tmp[2]; if (*cmd == ' ') cmd++; /* Handle a leading space */ char * space_or_linefeed = strpbrk(cmd, " \n"); char * arg = NULL; /* We read too much stuff before finding EOL or another signal * that the interpreter was found, so bail. */ if (!space_or_linefeed) { return -ENOEXEC; } /* If we found a space, accept one argument before the path... */ if (*space_or_linefeed == ' ') { *space_or_linefeed = '\0'; space_or_linefeed++; arg = space_or_linefeed; /* ... and look for another EOL. */ space_or_linefeed = strpbrk(space_or_linefeed, "\n"); if (!space_or_linefeed) { /* If we didn't find one, bail. */ return -ENOEXEC; } } /* Make sure interpreter or argument is nil-terminated */ *space_or_linefeed = '\0'; char script[strlen(path)+1]; memcpy(script, path, strlen(path)+1); unsigned int nargc = argc + (arg ? 2 : 1); char * args[nargc + 2]; args[0] = cmd; args[1] = arg ? arg : script; args[2] = arg ? script : NULL; args[3] = NULL; int j = arg ? 3 : 2; for (int i = 1; i < argc; ++i, ++j) { args[j] = argv[i]; } args[j] = NULL; /* Try to execut the interpreter with the new arguments */ return exec(cmd, nargc, args, env, interp+1); } /* Consider exposing this and making it a list so it can be extended ... */ typedef int (*exec_func)(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp); typedef struct { exec_func func; unsigned char bytes[4]; unsigned int match; const char * name; } exec_def_t; exec_def_t fmts[] = { {elf_exec, {ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3}, 4, "ELF"}, {exec_shebang, {'#', '!', 0, 0}, 2, "#!"}, }; static int matches(unsigned char * a, unsigned char * b, unsigned int len) { for (unsigned int i = 0; i < len; ++i) { if (a[i] != b[i]) return 0; } return 1; } /** * @brief Replace the current process with a new one. * * @param path Filename of the new executable. * @param argc Number of arguments passed in @p argv * @param argv Arguments to supply to the new executable's entry point. * @param env Environment strings to pass to the new executable. * @param interp_depth Should be 0 for all external callers. * @returns Either never or -ENOEXEC on failure. */ int exec(const char * path, int argc, char *const argv[], char *const env[], int interp_depth) { fs_node_t * file = kopen(path, 0); if (!file) return -ENOENT; if (!has_permission(file, 01)) return -EACCES; unsigned char head[4]; read_fs(file, 0, 4, head); this_core->current_process->name = strdup(path); gettimeofday((struct timeval*)&this_core->current_process->start, NULL); for (unsigned int i = 0; i < sizeof(fmts) / sizeof(exec_def_t); ++i) { if (matches(fmts[i].bytes, head, fmts[i].match)) { return fmts[i].func(path, file, argc, argv, env, interp_depth); } } return -ENOEXEC; } /** * This is generally only called by system startup code to launch /bin/init. * Copies arguments from kernel constants into the heap, sets up a new MMU context * from the kernel boot context, and then calls @ref exec. */ int system(const char * path, int argc, char *const argv[], char *const envin[]) { char ** argv_ = malloc(sizeof(char*) * (argc + 1)); for (int j = 0; j < argc; ++j) { argv_[j] = malloc((strlen(argv[j]) + 1)); memcpy((void*)argv_[j], argv[j], strlen(argv[j]) + 1); } argv_[argc] = NULL; char * env[] = {NULL}; this_core->current_process->thread.page_directory = malloc(sizeof(page_directory_t)); this_core->current_process->thread.page_directory->directory = mmu_clone(NULL); /* base PML? for exec? */ this_core->current_process->thread.page_directory->refcount = 1; spin_init(this_core->current_process->thread.page_directory->lock); mmu_set_directory(this_core->current_process->thread.page_directory->directory); this_core->current_process->cmdline = (char**)argv_; exec(path,argc,argv_,envin ? envin : env,0); return -EINVAL; } ================================================ FILE: kernel/generic.c ================================================ /** * @file kernel/generic.c * @brief Architecture-neutral startup sequences. * * The generic startup sequence is broken into two parts: * @c generic_startup should be called as soon as the platform * has configured memory and is ready for the VFS and scheduler * to be initialized. @c generic_main should be called after * the platform has set up its own device drivers, loaded any * early filesystems, and is ready to yield control to init. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include extern int system(const char * path, int argc, const char ** argv, const char ** envin); extern void tarfs_register_init(void); extern void tmpfs_register_init(void); extern void tasking_start(void); extern void packetfs_initialize(void); extern void zero_initialize(void); extern void procfs_initialize(void); extern void shm_install(void); extern void random_initialize(void); extern void snd_install(void); extern void net_install(void); extern void console_initialize(void); extern void modules_install(void); void generic_startup(void) { args_parse(arch_get_cmdline()); initialize_process_tree(); shm_install(); vfs_install(); tarfs_register_init(); tmpfs_register_init(); map_vfs_directory("/dev"); console_initialize(); packetfs_initialize(); zero_initialize(); procfs_initialize(); random_initialize(); snd_install(); net_install(); tasking_start(); modules_install(); } int generic_main(void) { if (args_present("root")) { const char * root_type = "tar"; if (args_present("root_type")) { root_type = args_value("root_type"); } vfs_mount_type(root_type,args_value("root"),"/"); } const char * boot_arg = NULL; if (args_present("args")) { boot_arg = strdup(args_value("args")); } const char * boot_app = "/bin/init"; if (args_present("init")) { boot_app = args_value("init"); } dprintf("generic: Running %s as init process.\n", boot_app); const char * argv[] = { boot_app, boot_arg, NULL }; int argc = 0; while (argv[argc]) argc++; system(argv[0], argc, argv, NULL); dprintf("generic: Failed to execute %s.\n", boot_app); switch_task(0); return 0; } ================================================ FILE: kernel/misc/args.c ================================================ /** * @brief Kernel commandline argument parser. * * Arguments to the kernel are provided from the bootloader and * provide information such as what mode to pass to init, or what * hard disk partition should be mounted as root. We parse them * into a hash table for easy lookup by key. * * An argument may be value-less (having no '='), in which case * its value in the hash table will be NULL but it will be present. * Examples of value-less arguments are @c lfbwc or @c noi965. * * Arguments with values can have quoted or unquoted values. Unquoted * values are terminated by a space or the end of the command line and * are not processed for escapes. Examples of arguments with * unquoted values are @c root=/dev/ram0 or @c start=live-session. * * Quoted values must started immediately with a double quote ("). * Double quotes within the value may be escaped with a backslash (\). * Backslash can also be escaped. Any other character after a * backslash results in both a literal backslash and the following * character. * * If a quoted value is not properly terminated with an unescaped * double quote character, the entire argument will be ignored. * * @copyright This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2023 K. Lange */ #ifdef _KERNEL_ # include # include # include # include #endif hashmap_t * kernel_args_map = NULL; /** * @brief Determine if an argument was passed to the kernel. * * Check if an argument was provided to the kernel. If the argument is * a simple switch, a response of 1 can be considered "on" for that * argument; otherwise, this just notes that the argument was present, * so the caller should check whether it is correctly set. */ int args_present(const char * karg) { if (!kernel_args_map) return 0; return hashmap_has(kernel_args_map, karg); } /** * @brief Return the value associated with an argument provided to the kernel. */ char * args_value(const char * karg) { if (!kernel_args_map) return 0; return hashmap_get(kernel_args_map, karg); } /** * @brief Parse the given arguments to the kernel. * * @param arg A string containing all arguments, separated by spaces. */ void args_parse(const char * cmdline) { /* Sanity check... */ if (!cmdline) { return; } if (!kernel_args_map) { kernel_args_map = hashmap_create(10); } char * argbuf = strdup(cmdline); char * x = argbuf; for (;;) { while (*x && *x == ' ') x++; /* skip spaces */ if (!*x) break; char * value = NULL; char * key = x; while (*x && *x != '=' && *x != ' ') x++; if (*x == '=') { *x++ = '\0'; if (*x == '"') { /* Start of quoted value */ x++; value = x; char * w = x; while (*x && *x != '"') { if (*x == '\\') { x++; switch (*x) { case '"': *w++ = '"'; x++; break; case '\\': *w++ = '\\'; x++; break; case '\0': goto _parse_error; default: *w++ = '\\'; *w++ = *x++; break; } } else { *w++ = *x++; } } if (*x == '"') { *w = '\0'; x++; } else if (*x == '\0') { goto _parse_error; } } else { /* Start of unquoted value */ value = x; while (*x && *x != ' ') x++; if (*x == ' ') { *x = '\0'; x++; } } } else if (*x == ' ') { /* Value-less argument where we need to set nil byte */ *x++ = '\0'; } hashmap_set(kernel_args_map, key, value ? strdup(value) : NULL); } _parse_error: free(argbuf); return; } #ifndef _KERNEL_ char * args_from_procfs(void) { /* Open */ FILE * f = fopen("/proc/cmdline", "r"); if (!f) return NULL; /* Determine size */ fseek(f, 0, SEEK_END); size_t size = ftell(f); fseek(f, 0, SEEK_SET); /* Read */ char * cmdline = calloc(size + 1, 1); size_t rsize = fread(cmdline, 1, size, f); if (cmdline[rsize-1] == '\n') cmdline[rsize-1] = '\0'; fclose(f); /* Parse */ kernel_args_map = hashmap_create(10); args_parse(cmdline); return cmdline; } #endif ================================================ FILE: kernel/misc/assert.c ================================================ /** * @file kernel/misc/assert.h * @brief Kernel assertion handler. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include void __assert_failed(const char * file, int line, const char * func, const char * cond) { arch_fatal_prepare(); dprintf("%s:%d (%s) Assertion failed: %s\n", file, line, func, cond); arch_dump_traceback(); arch_fatal(); } ================================================ FILE: kernel/misc/elf64.c ================================================ /** * @file kernel/misc/elf64.c * @brief Elf64 parsing tools for modules and static userspace binaries. * * Provides exec() for Elf64 binaries. Note that the loader only directly * loads static binaries; for dynamic binaries, the requested interpreter * is loaded, which should generally be /lib/ld.so, which should itself * be a static binary. This loader is platform-generic. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2023 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include hashmap_t * _modules_table = NULL; sched_mutex_t * _modules_mutex = NULL; void modules_install(void) { _modules_table = hashmap_create(10); _modules_mutex = mutex_init("module loader"); } hashmap_t * modules_get_list(void) { return _modules_table; } /** * Encode immediate for ADR(p) instruction */ static uint32_t aarch64_imm_adr(uint32_t val) { uint32_t low = (val & 0x3) << 29; uint32_t high = ((val >> 2) & 0x7ffff) << 5; return low | high; } /** * Encode immediate for 12-bit instructions */ static uint32_t aarch64_imm_12(uint32_t val) { return (val & 0xFFF) << 10; } int elf_module(char ** args) { int error = 0; Elf64_Header header; fs_node_t * file = kopen(args[0],0); if (!file) { return -ENOENT; } read_fs(file, 0, sizeof(Elf64_Header), (uint8_t*)&header); if (header.e_ident[0] != ELFMAG0 || header.e_ident[1] != ELFMAG1 || header.e_ident[2] != ELFMAG2 || header.e_ident[3] != ELFMAG3) { printf("Invalid file: Bad header.\n"); close_fs(file); return -EINVAL; } if (header.e_ident[EI_CLASS] != ELFCLASS64) { printf("(Wrong Elf class)\n"); close_fs(file); return -EINVAL; } if (header.e_type != ET_REL) { printf("(Not a relocatable object)\n"); close_fs(file); return -EINVAL; } mutex_acquire(_modules_mutex); /* Just slap the whole thing into memory, why not... */ char * module_load_address = mmu_map_module(file->length); read_fs(file, 0, file->length, (void*)module_load_address); /** * Set up section header entries to have correct loaded addresses, and map * any NOBITS sections to new memory. We'll page-align anything, which * should be good enough for any object files we make... */ for (unsigned int i = 0; i < header.e_shnum; ++i) { Elf64_Shdr * sectionHeader = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * i); if (sectionHeader->sh_type == SHT_NOBITS) { sectionHeader->sh_addr = (uintptr_t)mmu_map_module(sectionHeader->sh_size); memset((void*)sectionHeader->sh_addr, 0, sectionHeader->sh_size); } else { sectionHeader->sh_addr = (uintptr_t)(module_load_address + sectionHeader->sh_offset); if (sectionHeader->sh_addralign && (sectionHeader->sh_addr & (sectionHeader->sh_addralign -1))) { dprintf("mod: probably not aligned correctly: %#zx %ld\n", sectionHeader->sh_addr, sectionHeader->sh_addralign); } } } struct Module * moduleData = NULL; /** * Let's start loading symbols... */ for (unsigned int i = 0; i < header.e_shnum; ++i) { Elf64_Shdr * sectionHeader = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * i); if (sectionHeader->sh_type != SHT_SYMTAB) continue; Elf64_Shdr * strtab_hdr = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * sectionHeader->sh_link); char * symNames = (char*)strtab_hdr->sh_addr; Elf64_Sym * symTable = (Elf64_Sym*)sectionHeader->sh_addr; /* Uh, we should be able to figure out how many symbols we have by doing something less dumb than * just checking the size of the section, right? */ for (unsigned int sym = 0; sym < sectionHeader->sh_size / sizeof(Elf64_Sym); ++sym) { /* Unlike the previous implementation of this module loader in toaru32, * we specifically do not support binding symbols directly from newly * loaded modules. If a module wants to expose symbols, it should use * @c ksym_bind to supply new symbol names to the symbol table. */ if (symTable[sym].st_shndx > 0 && symTable[sym].st_shndx < SHN_LOPROC) { Elf64_Shdr * sh_hdr = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * symTable[sym].st_shndx); symTable[sym].st_value = symTable[sym].st_value + sh_hdr->sh_addr; } else if (symTable[sym].st_shndx == SHN_UNDEF) { symTable[sym].st_value = (uintptr_t)ksym_lookup(symNames + symTable[sym].st_name); } if (symTable[sym].st_name && !strcmp(symNames + symTable[sym].st_name, "metadata")) { moduleData = (void*)symTable[sym].st_value; } } } if (!moduleData) { error = EINVAL; goto _unmap_module; } for (unsigned int i = 0; i < header.e_shnum; ++i) { Elf64_Shdr * sectionHeader = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * i); if (sectionHeader->sh_type != SHT_RELA) continue; Elf64_Rela * table = (Elf64_Rela*)sectionHeader->sh_addr; /* Get the section these relocations apply to */ Elf64_Shdr * targetSection = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * sectionHeader->sh_info); /* Get the symbol table */ Elf64_Shdr * symbolSection = (Elf64_Shdr*)(module_load_address + header.e_shoff + header.e_shentsize * sectionHeader->sh_link); Elf64_Sym * symbolTable = (Elf64_Sym *)symbolSection->sh_addr; #define S (symbolTable[ELF64_R_SYM(table[rela].r_info)].st_value) #define A (table[rela].r_addend) #define T32 (*(uint32_t*)target) #define T64 (*(uint64_t*)target) #define P (target) for (unsigned int rela = 0; rela < sectionHeader->sh_size / sizeof(Elf64_Rela); ++rela) { uintptr_t target = table[rela].r_offset + targetSection->sh_addr; switch (ELF64_R_TYPE(table[rela].r_info)) { #if defined(__x86_64__) case R_X86_64_64: T64 = S + A; break; case R_X86_64_32: T32 = S + A; break; case R_X86_64_PC32: T32 = S + A - P; break; #elif defined(__aarch64__) case 275: { /* R_AARCH64_ADR_PREL_PG_HI21 */ T32 = T32 | aarch64_imm_adr( ((S + A) >> 12) - (P >> 12) ); break; } case 286: /* R_AARCH64_LDST64_ABS_LO12_NC */ T32 = T32 | aarch64_imm_12( ((S + A) >> 3) & 0x1FF ); break; case 282: /* R_AARCH64_JUMP26 */ case 283: /* R_AARCH64_CALL26 */ T32 = T32 | (((S + A - P) >> 2) & 0x3ffffff); break; case 257: /* ABS64 */ T64 = S + A; break; case 258: /* ABS32 */ T32 = S + A; break; #endif default: dprintf("mod: unsupported relocation %ld found\n", ELF64_R_TYPE(table[rela].r_info)); error = EINVAL; } } } #undef S #undef A #undef T32 #undef T64 #undef P if (error) goto _unmap_module; if (hashmap_has(_modules_table, moduleData->name)) { error = EEXIST; goto _unmap_module; } struct LoadedModule * loadedData = malloc(sizeof(struct LoadedModule)); loadedData->metadata = moduleData; loadedData->baseAddress = (uintptr_t)module_load_address; loadedData->fileSize = file->length; loadedData->loadedSize = (uintptr_t)mmu_map_module(0) - (uintptr_t)module_load_address; close_fs(file); hashmap_set(_modules_table, moduleData->name, loadedData); mutex_release(_modules_mutex); /* Count arguments */ int argc = 0; for (char ** aa = args; *aa; ++aa) ++argc; return moduleData->init(argc, args); _unmap_module: close_fs(file); mmu_unmap_module((uintptr_t)module_load_address, (uintptr_t)mmu_map_module(0) - (uintptr_t)module_load_address); mutex_release(_modules_mutex); return -error; } int elf_exec(const char * path, fs_node_t * file, int argc, const char *const argv[], const char *const env[], int interp) { Elf64_Header header; read_fs(file, 0, sizeof(Elf64_Header), (uint8_t*)&header); if (header.e_ident[0] != ELFMAG0 || header.e_ident[1] != ELFMAG1 || header.e_ident[2] != ELFMAG2 || header.e_ident[3] != ELFMAG3) { printf("Invalid file: Bad header.\n"); close_fs(file); return -EINVAL; } if (header.e_ident[EI_CLASS] != ELFCLASS64) { printf("(Wrong Elf class)\n"); return -EINVAL; } /* This loader can only handle basic executables. */ if (header.e_type != ET_EXEC) { printf("(Not an executable)\n"); /* TODO: what about DYN? */ return -EINVAL; } if ((file->mask & S_ISUID) && !(this_core->current_process->flags & (PROC_FLAG_TRACE_SYSCALLS | PROC_FLAG_TRACE_SIGNALS))) { /* setuid */ this_core->current_process->user = file->uid; } /* First check if it is dynamic and needs an interpreter */ for (int i = 0; i < header.e_phnum; ++i) { Elf64_Phdr phdr; read_fs(file, header.e_phoff + header.e_phentsize * i, sizeof(Elf64_Phdr), (uint8_t*)&phdr); if (phdr.p_type == PT_DYNAMIC) { close_fs(file); unsigned int nargc = argc + 3; const char * args[nargc+1]; /* oh yeah, great, a stack-allocated dynamic array... wonderful... */ args[0] = "ld.so"; args[1] = "-e"; args[2] = strdup(this_core->current_process->name); int j = 3; for (int i = 0; i < argc; ++i, ++j) { args[j] = argv[i]; } args[j] = NULL; fs_node_t * file = kopen("/lib/ld.so",0); /* FIXME PT_INTERP value */ if (!file) return -EINVAL; return elf_exec(NULL, file, nargc, args, env, 1); } } uintptr_t execBase = -1; uintptr_t heapBase = 0; mmu_set_directory(NULL); page_directory_t * this_directory = this_core->current_process->thread.page_directory; this_core->current_process->thread.page_directory = malloc(sizeof(page_directory_t)); this_core->current_process->thread.page_directory->refcount = 1; spin_init(this_core->current_process->thread.page_directory->lock); this_core->current_process->thread.page_directory->directory = mmu_clone(NULL); mmu_set_directory(this_core->current_process->thread.page_directory->directory); process_release_directory(this_directory); for (int i = 0; i < NUMSIGNALS; ++i) { if (this_core->current_process->signals[i].handler != 1) { this_core->current_process->signals[i].handler = 0; this_core->current_process->signals[i].flags = 0; } } for (int i = 0; i < header.e_phnum; ++i) { Elf64_Phdr phdr; read_fs(file, header.e_phoff + header.e_phentsize * i, sizeof(Elf64_Phdr), (uint8_t*)&phdr); if (phdr.p_type == PT_LOAD) { for (uintptr_t i = phdr.p_vaddr; i < phdr.p_vaddr + phdr.p_memsz; i += 0x1000) { union PML * page = mmu_get_page(i, MMU_GET_MAKE); mmu_frame_allocate(page, MMU_FLAG_WRITABLE); } read_fs(file, phdr.p_offset, phdr.p_filesz, (void*)phdr.p_vaddr); for (size_t i = phdr.p_filesz; i < phdr.p_memsz; ++i) { *(char*)(phdr.p_vaddr + i) = 0; } #ifdef __aarch64__ extern void arch_clear_icache(uintptr_t,uintptr_t); arch_clear_icache(phdr.p_vaddr, phdr.p_vaddr + phdr.p_memsz); #endif if (phdr.p_vaddr + phdr.p_memsz > heapBase) { heapBase = phdr.p_vaddr + phdr.p_memsz; } if (phdr.p_vaddr < execBase) { execBase = phdr.p_vaddr; } } /* TODO: Should also be setting up TLS PHDRs. */ } this_core->current_process->image.heap = (heapBase + 0xFFF) & (~0xFFF); this_core->current_process->image.entry = header.e_entry; close_fs(file); // arch_set_...? /* Map stack space */ uintptr_t userstack = 0x800000000000; for (uintptr_t i = userstack - 512 * 0x400; i < userstack; i += 0x1000) { union PML * page = mmu_get_page(i, MMU_GET_MAKE); mmu_frame_allocate(page, MMU_FLAG_WRITABLE); } this_core->current_process->image.userstack = userstack - 16 * 0x400; #define PUSH(type,val) do { \ userstack -= sizeof(type); \ while (userstack & (sizeof(type)-1)) userstack--; \ *((type*)userstack) = (val); \ } while (0) #define PUSHSTR(s) do { \ ssize_t l = strlen(s); \ do { \ PUSH(char,s[l]); \ l--; \ } while (l>=0); \ } while (0) /* XXX This should probably be done backwards so we can be * sure that we're aligning the stack properly. It * doesn't matter too much as our crt0+libc align it * correctly for us and environ + auxv detection is * based on the addresses of argv, not the actual * stack pointer, but it's still weird. */ char * argv_ptrs[argc]; for (int i = 0; i < argc; ++i) { PUSHSTR(argv[i]); argv_ptrs[i] = (char*)userstack; } /* Now push envp */ int envc = 0; char ** envpp = (char**)env; while (*envpp) { envc++; envpp++; } char * envp_ptrs[envc]; for (int i = 0; i < envc; ++i) { PUSHSTR(env[i]); envp_ptrs[i] = (char*)userstack; } PUSH(uintptr_t, 0); PUSH(uintptr_t, this_core->current_process->user); PUSH(uintptr_t, 11); /* AT_UID */ PUSH(uintptr_t, this_core->current_process->real_user); PUSH(uintptr_t, 12); /* AT_EUID */ PUSH(uintptr_t, 0); PUSH(uintptr_t, 0); /* envp NULL */ for (int i = envc; i > 0; i--) { PUSH(char*,envp_ptrs[i-1]); } char ** _envp = (char**)userstack; PUSH(uintptr_t, 0); /* argv NULL */ for (int i = argc; i > 0; i--) { PUSH(char*,argv_ptrs[i-1]); } char ** _argv = (char**)userstack; PUSH(uintptr_t, argc); arch_set_kernel_stack(this_core->current_process->image.stack); arch_enter_user(header.e_entry, argc, _argv, _envp, userstack); return -EINVAL; } ================================================ FILE: kernel/misc/fbterm.c ================================================ /** * @file kernel/misc/fbterm.c * @brief Crude framebuffer terminal for 32bpp framebuffer devices. * * Provides a simple graphical text renderer for early startup, with * support for simple escape sequences, on top of a framebuffer set up * with the `lfbvideo` module. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include /* Whether to scroll or wrap when cursor reaches the bottom. */ static int fbterm_scroll = 0; static void (*write_char)(int, int, int, uint32_t) = NULL; static int (*get_width)(void) = NULL; static int (*get_height)(void) = NULL; static void (*scroll_terminal)(void) = NULL; static int x = 0; static int y = 0; static int term_state = 0; static char term_buf[1024] = {0}; static int term_buf_c = 0; /* Bitmap font details */ #include "../../apps/terminal-font.h" #define char_height LARGE_FONT_CELL_HEIGHT #define char_width LARGE_FONT_CELL_WIDTH /* Default colors */ #define BG_COLOR 0xFF000000 /* Background */ #define FG_COLOR 0xFFCCCCCC /* Main text color */ static uint32_t fg_color = FG_COLOR; static uint32_t bg_color = BG_COLOR; static inline void set_point(int x, int y, uint32_t value) { if (lfb_resolution_b == 32) { ((uint32_t*)lfb_vid_memory)[y * (lfb_resolution_s/4) + x] = value; #ifdef __aarch64__ /* TODO just map it uncached in the first place... */ asm volatile ("dc cvac, %0\n" :: "r"((uintptr_t)&((uint32_t*)lfb_vid_memory)[y * (lfb_resolution_s/4) + x]) : "memory"); #endif } else if (lfb_resolution_b == 24) { lfb_vid_memory[y * lfb_resolution_s + x * 3 + 0] = (value >> 0) & 0xFF; lfb_vid_memory[y * lfb_resolution_s + x * 3 + 1] = (value >> 8) & 0xFF; lfb_vid_memory[y * lfb_resolution_s + x * 3 + 2] = (value >> 16) & 0xFF; } } static void fb_write_char(int _x, int _y, int val, uint32_t color) { if (val > 128) { val = 4; } int x = 1 + _x * char_width; int y = _y * char_height; uint8_t * c = large_font[val]; for (uint8_t i = 0; i < char_height; ++i) { for (uint8_t j = 0; j < char_width; ++j) { if (c[i] & (1 << (LARGE_FONT_MASK-j))) { set_point(x+j,y+i,color); } else { set_point(x+j,y+i,bg_color); } } } } /** * @brief Basic 16-color ANSI palette with Tango colors. */ static uint32_t term_colors[] = { 0xFF000000, 0xFFCC0000, 0xFF4E9A06, 0xFFC4A000, 0xFF3465A4, 0xFF75507B, 0xFF06989A, 0xFFD3D7CF, 0xFF555753, 0xFFEF2929, 0xFF8AE234, 0xFFFCE94F, 0xFF729FCF, 0xFFAD7FA8, 0xFF34E2E2, 0xFFEEEEEC, }; static int fb_get_width(void) { return (lfb_resolution_x - 1) / char_width; } static int fb_get_height(void) { return lfb_resolution_y / char_height; } static void fb_scroll_terminal(void) { memmove(lfb_vid_memory, lfb_vid_memory + sizeof(uint32_t) * lfb_resolution_x * char_height, (lfb_resolution_y - char_height) * lfb_resolution_x * 4); memset(lfb_vid_memory + sizeof(uint32_t) * (lfb_resolution_y - char_height) * lfb_resolution_x, 0x00, char_height * lfb_resolution_x * 4); } static void draw_square(int x, int y) { int center_x = lfb_resolution_x / 2; int center_y = lfb_resolution_y / 2; for (size_t _y = 0; _y < 7; ++_y) { uint32_t color = 0xFF00B2FF - (y * 8 + _y) * 0x200; for (size_t _x = 0; _x < 7; ++_x) { set_point(center_x - 32 + x * 8 + _x, center_y - 32 + y * 8 + _y, color); } } } void fbterm_draw_logo(void) { uint64_t logo_squares = 0x981818181818FFFFUL; for (size_t y = 0; y < 8; ++y) { for (size_t x = 0; x < 8; ++x) { if (logo_squares & (1 << x)) { draw_square(x,y); } } logo_squares >>= 8; } } void fbterm_reset(void) { x = 0; y = 0; term_state = 0; fg_color = FG_COLOR; bg_color = BG_COLOR; } static void fbterm_init_framebuffer(void) { write_char = fb_write_char; get_width = fb_get_width; get_height = fb_get_height; scroll_terminal = fb_scroll_terminal; fbterm_draw_logo(); } static void ega_write_char(int x, int y, int ch, uint32_t color) { unsigned short att = 7 << 8; unsigned short *where = (unsigned short*)(mmu_map_from_physical(0xB8000)) + (y * 80 + x); *where = (ch & 0xFF) | att; } static int ega_get_width(void) { return 80; } static int ega_get_height(void) { return 25; } static void ega_scroll_terminal(void) { memmove(mmu_map_from_physical(0xB8000), (char*)mmu_map_from_physical(0xB8000) + sizeof(unsigned short) * 80, sizeof(unsigned short) * (80 * 24)); memset((char*)mmu_map_from_physical(0xB8000) + sizeof(unsigned short) * (80 * 24), 0x00, 80 * sizeof(unsigned short)); } static void fbterm_init_ega(void) { write_char = ega_write_char; get_width = ega_get_width; get_height = ega_get_height; scroll_terminal = ega_scroll_terminal; } static void cursor_update(void) { if (x >= get_width()) { x = 0; y++; } if (y >= get_height()) { if (fbterm_scroll) { y--; scroll_terminal(); } else { y = 0; } } } static void process_char(char ch) { if (term_state == 1) { if (ch == '[') { term_buf_c = 0; term_buf[term_buf_c] = '\0'; term_state = 2; } else { term_state = 0; process_char(ch); } return; } else if (term_state == 2) { if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { /* do the thing */ switch (ch) { case 'm': { char * arg = &term_buf[0]; char * next; int argC = 0; int isBold = 0; do { next = strchr(arg, ';'); if (next) { *next = '\0'; next++; } int asInt = atoi(arg); if (asInt == 0) { fg_color = FG_COLOR; bg_color = BG_COLOR; isBold = 0; } else if (asInt == 1) { isBold = 1; } else if (asInt == 22) { fg_color = FG_COLOR; isBold = 0; } else if (asInt >= 30 && asInt <= 37) { fg_color = term_colors[asInt-30 + (isBold ? 8 : 0)]; } else if (asInt >= 90 && asInt <= 97) { fg_color = term_colors[asInt-90 + 8]; } else if (asInt >= 40 && asInt <= 47) { bg_color = term_colors[asInt-40 + (isBold ? 8 : 0)]; } else if (asInt >= 100 && asInt <= 107) { bg_color = term_colors[asInt-100 + 8]; } else if (asInt == 38) { fg_color = FG_COLOR; } else if (asInt == 48) { bg_color = BG_COLOR; } else if (asInt == 7) { uint32_t tmp = fg_color; fg_color = bg_color; bg_color = tmp; } arg = next; argC++; } while (arg); break; } case 'G': { /* Set cursor column */ x = atoi(term_buf) - 1; break; } case 'K': { if (atoi(term_buf) == 0) { for (int i = x; i < get_width(); ++i) { write_char(i,y,' ',bg_color); } } break; } } term_state = 0; } else { term_buf[term_buf_c++] = ch; term_buf[term_buf_c] = '\0'; } return; } else if (ch == '\033') { term_state = 1; return; } write_char(x,y,' ',bg_color); switch (ch) { case '\n': x = 0; y++; break; case '\r': x = 0; break; case '\b': if (x) { x--; write_char(x,y,' ',fg_color); } break; default: if ((unsigned int)ch > 127) return; write_char(x,y,ch,fg_color); x++; break; } cursor_update(); } static size_t (*previous_writer)(size_t,uint8_t*) = NULL; size_t fbterm_write(size_t size, uint8_t *buffer) { if (!buffer) return 0; for (unsigned int i = 0; i < size; ++i) { process_char(buffer[i]); } if (previous_writer) previous_writer(size,buffer); return size; } void fbterm_initialize(void) { if (lfb_resolution_x) { if (args_present("fbterm-scroll")) { fbterm_scroll = 1; } fbterm_init_framebuffer(); } else { #ifdef __x86_64__ fbterm_scroll = 1; fbterm_init_ega(); #else return; #endif } previous_writer = printf_output; printf_output = fbterm_write; console_set_output(fbterm_write); dprintf("fbterm: Generic framebuffer text output enabled.\n"); } ================================================ FILE: kernel/misc/gzip.c ================================================ /** * @file kernel/misc/gzip.c * @brief Gzip/DEFLATE decompression. * * Provides decompression for ramdisks. * Based on the same approach to DEFLATE decompression as libraries * like "tinf", this is a slow-but-simple implementation of Huffman * decoding. The kernel version operates directly on two pointers, * @c gzip_inputPtr and @c gzip_outputPtr. For a more robust API, * see the userspace version. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2020-2021 K. Lange */ #include #include static uint8_t bit_buffer = 0; static char buffer_size = 0; uint8_t * gzip_inputPtr = NULL; uint8_t * gzip_outputPtr = NULL; __attribute__((always_inline)) static inline uint8_t read_byte(void) { return *gzip_inputPtr++; } __attribute__((always_inline)) static inline void _write(unsigned int sym) { *gzip_outputPtr++ = sym; } /** * Decoded Huffman table */ struct huff { uint16_t counts[16]; /* Number of symbols of each length */ uint16_t symbols[288]; /* Ordered symbols */ }; /** * Fixed Huffman code tables, generated later. */ struct huff fixed_lengths; struct huff fixed_dists; /** * Read a little-endian short from the input. */ static uint16_t read_16le(void) { uint16_t a, b; a = read_byte(); b = read_byte(); return (a << 0) | (b << 8); } /** * Read a single bit from the source. * Fills the byte buffer with one byte when it runs out. */ static _Bool read_bit(void) { /* When we run out of bits... */ if (buffer_size == 0) { /* Refill from the next input byte */ bit_buffer = read_byte(); /* And restore bit buffer size to 8 bits */ buffer_size = 8; } /* Get the next available bit */ int out = bit_buffer & 1; /* Shift the bit buffer forward */ bit_buffer >>= 1; /* There is now one less bit available */ buffer_size--; return out; } /** * Read multible bits, in bit order, from the source. */ static uint32_t read_bits(unsigned int count) { uint32_t out = 0; for (unsigned int bit = 0; bit < count; bit++) { /* Read one bit at a time, from least to most significant */ out |= (read_bit() << bit); } return out; } /** * Build a Huffman table from an array of lengths. */ static void build_huffman(const uint8_t * lengths, size_t size, struct huff * out) { uint16_t offsets[16]; unsigned int count = 0; /* Zero symbol counts */ for (unsigned int i = 0; i < 16; ++i) out->counts[i] = 0; /* Count symbols */ for (unsigned int i = 0; i < size; ++i) out->counts[lengths[i]]++; /* Special case... */ out->counts[0] = 0; /* Figure out offsets */ for (unsigned int i = 0; i < 16; ++i) { offsets[i] = count; count += out->counts[i]; } /* Build symbol ordering */ for (unsigned int i = 0; i < size; ++i) { if (lengths[i]) out->symbols[offsets[lengths[i]]++] = i; } } /** * Build the fixed Huffman tables */ static void build_fixed(void) { /* From 3.2.6: * Lit Value Bits Codes * --------- ---- ----- * 0 - 143 8 00110000 through * 10111111 * 144 - 255 9 110010000 through * 111111111 * 256 - 279 7 0000000 through * 0010111 * 280 - 287 8 11000000 through * 11000111 */ uint8_t lengths[288]; for (int i = 0; i < 144; ++i) lengths[i] = 8; for (int i = 144; i < 256; ++i) lengths[i] = 9; for (int i = 256; i < 280; ++i) lengths[i] = 7; for (int i = 280; i < 288; ++i) lengths[i] = 8; build_huffman(lengths, 288, &fixed_lengths); /* Continued from 3.2.6: * Distance codes 0-31 are represented by (fixed-length) 5-bit * codes, with possible additional bits as shown in the table * shown in Paragraph 3.2.5, above. Note that distance codes 30- * 31 will never actually occur in the compressed data. */ for (int i = 0; i < 30; ++i) lengths[i] = 5; build_huffman(lengths, 30, &fixed_dists); } /** * Decode a symbol from the source using a Huffman table. */ static int decode(const struct huff * huff) { int count = 0, cur = 0; for (int i = 1; cur >= 0; i++) { cur = (cur << 1) | read_bit(); /* Shift */ count += huff->counts[i]; cur -= huff->counts[i]; } return huff->symbols[count + cur]; } struct huff_ring { size_t pointer; uint8_t data[32768]; }; static struct huff_ring data = {0, {0}}; /** * Emit one byte to the output, maintaining the ringbuffer. * The ringbuffer ensures we can always look back 32K bytes * while keeping output streaming. */ static void emit(unsigned char byte) { data.data[data.pointer++] = byte; data.pointer &= 0x7FFF; _write(byte); } /** * Look backwards in the output ring buffer. */ static void peek(unsigned int offset) { data.data[data.pointer] = data.data[(data.pointer + 0x8000 - offset) & 0x7FFF]; _write(data.data[data.pointer++]); data.pointer &= 0x7FFF; } /** * Decompress a block of Huffman-encoded data. */ static int inflate(const struct huff * huff_len, const struct huff * huff_dist) { /* These are the extra bits for lengths from the tables in section 3.2.5 * Extra Extra Extra * Code Bits Length(s) Code Bits Lengths Code Bits Length(s) * ---- ---- ------ ---- ---- ------- ---- ---- ------- * 257 0 3 267 1 15,16 277 4 67-82 * 258 0 4 268 1 17,18 278 4 83-98 * 259 0 5 269 2 19-22 279 4 99-114 * 260 0 6 270 2 23-26 280 4 115-130 * 261 0 7 271 2 27-30 281 5 131-162 * 262 0 8 272 2 31-34 282 5 163-194 * 263 0 9 273 3 35-42 283 5 195-226 * 264 0 10 274 3 43-50 284 5 227-257 * 265 1 11,12 275 3 51-58 285 0 258 * 266 1 13,14 276 3 59-66 */ static const uint16_t lens[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 }; static const uint16_t lext[] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; /* Extra bits for distances.... * Extra Extra Extra * Code Bits Dist Code Bits Dist Code Bits Distance * ---- ---- ---- ---- ---- ------ ---- ---- -------- * 0 0 1 10 4 33-48 20 9 1025-1536 * 1 0 2 11 4 49-64 21 9 1537-2048 * 2 0 3 12 5 65-96 22 10 2049-3072 * 3 0 4 13 5 97-128 23 10 3073-4096 * 4 1 5,6 14 6 129-192 24 11 4097-6144 * 5 1 7,8 15 6 193-256 25 11 6145-8192 * 6 2 9-12 16 7 257-384 26 12 8193-12288 * 7 2 13-16 17 7 385-512 27 12 12289-16384 * 8 3 17-24 18 8 513-768 28 13 16385-24576 * 9 3 25-32 19 8 769-1024 29 13 24577-32768 */ static const uint16_t dists[] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 }; static const uint16_t dext[] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; while (1) { unsigned int symbol = decode(huff_len); if (symbol < 256) { emit(symbol); } else if (symbol == 256) { /* "The literal/length symbol 256 (end of data), ..." */ break; } else { unsigned int length, distance, offset; symbol -= 257; length = read_bits(lext[symbol]) + lens[symbol]; distance = decode(huff_dist); offset = read_bits(dext[distance]) + dists[distance]; for (unsigned int i = 0; i < length; ++i) { peek(offset); } } } return 0; } /** * Decode a dynamic Huffman block. */ static void decode_huffman(void) { /* Ordering of code length codes: * (HCLEN + 4) x 3 bits: code lengths for the code length * alphabet given just above, in the order: ... */ static const uint8_t clens[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; unsigned int literals, distances, clengths; uint8_t lengths[320] = {0}; literals = 257 + read_bits(5); /* 5 Bits: HLIT ... 257 */ distances = 1 + read_bits(5); /* 5 Bits: HDIST ... 1 */ clengths = 4 + read_bits(4); /* 4 Bits: HCLEN ... 4 */ /* (HCLEN + 4) x 3 bits... */ for (unsigned int i = 0; i < clengths; ++i) { lengths[clens[i]] = read_bits(3); } struct huff codes; build_huffman(lengths, 19, &codes); /* Decode symbols: * HLIT + 257 code lengths for the literal/length alphabet... * HDIST + 1 code lengths for the distance alphabet... */ unsigned int count = 0; while (count < literals + distances) { int symbol = decode(&codes); int rep = 0, length; switch (symbol) { case 16: /* 16: Copy the previous code length 3-6 times */ rep = lengths[count-1]; length = read_bits(2) + 3; /* The next 2 bits indicate repeat length */ break; case 17: /* Repeat a code length of 0 for 3 - 10 times */ length = read_bits(3) + 3; /* 3 bits of length */ break; case 18: /* Repeat a code length of 0 for 11 - 138 times */ length = read_bits(7) + 11; /* 7 bits of length */ break; default: length = 1; rep = symbol; break; } while (length--) { lengths[count++] = rep; } } /* Build tables from lenghts decoded above */ struct huff huff_len; build_huffman(lengths, literals, &huff_len); struct huff huff_dist; build_huffman(lengths + literals, distances, &huff_dist); inflate(&huff_len, &huff_dist); } /** * Decode an uncompressed block. */ static int uncompressed(void) { /* Reset byte alignment */ bit_buffer = 0; buffer_size = 0; /* "The rest of the block consists of the following information:" * 0 1 2 3 4... * +---+---+---+---+================================+ * | LEN | NLEN |... LEN bytes of literal data...| * +---+---+---+---+================================+ */ uint16_t len = read_16le(); /* "the number of data bytes in the block" */ uint16_t nlen = read_16le(); /* "the one's complement of LEN */ /* Sanity check - does the ones-complement length actually match? */ if ((nlen & 0xFFFF) != (~len & 0xFFFF)) { return 1; } /* Emit LEN bytes from the source to the output */ for (int i = 0; i < len; ++i) { emit(read_byte()); } return 0; } /** * Decompress DEFLATE-compressed data. */ __attribute__((optimize("O2"))) __attribute__((hot)) int deflate_decompress(void) { bit_buffer = 0; buffer_size = 0; build_fixed(); /* read compressed data */ while (1) { /* Read bit */ int is_final = read_bit(); int type = read_bits(2); switch (type) { case 0x00: /* BTYPE=00 Non-compressed blocks */ uncompressed(); break; case 0x01: /* BYTPE=01 Compressed with fixed Huffman codes */ inflate(&fixed_lengths, &fixed_dists); break; case 0x02: /* BTYPE=02 Compression with dynamic Huffman codes */ decode_huffman(); break; case 0x03: return 1; default: __builtin_unreachable(); break; } if (is_final) { break; } } return 0; } #define GZIP_FLAG_TEXT (1 << 0) #define GZIP_FLAG_HCRC (1 << 1) #define GZIP_FLAG_EXTR (1 << 2) #define GZIP_FLAG_NAME (1 << 3) #define GZIP_FLAG_COMM (1 << 4) static unsigned int read_32le(void) { unsigned int a, b, c, d; a = read_byte(); b = read_byte(); c = read_byte(); d = read_byte(); return (d << 24) | (c << 16) | (b << 8) | (a << 0); } int gzip_decompress(void) { /* Read gzip headers */ if (read_byte() != 0x1F) return 1; if (read_byte() != 0x8B) return 1; unsigned int cm = read_byte(); if (cm != 8) return 1; unsigned int flags = read_byte(); /* Read mtime */ unsigned int mtime = read_32le(); (void)mtime; /* Read extra flags */ unsigned int xflags = read_byte(); (void)xflags; /* Read and discord OS flag */ unsigned int os = read_byte(); (void)os; /* Extra bytes */ if (flags & GZIP_FLAG_EXTR) { unsigned short size = read_16le(); for (unsigned int i = 0; i < size; ++i) read_byte(); } if (flags & GZIP_FLAG_NAME) { unsigned int c; while ((c = read_byte()) != 0); } if (flags & GZIP_FLAG_COMM) { unsigned int c; while ((c = read_byte()) != 0); } unsigned int crc16 = 0; if (flags & GZIP_FLAG_HCRC) { crc16 = read_16le(); } (void)crc16; int status = deflate_decompress(); /* Read CRC and decompressed size from end of input */ unsigned int crc32 = read_32le(); unsigned int dsize = read_32le(); (void)crc32; (void)dsize; return status; } ================================================ FILE: kernel/misc/hashmap.c ================================================ /** * @brief Flexible mapping container. * @author K. Lange * * @copyright * 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-2021 K. Lange */ #include #include #include unsigned int hashmap_string_hash(const void * _key) { unsigned int hash = 0; char * key = (char *)_key; int c; /* This is the so-called "sdbm" hash. It comes from a piece of * public domain code from a clone of ndbm. */ while ((c = *key++)) { hash = c + (hash << 6) + (hash << 16) - hash; } return hash; } int hashmap_string_comp(const void * a, const void * b) { return !strcmp(a,b); } void * hashmap_string_dupe(const void * key) { return strdup(key); } unsigned int hashmap_int_hash(const void * key) { return (intptr_t)key; } int hashmap_int_comp(const void * a, const void * b) { return (intptr_t)a == (intptr_t)b; } void * hashmap_int_dupe(const void * key) { return (void*)key; } static void hashmap_int_free(void * ptr) { (void)ptr; return; } hashmap_t * hashmap_create(int size) { hashmap_t * map = malloc(sizeof(hashmap_t)); map->hash_func = &hashmap_string_hash; map->hash_comp = &hashmap_string_comp; map->hash_key_dup = &hashmap_string_dupe; map->hash_key_free = &free; map->hash_val_free = &free; map->size = size; map->entries = malloc(sizeof(hashmap_entry_t *) * size); memset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size); return map; } hashmap_t * hashmap_create_int(int size) { hashmap_t * map = malloc(sizeof(hashmap_t)); map->hash_func = &hashmap_int_hash; map->hash_comp = &hashmap_int_comp; map->hash_key_dup = &hashmap_int_dupe; map->hash_key_free = &hashmap_int_free; map->hash_val_free = &free; map->size = size; map->entries = malloc(sizeof(hashmap_entry_t *) * size); memset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size); return map; } void * hashmap_set(hashmap_t * map, const void * key, void * value) { unsigned int hash = map->hash_func(key) % map->size; hashmap_entry_t * x = map->entries[hash]; if (!x) { hashmap_entry_t * e = malloc(sizeof(hashmap_entry_t)); e->key = map->hash_key_dup(key); e->value = value; e->next = NULL; map->entries[hash] = e; return NULL; } else { hashmap_entry_t * p = NULL; do { if (map->hash_comp(x->key, key)) { void * out = x->value; x->value = value; return out; } else { p = x; x = x->next; } } while (x); hashmap_entry_t * e = malloc(sizeof(hashmap_entry_t)); e->key = map->hash_key_dup(key); e->value = value; e->next = NULL; p->next = e; return NULL; } } void * hashmap_get(hashmap_t * map, const void * key) { unsigned int hash = map->hash_func(key) % map->size; hashmap_entry_t * x = map->entries[hash]; if (!x) { return NULL; } else { do { if (map->hash_comp(x->key, key)) { return x->value; } x = x->next; } while (x); return NULL; } } void * hashmap_remove(hashmap_t * map, const void * key) { unsigned int hash = map->hash_func(key) % map->size; hashmap_entry_t * x = map->entries[hash]; if (!x) { return NULL; } else { if (map->hash_comp(x->key, key)) { void * out = x->value; map->entries[hash] = x->next; map->hash_key_free(x->key); map->hash_val_free(x); return out; } else { hashmap_entry_t * p = x; x = x->next; do { if (map->hash_comp(x->key, key)) { void * out = x->value; p->next = x->next; map->hash_key_free(x->key); map->hash_val_free(x); return out; } p = x; x = x->next; } while (x); } return NULL; } } int hashmap_has(hashmap_t * map, const void * key) { unsigned int hash = map->hash_func(key) % map->size; hashmap_entry_t * x = map->entries[hash]; if (!x) { return 0; } else { do { if (map->hash_comp(x->key, key)) { return 1; } x = x->next; } while (x); return 0; } } list_t * hashmap_keys(hashmap_t * map) { list_t * l = list_create("hashmap keys",map); for (unsigned int i = 0; i < map->size; ++i) { hashmap_entry_t * x = map->entries[i]; while (x) { list_insert(l, x->key); x = x->next; } } return l; } list_t * hashmap_values(hashmap_t * map) { list_t * l = list_create("hashmap values",map); for (unsigned int i = 0; i < map->size; ++i) { hashmap_entry_t * x = map->entries[i]; while (x) { list_insert(l, x->value); x = x->next; } } return l; } void hashmap_free(hashmap_t * map) { for (unsigned int i = 0; i < map->size; ++i) { hashmap_entry_t * x = map->entries[i], * p; while (x) { p = x; x = x->next; map->hash_key_free(p->key); map->hash_val_free(p); } } free(map->entries); } int hashmap_is_empty(hashmap_t * map) { for (unsigned int i = 0; i < map->size; ++i) { if (map->entries[i]) return 0; } return 1; } ================================================ FILE: kernel/misc/kprintf.c ================================================ /** * @file kprintf.c * @brief Kernel printf implementation. * * This is the newer, 64-bit-friendly printf originally implemented * for EFI builds of Kuroko. It was merged into the ToaruOS libc * and later became the kernel printf in Misaka. It supports the * standard set of width specifiers, '0' or ' ' padding, left or * right alignment, and the usermode version has a (rudimentary, * inaccurate) floating point printer. This kernel version excludes * float support. printf output is passed to callback functions * which can write the output to a string buffer or spit them * directly at an MMIO port. Support is also present for bounded * writes, and we've left @c sprintf out of the kernel entirely * in favor of @c snprintf. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2021 K. Lange */ #include #include #include size_t (*printf_output)(size_t, uint8_t *) = NULL; #define OUT(c) do { callback(userData, (c)); written++; } while (0) static 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) { size_t written = 0; unsigned long long n_width = 1; unsigned long long i = 9; if (precision == -1) precision = 1; if (value == 0) { n_width = 0; } else { unsigned long long val = value; while (val >= 10UL) { val /= 10UL; n_width++; } } if (n_width < (unsigned long long)precision) n_width = precision; int printed = 0; if (align_right) { while (n_width + printed < width) { OUT(fill_zero ? '0' : ' '); printed += 1; } i = n_width; char tmp[100]; while (i > 0) { unsigned long long n = value / 10; long long r = value % 10; tmp[i - 1] = r + '0'; i--; value = n; } while (i < n_width) { OUT(tmp[i]); i++; } } else { i = n_width; char tmp[100]; while (i > 0) { unsigned long long n = value / 10; long long r = value % 10; tmp[i - 1] = r + '0'; i--; value = n; printed++; } while (i < n_width) { OUT(tmp[i]); i++; } while (printed < (long long)width) { OUT(fill_zero ? '0' : ' '); printed += 1; } } return written; } /* * Hexadecimal to string */ static 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) { size_t written = 0; int i = width; unsigned long long n_width = 1; unsigned long long j = 0x0F; while (value > j && j < UINT64_MAX) { n_width += 1; j *= 0x10; j += 0x0F; } if (!fill_zero && align == 1) { while (i > (long long)n_width + 2*!!alt) { OUT(' '); i--; } } if (alt) { OUT('0'); OUT(caps ? 'X' : 'x'); } if (fill_zero && align == 1) { while (i > (long long)n_width + 2*!!alt) { OUT('0'); i--; } } i = (long long)n_width; while (i-- > 0) { char c = (caps ? "0123456789ABCDEF" : "0123456789abcdef")[(value>>(i*4))&0xF]; OUT(c); } if (align == 0) { i = width; while (i > (long long)n_width + 2*!!alt) { OUT(' '); i--; } } return written; } /* * vasprintf() */ size_t xvasprintf(int (*callback)(void *, char), void * userData, const char * fmt, va_list args) { const char * s; size_t written = 0; for (const char *f = fmt; *f; f++) { if (*f != '%') { OUT(*f); continue; } ++f; unsigned int arg_width = 0; int align = 1; /* right */ int fill_zero = 0; int big = 0; int alt = 0; int always_sign = 0; int precision = -1; while (1) { if (*f == '-') { align = 0; ++f; } else if (*f == '#') { alt = 1; ++f; } else if (*f == '*') { arg_width = (int)va_arg(args, int); ++f; } else if (*f == '0') { fill_zero = 1; ++f; } else if (*f == '+') { always_sign = 1; ++f; } else if (*f == ' ') { always_sign = 2; ++f; } else { break; } } while (*f >= '0' && *f <= '9') { arg_width *= 10; arg_width += *f - '0'; ++f; } if (*f == '.') { ++f; precision = 0; if (*f == '*') { precision = (int)va_arg(args, int); ++f; } else { while (*f >= '0' && *f <= '9') { precision *= 10; precision += *f - '0'; ++f; } } } if (*f == 'l') { big = 1; ++f; if (*f == 'l') { big = 2; ++f; } } if (*f == 'j') { big = (sizeof(uintmax_t) == sizeof(unsigned long long) ? 2 : sizeof(uintmax_t) == sizeof(unsigned long) ? 1 : 0); ++f; } if (*f == 'z') { big = (sizeof(size_t) == sizeof(unsigned long long) ? 2 : sizeof(size_t) == sizeof(unsigned long) ? 1 : 0); ++f; } if (*f == 't') { big = (sizeof(ptrdiff_t) == sizeof(unsigned long long) ? 2 : sizeof(ptrdiff_t) == sizeof(unsigned long) ? 1 : 0); ++f; } /* fmt[i] == '%' */ switch (*f) { case 's': /* String pointer -> String */ { size_t count = 0; if (big) { return written; } else { s = (char *)va_arg(args, char *); if (s == NULL) { s = "(null)"; } if (precision >= 0) { while (*s && precision > 0) { OUT(*s++); count++; precision--; if (arg_width && count == arg_width) break; } } else { while (*s) { OUT(*s++); count++; if (arg_width && count == arg_width) break; } } } while (count < arg_width) { OUT(' '); count++; } } break; case 'c': /* Single character */ OUT((char)va_arg(args,int)); break; case 'p': alt = 1; if (sizeof(void*) == sizeof(long long)) big = 2; /* fallthrough */ case 'X': case 'x': /* Hexadecimal number */ { unsigned long long val; if (big == 2) { val = (unsigned long long)va_arg(args, unsigned long long); } else if (big == 1) { val = (unsigned long)va_arg(args, unsigned long); } else { val = (unsigned int)va_arg(args, unsigned int); } written += print_hex(val, arg_width, callback, userData, fill_zero, alt, !(*f & 32), align); } break; case 'i': case 'd': /* Decimal number */ { long long val; if (big == 2) { val = (long long)va_arg(args, long long); } else if (big == 1) { val = (long)va_arg(args, long); } else { val = (int)va_arg(args, int); } if (val < 0) { OUT('-'); val = -val; } else if (always_sign) { OUT(always_sign == 2 ? ' ' : '+'); } written += print_dec(val, arg_width, callback, userData, fill_zero, align, precision); } break; case 'u': /* Unsigned ecimal number */ { unsigned long long val; if (big == 2) { val = (unsigned long long)va_arg(args, unsigned long long); } else if (big == 1) { val = (unsigned long)va_arg(args, unsigned long); } else { val = (unsigned int)va_arg(args, unsigned int); } written += print_dec(val, arg_width, callback, userData, fill_zero, align, precision); } break; case '%': /* Escape */ OUT('%'); break; default: /* Nothing at all, just dump it */ OUT(*f); break; } } return written; } struct CBData { char * str; size_t size; size_t written; }; static int cb_sprintf(void * user, char c) { struct CBData * data = user; if (data->size > data->written + 1) { data->str[data->written] = c; data->written++; if (data->written < data->size) { data->str[data->written] = '\0'; } } return 0; } int vsnprintf(char *str, size_t size, const char *format, va_list ap) { struct CBData data = {str,size,0}; int out = xvasprintf(cb_sprintf, &data, format, ap); cb_sprintf(&data, '\0'); return out; } int snprintf(char * str, size_t size, const char * format, ...) { struct CBData data = {str,size,0}; va_list args; va_start(args, format); int out = xvasprintf(cb_sprintf, &data, format, args); va_end(args); cb_sprintf(&data, '\0'); return out; } static int cb_printf(void * user, char c) { printf_output(1,(uint8_t*)&c); return 0; } int printf(const char * fmt, ...) { va_list args; va_start(args, fmt); int out = xvasprintf(cb_printf, NULL, fmt, args); va_end(args); return out; } ================================================ FILE: kernel/misc/ksym.c ================================================ /** * @file kernel/misc/ksym.c * @brief Kernel symbol table management. * * Essentially some wrappers around a hashmap; allows different * boot methods to provide symbol tables for use with linking * kernel modules. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include static hashmap_t * ksym_hash = NULL; void ksym_install(void) { assert(ksym_hash == NULL); ksym_hash = hashmap_create(20); } void ksym_bind(const char * symname, void * value) { assert(ksym_hash != NULL); hashmap_set(ksym_hash, symname, value); } void * ksym_lookup(const char * symname) { return hashmap_get(ksym_hash, symname); } list_t * ksym_list(void) { assert(ksym_hash != NULL); return hashmap_keys(ksym_hash); } hashmap_t * ksym_get_map(void) { return ksym_hash; } ================================================ FILE: kernel/misc/list.c ================================================ /** * @brief General-purpose list implementations. * @author K. Lange * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2021 K. Lange */ #include #include #include void list_destroy(list_t * list) { /* Free all of the contents of a list */ node_t * n = list->head; while (n) { free(n->value); n = n->next; } } void list_free(list_t * list) { /* Free the actual structure of a list */ node_t * n = list->head; while (n) { node_t * s = n->next; free(n); n = s; } } void list_append(list_t * list, node_t * node) { //assert(!(node->next || node->prev) && "Node is already in a list."); node->next = NULL; /* Insert a node onto the end of a list */ node->owner = list; if (!list->length) { list->head = node; list->tail = node; node->prev = NULL; node->next = NULL; list->length++; return; } list->tail->next = node; node->prev = list->tail; list->tail = node; list->length++; } node_t * list_insert(list_t * list, void * item) { /* Insert an item into a list */ node_t * node = malloc(sizeof(node_t)); node->value = item; node->next = NULL; node->prev = NULL; node->owner = NULL; list_append(list, node); return node; } void list_append_after(list_t * list, node_t * before, node_t * node) { //assert(!(node->next || node->prev) && "Node is already in a list."); node->owner = list; if (!list->length) { list_append(list, node); return; } if (before == NULL) { node->next = list->head; node->prev = NULL; list->head->prev = node; list->head = node; list->length++; return; } if (before == list->tail) { list->tail = node; } else { before->next->prev = node; node->next = before->next; } node->prev = before; before->next = node; list->length++; } node_t * list_insert_after(list_t * list, node_t * before, void * item) { node_t * node = malloc(sizeof(node_t)); node->value = item; node->next = NULL; node->prev = NULL; node->owner = NULL; list_append_after(list, before, node); return node; } void list_append_before(list_t * list, node_t * after, node_t * node) { //assert(!(node->next || node->prev) && "Node is already in a list."); node->owner = list; if (!list->length) { list_append(list, node); return; } if (after == NULL) { node->next = NULL; node->prev = list->tail; list->tail->next = node; list->tail = node; list->length++; return; } if (after == list->head) { list->head = node; } else { after->prev->next = node; node->prev = after->prev; } node->next = after; after->prev = node; list->length++; } node_t * list_insert_before(list_t * list, node_t * after, void * item) { node_t * node = malloc(sizeof(node_t)); node->value = item; node->next = NULL; node->prev = NULL; node->owner = NULL; list_append_before(list, after, node); return node; } list_t * list_create(const char * name, const void * metadata) { /* Create a fresh list */ list_t * out = malloc(sizeof(list_t)); out->head = NULL; out->tail = NULL; out->length = 0; out->name = name; out->metadata = metadata; return out; } node_t * list_find(list_t * list, void * value) { foreach(item, list) { if (item->value == value) { return item; } } return NULL; } int list_index_of(list_t * list, void * value) { int i = 0; foreach(item, list) { if (item->value == value) { return i; } i++; } return -1; /* not find */ } void * list_index(list_t * list, int index) { int i = 0; foreach(item, list) { if (i == index) return item->value; i++; } return NULL; } void list_remove(list_t * list, size_t index) { /* remove index from the list */ if (index > list->length) return; size_t i = 0; node_t * n = list->head; while (i < index) { n = n->next; i++; } list_delete(list, n); } void list_delete(list_t * list, node_t * node) { /* remove node from the list */ //assert(node->owner == list && "Tried to remove a list node from a list it does not belong to."); if (node == list->head) { list->head = node->next; } if (node == list->tail) { list->tail = node->prev; } if (node->prev) { node->prev->next = node->next; } if (node->next) { node->next->prev = node->prev; } node->prev = NULL; node->next = NULL; node->owner = NULL; list->length--; } node_t * list_pop(list_t * list) { /* Remove and return the last value in the list * If you don't need it, you still probably want to free it! * Try free(list_pop(list)); ! * */ if (!list->tail) return NULL; node_t * out = list->tail; list_delete(list, out); return out; } node_t * list_dequeue(list_t * list) { if (!list->head) return NULL; node_t * out = list->head; list_delete(list, out); return out; } list_t * list_copy(list_t * original) { /* Create a new copy of original */ list_t * out = list_create(original->name, original->metadata); node_t * node = original->head; while (node) { list_insert(out, node->value); } return out; } void list_merge(list_t * target, list_t * source) { /* Destructively merges source into target */ foreach(node, source) { node->owner = target; } if (source->head) { source->head->prev = target->tail; } if (target->tail) { target->tail->next = source->head; } else { target->head = source->head; } if (source->tail) { target->tail = source->tail; } target->length += source->length; free(source); } ================================================ FILE: kernel/misc/malloc.c ================================================ /** * @file kernel/misc/malloc.c * @brief klange's Slab Allocator * * This is one of the oldest parts of ToaruOS: the infamous heap allocator. * Used in userspace and the kernel alike, this is a straightforward "slab"- * style allocator. It has a handful of fixed sizes to stick small objects * in and keeps several together in a single page. It's surprisingly fast, * needs only an 'sbrk', makes only page-multiple calls to that sbrk, and * throwing a big lock around the whole thing seems to have worked just fine * for making it thread-safe in userspace applications (not necessarily * tested in the kernel). * * FIXME The heap allocator has long been lacking an ability to merge large * freed blocks. There's #if 0'd code dating back over a decade in here. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (c) 2010-2021 K. Lange. All rights reserved. * * Developed by: K. Lange * Dave Majnemer * Assocation for Computing Machinery * University of Illinois, Urbana-Champaign * http://acm.uiuc.edu */ /* Includes {{{ */ #include #include #include #include #include #include #include #include /* }}} */ /* Definitions {{{ */ /* * Defines for often-used integral values * related to our binning and paging strategy. */ #if defined(__x86_64__) || defined(__aarch64__) #define NUM_BINS 10U /* Number of bins, total, under 64-bit. */ #define SMALLEST_BIN_LOG 3U /* Logarithm base two of the smallest bin: log_2(sizeof(int64)). */ #else #define NUM_BINS 11U /* Number of bins, total, under 32-bit. */ #define SMALLEST_BIN_LOG 2U /* Logarithm base two of the smallest bin: log_2(sizeof(int32)). */ #endif #define BIG_BIN (NUM_BINS - 1) /* Index for the big bin, (NUM_BINS - 1) */ #define SMALLEST_BIN (1UL << SMALLEST_BIN_LOG) /* Size of the smallest bin. */ #define PAGE_SIZE 0x1000 /* Size of a page (in bytes), should be 4KB */ #define PAGE_MASK (PAGE_SIZE - 1) /* Block mask, size of a page * number of pages - 1. */ #define SKIP_P INT32_MAX /* INT32_MAX is half of UINT32_MAX; this gives us a 50% marker for skip lists. */ #define SKIP_MAX_LEVEL 6 /* We have a maximum of 6 levels in our skip lists. */ #define BIN_MAGIC 0xDEFAD00D #if 1 #define assert(statement) ((statement) ? (void)0 : __assert_fail(__FILE__, __LINE__, #statement)) #else #define assert(statement) (void)0 #endif static void __assert_fail(const char * f, int l, const char * stmt) { arch_fatal_prepare(); dprintf("assertion failed in %s:%d %s\n", f, l, stmt); arch_dump_traceback(); arch_fatal(); } /* }}} */ /* * Internal functions. */ static void * __attribute__ ((malloc)) klmalloc(uintptr_t size); static void * __attribute__ ((malloc)) klrealloc(void * ptr, uintptr_t size); static void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size); static void * __attribute__ ((malloc)) klvalloc(uintptr_t size); static void klfree(void * ptr); static spin_lock_t mem_lock = { 0 }; void * __attribute__ ((malloc)) malloc(uintptr_t size) { spin_lock(mem_lock); void * out = klmalloc(size); spin_unlock(mem_lock); return out; } void * __attribute__ ((malloc)) realloc(void * ptr, uintptr_t size) { spin_lock(mem_lock); void * out = klrealloc(ptr, size); spin_unlock(mem_lock); return out; } void * __attribute__ ((malloc)) calloc(uintptr_t nmemb, uintptr_t size) { spin_lock(mem_lock); void * out = klcalloc(nmemb, size); spin_unlock(mem_lock); return out; } void * __attribute__ ((malloc)) valloc(uintptr_t size) { spin_lock(mem_lock); void * out = klvalloc(size); spin_unlock(mem_lock); return out; } void free(void * ptr) { spin_lock(mem_lock); #ifndef __aarch64__ if (ptr < (void*)0xffffff0000000000) { printf("Invalid free detected (%p)\n", ptr); while (1) {}; } #endif klfree(ptr); spin_unlock(mem_lock); } /* Bin management {{{ */ /* * Adjust bin size in bin_size call to proper bounds. */ static inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_adjust_bin(uintptr_t bin) { if (bin <= (uintptr_t)SMALLEST_BIN_LOG) { return 0; } bin -= SMALLEST_BIN_LOG + 1; if (bin > (uintptr_t)BIG_BIN) { return BIG_BIN; } return bin; } /* * Given a size value, find the correct bin * to place the requested allocation in. */ static inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_bin_size(uintptr_t size) { uintptr_t bin = sizeof(size) * CHAR_BIT - __builtin_clzl(size); bin += !!(size & (size - 1)); return klmalloc_adjust_bin(bin); } /* * Bin header - One page of memory. * Appears at the front of a bin to point to the * previous bin (or NULL if the first), the next bin * (or NULL if the last) and the head of the bin, which * is a stack of cells of data. */ typedef struct _klmalloc_bin_header { struct _klmalloc_bin_header * next; /* Pointer to the next node. */ void * head; /* Head of this bin. */ uintptr_t size; /* Size of this bin, if big; otherwise bin index. */ uintptr_t bin_magic; } klmalloc_bin_header; /* * A big bin header is basically the same as a regular bin header * only with a pointer to the previous (physically) instead of * a "next" and with a list of forward headers. */ typedef struct _klmalloc_big_bin_header { struct _klmalloc_big_bin_header * next; void * head; uintptr_t size; uintptr_t bin_magic; struct _klmalloc_big_bin_header * prev; struct _klmalloc_big_bin_header * forward[SKIP_MAX_LEVEL+1]; } klmalloc_big_bin_header; /* * List of pages in a bin. */ typedef struct _klmalloc_bin_header_head { klmalloc_bin_header * first; } klmalloc_bin_header_head; /* * Array of available bins. */ static klmalloc_bin_header_head klmalloc_bin_head[NUM_BINS - 1]; /* Small bins */ static struct _klmalloc_big_bins { klmalloc_big_bin_header head; int level; } klmalloc_big_bins; static klmalloc_big_bin_header * klmalloc_newest_big = NULL; /* Newest big bin */ /* }}} Bin management */ /* Doubly-Linked List {{{ */ /* * Remove an entry from a page list. * Decouples the element from its * position in the list by linking * its neighbors to eachother. */ static inline void __attribute__ ((always_inline)) klmalloc_list_decouple(klmalloc_bin_header_head *head, klmalloc_bin_header *node) { klmalloc_bin_header *next = node->next; head->first = next; node->next = NULL; } /* * Insert an entry into a page list. * The new entry is placed at the front * of the list and the existing border * elements are updated to point back * to it (our list is doubly linked). */ static inline void __attribute__ ((always_inline)) klmalloc_list_insert(klmalloc_bin_header_head *head, klmalloc_bin_header *node) { node->next = head->first; head->first = node; } /* * Get the head of a page list. * Because redundant function calls * are really great, and just in case * we change the list implementation. */ static inline klmalloc_bin_header * __attribute__ ((always_inline)) klmalloc_list_head(klmalloc_bin_header_head *head) { return head->first; } /* }}} Lists */ /* Skip List {{{ */ /* * Skip lists are efficient * data structures for storing * and searching ordered data. * * Here, the skip lists are used * to keep track of big bins. */ /* * Generate a random value in an appropriate range. * This is a xor-shift RNG. */ static uint32_t __attribute__ ((pure)) klmalloc_skip_rand(void) { static uint32_t x = 123456789; static uint32_t y = 362436069; static uint32_t z = 521288629; static uint32_t w = 88675123; uint32_t t; t = x ^ (x << 11); x = y; y = z; z = w; return w = w ^ (w >> 19) ^ t ^ (t >> 8); } /* * Generate a random level for a skip node */ static inline int __attribute__ ((pure, always_inline)) klmalloc_random_level(void) { int level = 0; /* * Keep trying to check rand() against 50% of its maximum. * This provides 50%, 25%, 12.5%, etc. chance for each level. */ while (klmalloc_skip_rand() < SKIP_P && level < SKIP_MAX_LEVEL) { ++level; } return level; } /* * Find best fit for a given value. */ static klmalloc_big_bin_header * klmalloc_skip_list_findbest(uintptr_t search_size) { klmalloc_big_bin_header * node = &klmalloc_big_bins.head; /* * Loop through the skip list until we hit something > our search value. */ int i; for (i = klmalloc_big_bins.level; i >= 0; --i) { while (node->forward[i] && (node->forward[i]->size < search_size)) { node = node->forward[i]; if (node) assert((node->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); } } /* * This value will either be NULL (we found nothing) * or a node (we found a minimum fit). */ node = node->forward[0]; if (node) { assert((uintptr_t)node % PAGE_SIZE == 0); assert((node->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); } return node; } /* * Insert a header into the skip list. */ static void klmalloc_skip_list_insert(klmalloc_big_bin_header * value) { /* * You better be giving me something valid to insert, * or I will slit your ****ing throat. */ assert(value != NULL); assert(value->head != NULL); assert((uintptr_t)value->head > (uintptr_t)value); if (value->size > NUM_BINS) { assert((uintptr_t)value->head < (uintptr_t)value + value->size); } else { assert((uintptr_t)value->head < (uintptr_t)value + PAGE_SIZE); } assert((uintptr_t)value % PAGE_SIZE == 0); assert((value->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); assert(value->size != 0); /* * Starting from the head node of the bin locator... */ klmalloc_big_bin_header * node = &klmalloc_big_bins.head; klmalloc_big_bin_header * update[SKIP_MAX_LEVEL + 1]; /* * Loop through the skiplist to find the right place * to insert the node (where ->forward[] > value) */ int i; for (i = klmalloc_big_bins.level; i >= 0; --i) { while (node->forward[i] && node->forward[i]->size < value->size) { node = node->forward[i]; if (node) assert((node->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); } update[i] = node; } node = node->forward[0]; /* * Make the new skip node and update * the forward values. */ if (node != value) { int level = klmalloc_random_level(); /* * Get all of the nodes before this. */ if (level > klmalloc_big_bins.level) { for (i = klmalloc_big_bins.level + 1; i <= level; ++i) { update[i] = &klmalloc_big_bins.head; } klmalloc_big_bins.level = level; } /* * Make the new node. */ node = value; /* * Run through and point the preceeding nodes * for each level to the new node. */ for (i = 0; i <= level; ++i) { node->forward[i] = update[i]->forward[i]; if (node->forward[i]) assert((node->forward[i]->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); update[i]->forward[i] = node; } } } /* * Delete a header from the skip list. * Be sure you didn't change the size, or we won't be able to find it. */ static void klmalloc_skip_list_delete(klmalloc_big_bin_header * value) { /* * Debug assertions */ assert(value != NULL); assert(value->head); assert((uintptr_t)value->head > (uintptr_t)value); if (value->size > NUM_BINS) { assert((uintptr_t)value->head < (uintptr_t)value + value->size); } else { assert((uintptr_t)value->head < (uintptr_t)value + PAGE_SIZE); } /* * Starting from the bin header, again... */ klmalloc_big_bin_header * node = &klmalloc_big_bins.head; klmalloc_big_bin_header * update[SKIP_MAX_LEVEL + 1]; /* * Find the node. */ int i; for (i = klmalloc_big_bins.level; i >= 0; --i) { while (node->forward[i] && node->forward[i]->size < value->size) { node = node->forward[i]; if (node) assert((node->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); } update[i] = node; } node = node->forward[0]; while (node != value) { node = node->forward[0]; } if (node != value) { node = klmalloc_big_bins.head.forward[0]; while (node->forward[0] && node->forward[0] != value) { node = node->forward[0]; } node = node->forward[0]; } /* * If we found the node, delete it; * otherwise, we do nothing. */ if (node == value) { for (i = 0; i <= klmalloc_big_bins.level; ++i) { if (update[i]->forward[i] != node) { break; } update[i]->forward[i] = node->forward[i]; if (update[i]->forward[i]) { assert((uintptr_t)(update[i]->forward[i]) % PAGE_SIZE == 0); assert((update[i]->forward[i]->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); } } while (klmalloc_big_bins.level > 0 && klmalloc_big_bins.head.forward[klmalloc_big_bins.level] == NULL) { --klmalloc_big_bins.level; } } } /* }}} */ /* Stack {{{ */ /* * Pop an item from a block. * Free space is stored as a stack, * so we get a free space for a bin * by popping a free node from the * top of the stack. */ static void * klmalloc_stack_pop(klmalloc_bin_header *header) { assert(header); assert(header->head != NULL); assert((uintptr_t)header->head > (uintptr_t)header); if (header->size > NUM_BINS) { assert((uintptr_t)header->head < (uintptr_t)header + header->size); } else { assert((uintptr_t)header->head < (uintptr_t)header + PAGE_SIZE); assert((uintptr_t)header->head > (uintptr_t)header + sizeof(klmalloc_bin_header) - 1); } /* * Remove the current head and point * the head to where the old head pointed. */ void *item = header->head; uintptr_t **head = header->head; uintptr_t *next = *head; header->head = next; return item; } /* * Push an item into a block. * When we free memory, we need * to add the freed cell back * into the stack of free spaces * for the block. */ static void klmalloc_stack_push(klmalloc_bin_header *header, void *ptr) { assert(ptr != NULL); assert((uintptr_t)ptr > (uintptr_t)header); if (header->size > NUM_BINS) { assert((uintptr_t)ptr < (uintptr_t)header + header->size); } else { assert((((uintptr_t)ptr - sizeof(klmalloc_bin_header)) & ((1UL << (header->size + SMALLEST_BIN_LOG)) - 1)) == 0); assert((uintptr_t)ptr < (uintptr_t)header + PAGE_SIZE); } uintptr_t **item = (uintptr_t **)ptr; *item = (uintptr_t *)header->head; header->head = item; } /* * Is this cell stack empty? * If the head of the stack points * to NULL, we have exhausted the * stack, so there is no more free * space available in the block. */ static inline int __attribute__ ((always_inline)) klmalloc_stack_empty(klmalloc_bin_header *header) { return header->head == NULL; } /* }}} Stack */ /* malloc() {{{ */ static void * __attribute__ ((malloc)) klmalloc(uintptr_t size) { /* * C standard implementation: * If size is zero, we can choose do a number of things. * This implementation will return a NULL pointer. */ if (__builtin_expect(size == 0, 0)) return NULL; /* * Find the appropriate bin for the requested * allocation and start looking through that list. */ unsigned int bucket_id = klmalloc_bin_size(size); if (bucket_id < BIG_BIN) { /* * Small bins. */ klmalloc_bin_header * bin_header = klmalloc_list_head(&klmalloc_bin_head[bucket_id]); if (!bin_header) { /* * Grow the heap for the new bin. */ bin_header = (klmalloc_bin_header*)sbrk(PAGE_SIZE); bin_header->bin_magic = BIN_MAGIC; assert((uintptr_t)bin_header % PAGE_SIZE == 0); /* * Set the head of the stack. */ bin_header->head = (void*)((uintptr_t)bin_header + sizeof(klmalloc_bin_header)); /* * Insert the new bin at the front of * the list of bins for this size. */ klmalloc_list_insert(&klmalloc_bin_head[bucket_id], bin_header); /* * Initialize the stack inside the bin. * The stack is initially full, with each * entry pointing to the next until the end * which points to NULL. */ uintptr_t adj = SMALLEST_BIN_LOG + bucket_id; uintptr_t i, available = ((PAGE_SIZE - sizeof(klmalloc_bin_header)) >> adj) - 1; uintptr_t **base = bin_header->head; for (i = 0; i < available; ++i) { /* * Our available memory is made into a stack, with each * piece of memory turned into a pointer to the next * available piece. When we want to get a new piece * of memory from this block, we just pop off a free * spot and give its address. */ base[i << bucket_id] = (uintptr_t *)&base[(i + 1) << bucket_id]; } base[available << bucket_id] = NULL; bin_header->size = bucket_id; } else { assert(bin_header->bin_magic == BIN_MAGIC); } uintptr_t ** item = klmalloc_stack_pop(bin_header); if (klmalloc_stack_empty(bin_header)) { klmalloc_list_decouple(&(klmalloc_bin_head[bucket_id]),bin_header); } return item; } else { /* * Big bins. */ klmalloc_big_bin_header * bin_header = klmalloc_skip_list_findbest(size); if (bin_header) { assert(bin_header->size >= size); /* * If we found one, delete it from the skip list */ klmalloc_skip_list_delete(bin_header); /* * Retreive the head of the block. */ uintptr_t ** item = klmalloc_stack_pop((klmalloc_bin_header *)bin_header); #if 0 /* * Resize block, if necessary */ assert(bin_header->head == NULL); uintptr_t old_size = bin_header->size; //uintptr_t rsize = size; /* * Round the requeste size to our full required size. */ size = ((size + sizeof(klmalloc_big_bin_header)) / PAGE_SIZE + 1) * PAGE_SIZE - sizeof(klmalloc_big_bin_header); assert((size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); if (bin_header->size > size * 2) { assert(old_size != size); /* * If we have extra space, start splitting. */ bin_header->size = size; assert(sbrk(0) >= bin_header->size + (uintptr_t)bin_header); /* * Make a new block at the end of the needed space. */ klmalloc_big_bin_header * header_new = (klmalloc_big_bin_header *)((uintptr_t)bin_header + sizeof(klmalloc_big_bin_header) + size); assert((uintptr_t)header_new % PAGE_SIZE == 0); memset(header_new, 0, sizeof(klmalloc_big_bin_header) + sizeof(void *)); header_new->prev = bin_header; if (bin_header->next) { bin_header->next->prev = header_new; } header_new->next = bin_header->next; bin_header->next = header_new; if (klmalloc_newest_big == bin_header) { klmalloc_newest_big = header_new; } header_new->size = old_size - (size + sizeof(klmalloc_big_bin_header)); assert(((uintptr_t)header_new->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); fprintf(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); /* * Free the new block. */ klfree((void *)((uintptr_t)header_new + sizeof(klmalloc_big_bin_header))); } #endif return item; } else { /* * Round requested size to a set of pages, plus the header size. */ uintptr_t pages = (size + sizeof(klmalloc_big_bin_header)) / PAGE_SIZE + 1; bin_header = (klmalloc_big_bin_header*)sbrk(PAGE_SIZE * pages); bin_header->bin_magic = BIN_MAGIC; assert((uintptr_t)bin_header % PAGE_SIZE == 0); /* * Give the header the remaining space. */ bin_header->size = pages * PAGE_SIZE - sizeof(klmalloc_big_bin_header); assert((bin_header->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); /* * Link the block in physical memory. */ bin_header->prev = klmalloc_newest_big; if (bin_header->prev) { bin_header->prev->next = bin_header; } klmalloc_newest_big = bin_header; bin_header->next = NULL; /* * Return the head of the block. */ bin_header->head = NULL; return (void*)((uintptr_t)bin_header + sizeof(klmalloc_big_bin_header)); } } } /* }}} */ /* free() {{{ */ static void klfree(void *ptr) { /* * C standard implementation: Do nothing when NULL is passed to free. */ if (__builtin_expect(ptr == NULL, 0)) { return; } /* * Woah, woah, hold on, was this a page-aligned block? */ if ((uintptr_t)ptr % PAGE_SIZE == 0) { /* * Well howdy-do, it was. */ ptr = (void *)((uintptr_t)ptr - 1); } /* * Get our pointer to the head of this block by * page aligning it. */ klmalloc_bin_header * header = (klmalloc_bin_header *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK); assert((uintptr_t)header % PAGE_SIZE == 0); if (header->bin_magic != BIN_MAGIC) return; /* * For small bins, the bin number is stored in the size * field of the header. For large bins, the actual size * available in the bin is stored in this field. It's * easy to tell which is which, though. */ uintptr_t bucket_id = header->size; if (bucket_id > (uintptr_t)NUM_BINS) { bucket_id = BIG_BIN; klmalloc_big_bin_header *bheader = (klmalloc_big_bin_header*)header; assert(bheader); assert(bheader->head == NULL); assert((bheader->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); /* * Coalesce forward blocks into us. */ #if 0 if (bheader != klmalloc_newest_big) { /* * If we are not the newest big bin, there is most definitely * something in front of us that we can read. */ assert((bheader->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); klmalloc_big_bin_header * next = (void *)((uintptr_t)bheader + sizeof(klmalloc_big_bin_header) + bheader->size); assert((uintptr_t)next % PAGE_SIZE == 0); if (next == bheader->next && next->head) { //next->size > NUM_BINS && next->head) { /* * If that something is an available big bin, we can * coalesce it into us to form one larger bin. */ uintptr_t old_size = bheader->size; klmalloc_skip_list_delete(next); bheader->size = (uintptr_t)bheader->size + (uintptr_t)sizeof(klmalloc_big_bin_header) + next->size; assert((bheader->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); if (next == klmalloc_newest_big) { /* * If the guy in front of us was the newest, * we are now the newest (as we are him). */ klmalloc_newest_big = bheader; } else { if (next->next) { next->next->prev = bheader; } } fprintf(stderr,"Coelesced (forwards) %p [%zx] <- %p [%zx] = %zx\n", (void*)bheader, old_size, (void*)next, next->size, bheader->size); } } #endif /* * Coalesce backwards */ #if 0 if (bheader->prev && bheader->prev->head) { /* * If there is something behind us, it is available, and there is nothing between * it and us, we can coalesce ourselves into it to form a big block. */ if ((uintptr_t)bheader->prev + (bheader->prev->size + sizeof(klmalloc_big_bin_header)) == (uintptr_t)bheader) { uintptr_t old_size = bheader->prev->size; klmalloc_skip_list_delete(bheader->prev); bheader->prev->size = (uintptr_t)bheader->prev->size + (uintptr_t)bheader->size + sizeof(klmalloc_big_bin_header); assert((bheader->prev->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); klmalloc_skip_list_insert(bheader->prev); if (klmalloc_newest_big == bheader) { klmalloc_newest_big = bheader->prev; } else { if (bheader->next) { bheader->next->prev = bheader->prev; } } fprintf(stderr,"Coelesced (backwards) %p [%zx] <- %p [%zx] = %zx\n", (void*)bheader->prev, old_size, (void*)bheader, bheader->size, bheader->size); /* * If we coalesced backwards, we are done. */ return; } } #endif /* * Push new space back into the stack. */ klmalloc_stack_push((klmalloc_bin_header *)bheader, (void *)((uintptr_t)bheader + sizeof(klmalloc_big_bin_header))); assert(bheader->head != NULL); /* * Insert the block into list of available slabs. */ klmalloc_skip_list_insert(bheader); } else { /* * If the stack is empty, we are freeing * a block from a previously full bin. * Return it to the busy bins list. */ if (klmalloc_stack_empty(header)) { klmalloc_list_insert(&klmalloc_bin_head[bucket_id], header); } /* * Push new space back into the stack. */ klmalloc_stack_push(header, ptr); } } /* }}} */ /* valloc() {{{ */ static void * __attribute__ ((malloc)) klvalloc(uintptr_t size) { /* * Allocate a page-aligned block. * XXX: THIS IS HORRIBLY, HORRIBLY WASTEFUL!! ONLY USE THIS * IF YOU KNOW WHAT YOU ARE DOING! */ uintptr_t true_size = size + PAGE_SIZE - sizeof(klmalloc_big_bin_header); /* Here we go... */ void * result = klmalloc(true_size); void * out = (void *)((uintptr_t)result + (PAGE_SIZE - sizeof(klmalloc_big_bin_header))); assert((uintptr_t)out % PAGE_SIZE == 0); return out; } /* }}} */ /* realloc() {{{ */ static void * __attribute__ ((malloc)) klrealloc(void *ptr, uintptr_t size) { /* * C standard implementation: When NULL is passed to realloc, * simply malloc the requested size and return a pointer to that. */ if (__builtin_expect(ptr == NULL, 0)) return klmalloc(size); /* * C standard implementation: For a size of zero, free the * pointer and return NULL, allocating no new memory. */ if (__builtin_expect(size == 0, 0)) { free(ptr); return NULL; } /* * Find the bin for the given pointer * by aligning it to a page. */ klmalloc_bin_header * header_old = (void *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK); if (header_old->bin_magic != BIN_MAGIC) { assert(0 && "Bad magic on realloc."); return NULL; } uintptr_t old_size = header_old->size; if (old_size < (uintptr_t)BIG_BIN) { /* * If we are copying from a small bin, * we need to get the size of the bin * from its id. */ old_size = (1UL << (SMALLEST_BIN_LOG + old_size)); } /* * (This will only happen for a big bin, mathematically speaking) * If we still have room in our bin for the additonal space, * we don't need to do anything. */ if (old_size >= size) { /* * TODO: Break apart blocks here, which is far more important * than breaking them up on allocations. */ return ptr; } /* * Reallocate more memory. */ void * newptr = klmalloc(size); if (__builtin_expect(newptr != NULL, 1)) { /* * Copy the old value into the new value. * Be sure to only copy as much as was in * the old block. */ memcpy(newptr, ptr, old_size); klfree(ptr); return newptr; } /* * We failed to allocate more memory, * which means we're probably out. * * Bail and return NULL. */ return NULL; } /* }}} */ /* calloc() {{{ */ static void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size) { /* * Allocate memory and zero it before returning * a pointer to the newly allocated memory. * * Implemented by way of a simple malloc followed * by a memset to 0x00 across the length of the * requested memory chunk. */ void *ptr = klmalloc(nmemb * size); if (__builtin_expect(ptr != NULL, 1)) memset(ptr,0x00,nmemb * size); return ptr; } /* }}} */ ================================================ FILE: kernel/misc/pci.c ================================================ /** * @file kernel/misc/pci.c * @brief PCI configuration and scanning. * * Functions for dealing with PCI devices through configuration mode #1 * (CPU port I/O methods), including scanning and modifying device * configuration bytes. * * This used to have methods for dealing with ISA bridge IRQ remapping, * but it has been removed for the moment. * * TODO: Implement MSI configuration? * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2021 K. Lange */ #include #include #include #include #include #ifdef __x86_64__ #include #endif static uintptr_t pcie_addr(uint32_t device, int field) { return (pci_extract_bus(device) << 20) | (pci_extract_slot(device) << 15) | (pci_extract_func(device) << 12) | (field); } uintptr_t pcie_ecam_phys = 0x3f000000; /** * @brief Write to a PCI device configuration space field. */ void pci_write_field(uint32_t device, int field, int size, uint32_t value) { #ifdef __x86_64__ outportl(PCI_ADDRESS_PORT, pci_get_addr(device, field)); outportl(PCI_VALUE_PORT, value); #else /* ECAM space */ if (size == 4) { *(volatile uint32_t*)mmu_map_from_physical(pcie_ecam_phys + pcie_addr(device,field)) = value; return; } else if (size == 2) { *(volatile uint16_t*)mmu_map_from_physical(pcie_ecam_phys + pcie_addr(device,field)) = value; return; } else if (size == 1) { *(volatile uint8_t*)mmu_map_from_physical(pcie_ecam_phys + pcie_addr(device,field)) = value; return; } dprintf("rejected invalid field write\n"); #endif } /** * @brief Read from a PCI device configuration space field. */ uint32_t pci_read_field(uint32_t device, int field, int size) { #ifdef __x86_64__ outportl(PCI_ADDRESS_PORT, pci_get_addr(device, field)); if (size == 4) { uint32_t t = inportl(PCI_VALUE_PORT); return t; } else if (size == 2) { uint16_t t = inports(PCI_VALUE_PORT + (field & 2)); return t; } else if (size == 1) { uint8_t t = inportb(PCI_VALUE_PORT + (field & 3)); return t; } #else uintptr_t field_addr = pcie_addr(device,field); if (size == 4) { return *(volatile uint32_t*)mmu_map_from_physical(pcie_ecam_phys + field_addr); } else if (size == 2) { return *(volatile uint16_t*)mmu_map_from_physical(pcie_ecam_phys + field_addr); } else if (size == 1) { return *(volatile uint8_t*)mmu_map_from_physical(pcie_ecam_phys + field_addr); } #endif return 0xFFFF; } /** * @brief Obtain the device type from the class and subclass fields. */ uint16_t pci_find_type(uint32_t dev) { return (pci_read_field(dev, PCI_CLASS, 1) << 8) | pci_read_field(dev, PCI_SUBCLASS, 1); } static void pci_scan_hit(pci_func_t f, uint32_t dev, void * extra) { int dev_vend = (int)pci_read_field(dev, PCI_VENDOR_ID, 2); int dev_dvid = (int)pci_read_field(dev, PCI_DEVICE_ID, 2); f(dev, dev_vend, dev_dvid, extra); } void pci_scan_func(pci_func_t f, int type, int bus, int slot, int func, void * extra) { uint32_t dev = pci_box_device(bus, slot, func); if (type == -1 || type == pci_find_type(dev)) { pci_scan_hit(f, dev, extra); } if (pci_find_type(dev) == PCI_TYPE_BRIDGE) { pci_scan_bus(f, type, pci_read_field(dev, PCI_SECONDARY_BUS, 1), extra); } } void pci_scan_slot(pci_func_t f, int type, int bus, int slot, void * extra) { uint32_t dev = pci_box_device(bus, slot, 0); if (pci_read_field(dev, PCI_VENDOR_ID, 2) == PCI_NONE) { return; } pci_scan_func(f, type, bus, slot, 0, extra); if (!pci_read_field(dev, PCI_HEADER_TYPE, 1)) { return; } for (int func = 1; func < 8; func++) { uint32_t dev = pci_box_device(bus, slot, func); if (pci_read_field(dev, PCI_VENDOR_ID, 2) != PCI_NONE) { pci_scan_func(f, type, bus, slot, func, extra); } } } void pci_scan_bus(pci_func_t f, int type, int bus, void * extra) { for (int slot = 0; slot < 32; ++slot) { pci_scan_slot(f, type, bus, slot, extra); } } /** * @brief Scan PCI buses for devices, calling the given function for each device. * * Used by drivers to implement device discovery, runs a callback function for ever * device found. A device consists of a bus, slot, and function. Also performs * recursive scans of bridges. */ void pci_scan(pci_func_t f, int type, void * extra) { if ((pci_read_field(0, PCI_HEADER_TYPE, 1) & 0x80) == 0) { pci_scan_bus(f,type,0,extra); return; } int hit = 0; for (int func = 0; func < 8; ++func) { uint32_t dev = pci_box_device(0, 0, func); if (pci_read_field(dev, PCI_VENDOR_ID, 2) != PCI_NONE) { hit = 1; pci_scan_bus(f, type, func, extra); } else { break; } } if (!hit) { for (int bus = 0; bus < 256; ++bus) { for (int slot = 0; slot < 32; ++slot) { pci_scan_slot(f,type,bus,slot,extra); } } } } int pci_get_interrupt(uint32_t device) { return pci_read_field(device, PCI_INTERRUPT_LINE, 1); } ================================================ FILE: kernel/misc/ringbuffer.c ================================================ /** * @file kernel/misc/ringbuffer.c * @brief Generic ringbuffer with blocking reads and writes. * * Provides a buffer interface for devices such as at PTYs with * blocking reads and writes. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include size_t ring_buffer_unread(ring_buffer_t * ring_buffer) { if (ring_buffer->read_ptr == ring_buffer->write_ptr) { return 0; } if (ring_buffer->read_ptr > ring_buffer->write_ptr) { return (ring_buffer->size - ring_buffer->read_ptr) + ring_buffer->write_ptr; } else { return (ring_buffer->write_ptr - ring_buffer->read_ptr); } } size_t ring_buffer_size(fs_node_t * node) { ring_buffer_t * ring_buffer = (ring_buffer_t *)node->device; return ring_buffer_unread(ring_buffer); } size_t ring_buffer_available(ring_buffer_t * ring_buffer) { if (ring_buffer->read_ptr == ring_buffer->write_ptr) { return ring_buffer->size - 1; } if (ring_buffer->read_ptr > ring_buffer->write_ptr) { return ring_buffer->read_ptr - ring_buffer->write_ptr - 1; } else { return (ring_buffer->size - ring_buffer->write_ptr) + ring_buffer->read_ptr - 1; } } static inline void ring_buffer_increment_read(ring_buffer_t * ring_buffer) { ring_buffer->read_ptr++; if (ring_buffer->read_ptr == ring_buffer->size) { ring_buffer->read_ptr = 0; } } static inline void ring_buffer_increment_write(ring_buffer_t * ring_buffer) { ring_buffer->write_ptr++; if (ring_buffer->write_ptr == ring_buffer->size) { ring_buffer->write_ptr = 0; } } void ring_buffer_alert_waiters(ring_buffer_t * ring_buffer) { if (ring_buffer->alert_waiters) { while (ring_buffer->alert_waiters->head) { node_t * node = list_dequeue(ring_buffer->alert_waiters); process_t * p = node->value; process_alert_node(p, ring_buffer); free(node); } } } void ring_buffer_select_wait(ring_buffer_t * ring_buffer, void * process) { if (!ring_buffer->alert_waiters) { ring_buffer->alert_waiters = list_create("ringbuffer alerts", ring_buffer); } if (!list_find(ring_buffer->alert_waiters, process)) { list_insert(ring_buffer->alert_waiters, process); } list_insert(((process_t *)process)->node_waits, ring_buffer); } void ring_buffer_discard(ring_buffer_t * ring_buffer) { spin_lock(ring_buffer->lock); ring_buffer->read_ptr = ring_buffer->write_ptr; spin_unlock(ring_buffer->lock); } size_t ring_buffer_read(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer) { size_t collected = 0; while (collected == 0) { spin_lock(ring_buffer->lock); while (ring_buffer_unread(ring_buffer) > 0 && collected < size) { buffer[collected] = ring_buffer->buffer[ring_buffer->read_ptr]; ring_buffer_increment_read(ring_buffer); collected++; } wakeup_queue(ring_buffer->wait_queue_writers); if (collected == 0) { if (ring_buffer->internal_stop || ring_buffer->soft_stop) { ring_buffer->soft_stop = 0; spin_unlock(ring_buffer->lock); return 0; } if (sleep_on_unlocking(ring_buffer->wait_queue_readers, &ring_buffer->lock)) { return -ERESTARTSYS; } } else { spin_unlock(ring_buffer->lock); } } wakeup_queue(ring_buffer->wait_queue_writers); return collected; } size_t ring_buffer_write(ring_buffer_t * ring_buffer, size_t size, uint8_t * buffer) { size_t written = 0; while (written < size) { spin_lock(ring_buffer->lock); while (ring_buffer_available(ring_buffer) > 0 && written < size) { ring_buffer->buffer[ring_buffer->write_ptr] = buffer[written]; ring_buffer_increment_write(ring_buffer); written++; } wakeup_queue(ring_buffer->wait_queue_readers); ring_buffer_alert_waiters(ring_buffer); if (written < size) { if (ring_buffer->discard) { spin_unlock(ring_buffer->lock); break; } if (sleep_on_unlocking(ring_buffer->wait_queue_writers, &ring_buffer->lock)) { if (!written) return -ERESTARTSYS; break; } if (ring_buffer->internal_stop) { break; } } else { spin_unlock(ring_buffer->lock); } } wakeup_queue(ring_buffer->wait_queue_readers); ring_buffer_alert_waiters(ring_buffer); return written; } ring_buffer_t * ring_buffer_create(size_t size) { ring_buffer_t * out = malloc(sizeof(ring_buffer_t)); if (size == 4096) { out->buffer = mmu_map_from_physical(mmu_allocate_a_frame() << 12); } else { out->buffer = malloc(size); } out->write_ptr = 0; out->read_ptr = 0; out->size = size; out->alert_waiters = NULL; spin_init(out->lock); out->internal_stop = 0; out->discard = 0; out->soft_stop = 0; out->wait_queue_readers = list_create("ringbuffer readers",out); out->wait_queue_writers = list_create("ringbuffer writers",out); return out; } void ring_buffer_destroy(ring_buffer_t * ring_buffer) { if (ring_buffer->size == 4096) { mmu_frame_release((uintptr_t)ring_buffer->buffer & 0xFFFFFFFFF); } else { free(ring_buffer->buffer); } wakeup_queue(ring_buffer->wait_queue_writers); wakeup_queue(ring_buffer->wait_queue_readers); ring_buffer_alert_waiters(ring_buffer); list_free(ring_buffer->wait_queue_writers); list_free(ring_buffer->wait_queue_readers); free(ring_buffer->wait_queue_writers); free(ring_buffer->wait_queue_readers); if (ring_buffer->alert_waiters) { list_free(ring_buffer->alert_waiters); free(ring_buffer->alert_waiters); } } void ring_buffer_interrupt(ring_buffer_t * ring_buffer) { ring_buffer->internal_stop = 1; wakeup_queue(ring_buffer->wait_queue_readers); wakeup_queue(ring_buffer->wait_queue_writers); } void ring_buffer_eof(ring_buffer_t * ring_buffer) { ring_buffer->soft_stop = 1; wakeup_queue(ring_buffer->wait_queue_readers); wakeup_queue(ring_buffer->wait_queue_writers); } ================================================ FILE: kernel/misc/string.c ================================================ /** * @file kernel/misc/string.c * @brief Generic string functions and C standard library implementations for the kernel. * * @copyright * 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-2021 K. Lange * Copyright (C) 2015 Dale Weiler */ #include #include #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define BITOP(A, B, OP) \ ((A)[(size_t)(B)/(8*sizeof *(A))] OP (size_t)1<<((size_t)(B)%(8*sizeof *(A)))) unsigned short * memsetw(unsigned short * dest, unsigned short val, int count) { int i = 0; for ( ; i < count; ++i ) { dest[i] = val; } return dest; } #if 1 void * memcpy(void * restrict dest, const void * restrict src, size_t n) { uint64_t * d_64 = dest; const uint64_t * s_64 = src; for (; n >= 8; n -= 8) { *d_64++ = *s_64++; } uint32_t * d_32 = (void*)d_64; const uint32_t * s_32 = (const void*)s_64; for (; n >= 4; n -= 4) { *d_32++ = *s_32++; } uint8_t * d = (void*)d_32; const uint8_t * s = (const void*)s_32; for (; n > 0; n--) { *d++ = *s++; } return dest; } #else /* FIXME why is there an x86-specific memcpy outside of the arch dir... */ void * memcpy(void * restrict dest, const void * restrict src, size_t n) { asm volatile("rep movsb" : : "D"(dest), "S"(src), "c"(n) : "flags", "memory"); return dest; } #endif size_t strlen(const char * s) { const char * a = s; const size_t * w; for (; (uintptr_t)s % ALIGN; s++) { if (!*s) { return s-a; } } for (w = (const void *)s; !HASZERO(*w); w++); for (s = (const void *)w; *s; s++); return s-a; } int strcmp(const char * a, const char * b) { uint32_t i = 0; while (1) { if (a[i] < b[i]) { return -1; } else if (a[i] > b[i]) { return 1; } else { if (a[i] == '\0') { return 0; } ++i; } } } void * memset(void * dest, int c, size_t n) { size_t i = 0; for ( ; i < n; ++i ) { ((char *)dest)[i] = c; } return dest; } void * memmove(void * dest, const void * src, size_t n) { char * d = dest; const char * s = src; if (d==s) { return d; } if (s+n <= d || d+n <= s) { return memcpy(d, s, n); } if (d= sizeof(size_t); n -= sizeof(size_t), d += sizeof(size_t), s += sizeof(size_t)) { *(size_t *)d = *(size_t *)s; } } for (; n; n--) { *d++ = *s++; } } else { if ((uintptr_t)s % sizeof(size_t) == (uintptr_t)d % sizeof(size_t)) { while ((uintptr_t)(d+n) % sizeof(size_t)) { if (!n--) { return dest; } d[n] = s[n]; } while (n >= sizeof(size_t)) { n -= sizeof(size_t); *(size_t *)(d+n) = *(size_t *)(s+n); } } while (n) { n--; d[n] = s[n]; } } return dest; } void * memchr(const void * src, int c, size_t n) { const unsigned char * s = src; c = (unsigned char)c; for (; ((uintptr_t)s & (ALIGN - 1)) && n && *s != c; s++, n--); if (n && *s != c) { const size_t * w; size_t k = ONES * c; for (w = (const void *)s; n >= sizeof(size_t) && !HASZERO(*w^k); w++, n -= sizeof(size_t)); for (s = (const void *)w; n && *s != c; s++, n--); } return n ? (void *)s : 0; } void * memrchr(const void * m, int c, size_t n) { const unsigned char * s = m; c = (unsigned char)c; while (n--) { if (s[n] == c) { return (void*)(s+n); } } return 0; } size_t strspn(const char * s, const char * c) { const char * a = s; size_t byteset[32/sizeof(size_t)] = { 0 }; if (!c[0]) { return 0; } if (!c[1]) { for (; *s == *c; s++); return s-a; } for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); for (; *s && BITOP(byteset, *(unsigned char *)s, &); s++); return s-a; } char * strchrnul(const char * s, int c) { size_t * w; size_t k; c = (unsigned char)c; if (!c) { return (char *)s + strlen(s); } for (; (uintptr_t)s % ALIGN; s++) { if (!*s || *(unsigned char *)s == c) { return (char *)s; } } k = ONES * c; for (w = (void *)s; !HASZERO(*w) && !HASZERO(*w^k); w++); for (s = (void *)w; *s && *(unsigned char *)s != c; s++); return (char *)s; } char * strchr(const char * s, int c) { char *r = strchrnul(s, c); return *(unsigned char *)r == (unsigned char)c ? r : 0; } char * strrchr(const char * s, int c) { return memrchr(s, c, strlen(s) + 1); } char * stpcpy(char * restrict d, const char * restrict s) { size_t * wd; const size_t * ws; if ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) { for (; (uintptr_t)s % ALIGN; s++, d++) { if (!(*d = *s)) { return d; } } wd = (void *)d; ws = (const void *)s; for (; !HASZERO(*ws); *wd++ = *ws++); d = (void *)wd; s = (const void *)ws; } for (; (*d=*s); s++, d++); return d; } char * strcpy(char * restrict dest, const char * restrict src) { stpcpy(dest, src); return dest; } size_t lfind(const char * str, const char accept) { return (size_t)strchr(str, accept); } size_t rfind(const char * str, const char accept) { return (size_t)strrchr(str, accept); } size_t strcspn(const char * s, const char * c) { const char *a = s; if (c[0] && c[1]) { size_t byteset[32/sizeof(size_t)] = { 0 }; for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); for (; *s && !BITOP(byteset, *(unsigned char *)s, &); s++); return s-a; } return strchrnul(s, *c)-a; } char * strpbrk(const char * s, const char * b) { s += strcspn(s, b); return *s ? (char *)s : 0; } char * strtok_r(char * str, const char * delim, char ** saveptr) { char * token; if (str == NULL) { str = *saveptr; } str += strspn(str, delim); if (*str == '\0') { *saveptr = str; return NULL; } token = str; str = strpbrk(token, delim); if (str == NULL) { *saveptr = (char *)lfind(token, '\0'); } else { *str = '\0'; *saveptr = str + 1; } return token; } static char *strstr_2b(const unsigned char * h, const unsigned char * n) { uint16_t nw = n[0] << 8 | n[1]; uint16_t hw = h[0] << 8 | h[1]; for (h++; *h && hw != nw; hw = hw << 8 | *++h); return *h ? (char *)h-1 : 0; } static char *strstr_3b(const unsigned char * h, const unsigned char * n) { uint32_t nw = n[0] << 24 | n[1] << 16 | n[2] << 8; uint32_t hw = h[0] << 24 | h[1] << 16 | h[2] << 8; for (h += 2; *h && hw != nw; hw = (hw|*++h) << 8); return *h ? (char *)h-2 : 0; } static char *strstr_4b(const unsigned char * h, const unsigned char * n) { uint32_t nw = n[0] << 24 | n[1] << 16 | n[2] << 8 | n[3]; uint32_t hw = h[0] << 24 | h[1] << 16 | h[2] << 8 | h[3]; for (h += 3; *h && hw != nw; hw = hw << 8 | *++h); return *h ? (char *)h-3 : 0; } int memcmp(const void * vl, const void * vr, size_t n) { const unsigned char *l = vl; const unsigned char *r = vr; for (; n && *l == *r; n--, l++, r++); return n ? *l-*r : 0; } static char *strstr_twoway(const unsigned char * h, const unsigned char * n) { size_t mem; size_t mem0; size_t byteset[32 / sizeof(size_t)] = { 0 }; size_t shift[256]; size_t l; /* Computing length of needle and fill shift table */ for (l = 0; n[l] && h[l]; l++) { BITOP(byteset, n[l], |=); shift[n[l]] = l+1; } if (n[l]) { return 0; /* hit the end of h */ } /* Compute maximal suffix */ size_t ip = -1; size_t jp = 0; size_t k = 1; size_t p = 1; while (jp+k n[jp+k]) { jp += k; k = 1; p = jp - ip; } else { ip = jp++; k = p = 1; } } size_t ms = ip; size_t p0 = p; /* And with the opposite comparison */ ip = -1; jp = 0; k = p = 1; while (jp+k ms+1) { ms = ip; } else { p = p0; } /* Periodic needle? */ if (memcmp(n, n+p, ms+1)) { mem0 = 0; p = MAX(ms, l-ms-1) + 1; } else { mem0 = l-p; } mem = 0; /* Initialize incremental end-of-haystack pointer */ const unsigned char * z = h; /* Search loop */ for (;;) { /* Update incremental end-of-haystack pointer */ if ((size_t)(z-h) < l) { /* Fast estimate for MIN(l,63) */ size_t grow = l | 63; const unsigned char *z2 = memchr(z, 0, grow); if (z2) { z = z2; if ((size_t)(z-h) < l) { return 0; } } else { z += grow; } } /* Check last byte first; advance by shift on mismatch */ if (BITOP(byteset, h[l-1], &)) { k = l-shift[h[l-1]]; if (k) { if (mem0 && mem && k < p) k = l-p; h += k; mem = 0; continue; } } else { h += l; mem = 0; continue; } /* Compare right half */ for (k=MAX(ms+1,mem); n[k] && n[k] == h[k]; k++); if (n[k]) { h += k-ms; mem = 0; continue; } /* Compare left half */ for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--); if (k <= mem) { return (char *)h; } h += p; mem = mem0; } } char *strstr(const char * h, const char * n) { /* Return immediately on empty needle */ if (!n[0]) { return (char *)h; } /* Use faster algorithms for short needles */ h = strchr(h, *n); if (!h || !n[1]) { return (char *)h; } if (!h[1]) return 0; if (!n[2]) return strstr_2b((void *)h, (void *)n); if (!h[2]) return 0; if (!n[3]) return strstr_3b((void *)h, (void *)n); if (!h[3]) return 0; if (!n[4]) return strstr_4b((void *)h, (void *)n); /* Two-way on large needles */ return strstr_twoway((void *)h, (void *)n); } uint8_t startswith(const char * str, const char * accept) { return strstr(str, accept) == str; } char * strdup(const char * c) { char * out = malloc(strlen(c) + 1); memcpy(out, c, strlen(c)+1); return out; } int atoi(const char * c) { int sign = 1; long out = 0; if (*c == '-') { sign = -1; c++; } while (*c) { out *= 10; out += (*c - '0'); c++; } return out * sign; } ================================================ FILE: kernel/misc/tokenize.c ================================================ /** * @file kernel/misc/tokenize.c * @brief Wrapper around strtok_r, used to turn strings into arrays. * * @copyright * 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-2021 K. Lange */ #include #include int tokenize(char * str, const char * sep, char **buf) { char * pch_i; char * save_i; int argc = 0; pch_i = strtok_r(str,sep,&save_i); if (!pch_i) { return 0; } while (pch_i != NULL) { buf[argc] = (char *)pch_i; ++argc; pch_i = strtok_r(NULL,sep,&save_i); } buf[argc] = NULL; return argc; } ================================================ FILE: kernel/misc/tree.c ================================================ /** * @file kernel/misc/tree.c * @brief General-purpose tree implementation. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2018 K. Lange */ #include #include #include #include tree_t * tree_create(void) { /* Create a new tree */ tree_t * out = malloc(sizeof(tree_t)); out->nodes = 0; out->root = NULL; return out; } void tree_set_root(tree_t * tree, void * value) { /* Set the root node for a new tree. */ tree_node_t * root = tree_node_create(value); tree->root = root; tree->nodes = 1; } void tree_node_destroy(tree_node_t * node) { /* Free the contents of a node and its children, but not the nodes themselves */ foreach(child, node->children) { tree_node_destroy((tree_node_t *)child->value); } free(node->value); } void tree_destroy(tree_t * tree) { /* Free the contents of a tree, but not the nodes */ if (tree->root) { tree_node_destroy(tree->root); } } void tree_node_free(tree_node_t * node) { /* Free a node and its children, but not their contents */ if (!node) return; foreach(child, node->children) { tree_node_free(child->value); } free(node); } void tree_free(tree_t * tree) { /* Free all of the nodes in a tree, but not their contents */ tree_node_free(tree->root); } tree_node_t * tree_node_create(void * value) { /* Create a new tree node pointing to the given value */ tree_node_t * out = malloc(sizeof(tree_node_t)); out->value = value; out->children = list_create("tree node children",out); out->parent = NULL; return out; } void tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node) { /* Insert a node as a child of parent */ list_insert(parent->children, node); node->parent = parent; tree->nodes++; } tree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value) { /* Insert a (fresh) node as a child of parent */ tree_node_t * out = tree_node_create(value); tree_node_insert_child_node(tree, parent, out); return out; } tree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle) { /* Recursive node part of tree_find_parent */ tree_node_t * found = NULL; foreach(child, haystack->children) { if (child->value == needle) { return haystack; } found = tree_node_find_parent((tree_node_t *)child->value, needle); if (found) { break; } } return found; } tree_node_t * tree_find_parent(tree_t * tree, tree_node_t * node) { /* Return the parent of a node, inefficiently. */ if (!tree->root) return NULL; return tree_node_find_parent(tree->root, node); } size_t tree_count_children(tree_node_t * node) { /* return the number of children this node has */ if (!node) return 0; if (!node->children) return 0; size_t out = node->children->length; foreach(child, node->children) { out += tree_count_children((tree_node_t *)child->value); } return out; } void tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node) { /* remove a node when we know its parent; update node counts for the tree */ tree->nodes -= tree_count_children(node) + 1; list_delete(parent->children, list_find(parent->children, node)); tree_node_free(node); } void tree_node_remove(tree_t * tree, tree_node_t * node) { /* remove an entire branch given its root */ tree_node_t * parent = node->parent; if (!parent) { if (node == tree->root) { tree->nodes = 0; tree->root = NULL; tree_node_free(node); } } tree_node_parent_remove(tree, parent, node); } void tree_remove(tree_t * tree, tree_node_t * node) { /* Remove this node and move its children into its parent's list of children */ tree_node_t * parent = node->parent; /* This is something we just can't do. We don't know how to merge our * children into our "parent" because then we'd have more than one root node. * A good way to think about this is actually what this tree struct * primarily exists for: processes. Trying to remove the root is equivalent * to trying to kill init! Which is bad. We immediately fault on such * a case anyway ("Tried to kill init, shutting down!"). */ if (!parent) return; tree->nodes--; list_delete(parent->children, list_find(parent->children, node)); foreach(child, node->children) { /* Reassign the parents */ ((tree_node_t *)child->value)->parent = parent; } list_merge(parent->children, node->children); free(node); } void tree_remove_reparent_root(tree_t * tree, tree_node_t * node) { /* Remove this node and move its children into the root children */ tree_node_t * parent = node->parent; if (!parent) return; tree->nodes--; list_delete(parent->children, list_find(parent->children, node)); foreach(child, node->children) { /* Reassign the parents */ ((tree_node_t *)child->value)->parent = tree->root; } list_merge(tree->root->children, node->children); free(node); } void tree_break_off(tree_t * tree, tree_node_t * node) { tree_node_t * parent = node->parent; if (!parent) return; list_delete(parent->children, list_find(parent->children, node)); } tree_node_t * tree_node_find(tree_node_t * node, void * search, tree_comparator_t comparator) { if (comparator(node->value,search)) { return node; } tree_node_t * found; foreach(child, node->children) { found = tree_node_find((tree_node_t *)child->value, search, comparator); if (found) return found; } return NULL; } tree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator) { return tree_node_find(tree->root, value, comparator); } ================================================ FILE: kernel/net/arp.c ================================================ /** * @file kernel/net/arp.c * @brief Address resolution * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #ifndef MISAKA_DEBUG_NET #define printf(...) #endif struct arp_header { uint16_t arp_htype; uint16_t arp_ptype; uint8_t arp_hlen; uint8_t arp_plen; uint16_t arp_oper; union { struct { uint8_t arp_sha[6]; uint32_t arp_spa; uint8_t arp_tha[6]; uint32_t arp_tpa; } __attribute__((packed)) arp_eth_ipv4; } arp_data; } __attribute__((packed)); static void ip_ntoa(const uint32_t src_addr, char * out) { snprintf(out, 16, "%d.%d.%d.%d", (src_addr & 0xFF000000) >> 24, (src_addr & 0xFF0000) >> 16, (src_addr & 0xFF00) >> 8, (src_addr & 0xFF)); } spin_lock_t net_arp_cache_lock = {0}; hashmap_t * net_arp_cache = NULL; void net_arp_cache_add(struct EthernetDevice * iface, uint32_t addr, uint8_t * hwaddr, uint16_t flags) { spin_lock(net_arp_cache_lock); struct ArpCacheEntry * entry = hashmap_get(net_arp_cache, (void*)(uintptr_t)addr); if (!entry) entry = malloc(sizeof(struct ArpCacheEntry)); memcpy(entry->hwaddr, hwaddr, 6); entry->flags = flags; entry->iface = iface; hashmap_set(net_arp_cache, (void*)(uintptr_t)addr, entry); spin_unlock(net_arp_cache_lock); } struct ArpCacheEntry * net_arp_cache_get(uint32_t addr) { spin_lock(net_arp_cache_lock); struct ArpCacheEntry * out = hashmap_get(net_arp_cache, (void*)(uintptr_t)addr); spin_unlock(net_arp_cache_lock); return out; } void net_arp_ask(uint32_t addr, fs_node_t * fsnic) { struct EthernetDevice * ethnic = fsnic->device; struct arp_header arp_request = {0}; arp_request.arp_htype = htons(1); /* Ethernet */ arp_request.arp_ptype = htons(ETHERNET_TYPE_IPV4); arp_request.arp_hlen = 6; arp_request.arp_plen = 4; arp_request.arp_oper = htons(1); /* Who is...? */ arp_request.arp_data.arp_eth_ipv4.arp_tpa = addr; memcpy(arp_request.arp_data.arp_eth_ipv4.arp_sha, ethnic->mac, 6); if (ethnic->ipv4_addr) { arp_request.arp_data.arp_eth_ipv4.arp_spa = ethnic->ipv4_addr; } net_eth_send(ethnic, sizeof(struct arp_header), &arp_request, ETHERNET_TYPE_ARP, ETHERNET_BROADCAST_MAC); } void net_arp_handle(struct arp_header * packet, fs_node_t * nic) { printf("net: arp: hardware %d protocol %d operation %d hlen %d plen %d\n", ntohs(packet->arp_htype), ntohs(packet->arp_ptype), ntohs(packet->arp_oper), packet->arp_hlen, packet->arp_plen); struct EthernetDevice * eth_dev = nic->device; if (ntohs(packet->arp_htype) == 1 && ntohs(packet->arp_ptype) == ETHERNET_TYPE_IPV4) { /* Ethernet, IPv4 */ if (packet->arp_data.arp_eth_ipv4.arp_spa) { net_arp_cache_add(eth_dev, packet->arp_data.arp_eth_ipv4.arp_spa, packet->arp_data.arp_eth_ipv4.arp_sha, 0); } if (ntohs(packet->arp_oper) == 1) { char spa[17]; ip_ntoa(ntohl(packet->arp_data.arp_eth_ipv4.arp_spa), spa); char tpa[17]; ip_ntoa(ntohl(packet->arp_data.arp_eth_ipv4.arp_tpa), tpa); printf("net: arp: " MAC_FORMAT " (%s) wants to know who %s is\n", FORMAT_MAC(packet->arp_data.arp_eth_ipv4.arp_sha), spa, tpa); if (eth_dev->ipv4_addr && packet->arp_data.arp_eth_ipv4.arp_tpa == eth_dev->ipv4_addr) { printf("net: arp: that's us, we should reply...\n"); struct arp_header response = {0}; response.arp_htype = htons(1); response.arp_ptype = htons(ETHERNET_TYPE_IPV4); response.arp_hlen = 6; response.arp_plen = 4; response.arp_oper = htons(2); memcpy(response.arp_data.arp_eth_ipv4.arp_sha, eth_dev->mac, 6); memcpy(response.arp_data.arp_eth_ipv4.arp_tha, packet->arp_data.arp_eth_ipv4.arp_sha, 6); response.arp_data.arp_eth_ipv4.arp_spa = eth_dev->ipv4_addr; response.arp_data.arp_eth_ipv4.arp_tpa = packet->arp_data.arp_eth_ipv4.arp_spa; net_eth_send(eth_dev, sizeof(struct arp_header), &response, ETHERNET_TYPE_ARP, packet->arp_data.arp_eth_ipv4.arp_sha); } } else if (ntohs(packet->arp_oper) == 2) { char spa[17]; ip_ntoa(ntohl(packet->arp_data.arp_eth_ipv4.arp_spa), spa); printf("net: arp: " MAC_FORMAT " says they are %s\n", FORMAT_MAC(packet->arp_data.arp_eth_ipv4.arp_sha), spa); } } } ================================================ FILE: kernel/net/eth.c ================================================ /** * @file kernel/net/eth.c * @brief Generic Ethernet frame management * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MISAKA_DEBUG_NET #define printf(...) #endif extern spin_lock_t net_raw_sockets_lock; extern list_t * net_raw_sockets_list; extern void net_ipv4_handle(void * packet, fs_node_t * nic, size_t); extern void net_arp_handle(void * packet, fs_node_t * nic); void net_eth_handle(struct ethernet_packet * frame, fs_node_t * nic, size_t size) { struct EthernetDevice * nic_eth = nic->device; if (size < sizeof(struct ethernet_packet)) { dprintf("eth: %s: invalid ethernet frame (too small)\n", nic_eth->if_name); return; } spin_lock(net_raw_sockets_lock); foreach(node, net_raw_sockets_list) { sock_t * sock = node->value; if (!sock->_fnode.device || sock->_fnode.device == nic) { net_sock_add(sock, frame, size); } } spin_unlock(net_raw_sockets_lock); if (!memcmp(frame->destination, nic_eth->mac, 6) || !memcmp(frame->destination, ETHERNET_BROADCAST_MAC, 6)) { /* Now pass the frame to the appropriate handler... */ switch (ntohs(frame->type)) { case ETHERNET_TYPE_ARP: net_arp_handle(&frame->payload, nic); break; case ETHERNET_TYPE_IPV4: { struct ipv4_packet * packet = (struct ipv4_packet*)&frame->payload; printf("net: eth: %s: rx ipv4 packet\n", nic->name); if (packet->source != 0xFFFFFFFF) { net_arp_cache_add(nic->device, packet->source, frame->source, 0); } net_ipv4_handle(packet, nic, size - sizeof(struct ethernet_packet)); break; } } } } void net_eth_send(struct EthernetDevice * nic, size_t len, void* data, uint16_t type, uint8_t * dest) { size_t total_size = sizeof(struct ethernet_packet) + len; struct ethernet_packet * packet = malloc(total_size); memcpy(packet->payload, data, len); memcpy(packet->destination, dest, 6); memcpy(packet->source, nic->mac, 6); packet->type = htons(type); write_fs(nic->device_node, 0, total_size, (uint8_t*)packet); free(packet); } ================================================ FILE: kernel/net/ipv4.c ================================================ /** * @file kernel/net/ipv4.c * @brief IPv4, TCP, UDP protocol implementation. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef MISAKA_DEBUG_NET #define printf(...) if (_debug) printf(__VA_ARGS__) //#define printf(...) #endif #define DEFAULT_TCP_WINDOW_SIZE 65535 static int _debug __attribute__((unused)) = 0; static void ip_ntoa(const uint32_t src_addr, char * out) { snprintf(out, 16, "%d.%d.%d.%d", (src_addr & 0xFF000000) >> 24, (src_addr & 0xFF0000) >> 16, (src_addr & 0xFF00) >> 8, (src_addr & 0xFF)); } static uint16_t icmp_checksum(struct ipv4_packet * packet) { uint32_t sum = 0; uint16_t * s = (uint16_t *)packet->payload; for (int i = 0; i < (ntohs(packet->length) - 20) / 2; ++i) { sum += ntohs(s[i]); } if (sum > 0xFFFF) { sum = (sum >> 16) + (sum & 0xFFFF); } return ~(sum & 0xFFFF) & 0xFFFF; } uint16_t calculate_ipv4_checksum(struct ipv4_packet * p) { uint32_t sum = 0; uint16_t * s = (uint16_t *)p; /* TODO: Checksums for options? */ for (int i = 0; i < 10; ++i) { sum += ntohs(s[i]); if (sum > 0xFFFF) { sum = (sum >> 16) + (sum & 0xFFFF); } } return ~(sum & 0xFFFF) & 0xFFFF; } uint16_t calculate_tcp_checksum(struct tcp_check_header * p, struct tcp_header * h, void * d, size_t payload_size) { uint32_t sum = 0; uint16_t * s = (uint16_t *)p; /* TODO: Checksums for options? */ for (int i = 0; i < 6; ++i) { sum += ntohs(s[i]); if (sum > 0xFFFF) { sum = (sum >> 16) + (sum & 0xFFFF); } } s = (uint16_t *)h; for (int i = 0; i < 10; ++i) { sum += ntohs(s[i]); if (sum > 0xFFFF) { sum = (sum >> 16) + (sum & 0xFFFF); } } uint16_t d_words = payload_size / 2; s = (uint16_t *)d; for (unsigned int i = 0; i < d_words; ++i) { sum += ntohs(s[i]); if (sum > 0xFFFF) { sum = (sum >> 16) + (sum & 0xFFFF); } } if (d_words * 2 != payload_size) { uint8_t * t = (uint8_t *)d; uint8_t tmp[2]; tmp[0] = t[d_words * sizeof(uint16_t)]; tmp[1] = 0; uint16_t * f = (uint16_t *)tmp; sum += ntohs(f[0]); if (sum > 0xFFFF) { sum = (sum >> 16) + (sum & 0xFFFF); } } return ~(sum & 0xFFFF) & 0xFFFF; } static hashmap_t * udp_sockets = NULL; static hashmap_t * tcp_sockets = NULL; static hashmap_t * icmp_sockets = NULL; void ipv4_install(void) { udp_sockets = hashmap_create_int(10); tcp_sockets = hashmap_create_int(10); icmp_sockets = hashmap_create_int(10); } int net_ipv4_send(struct ipv4_packet * response, fs_node_t * nic) { /* TODO: This should be routing, with a _hint_ about the interface, not the actual nic to send from! */ struct EthernetDevice * enic = nic->device; /* where are we going? */ uint32_t ipdest = response->destination; /* Get the ethernet address of the destination */ struct ArpCacheEntry * resp; /* Is this local or should we send it to the gateway? */ if (!enic->ipv4_subnet || ((ipdest & enic->ipv4_subnet) != (enic->ipv4_addr & enic->ipv4_subnet))) { ipdest = enic->ipv4_gateway; resp = net_arp_cache_get(ipdest); } else { resp = net_arp_cache_get(ipdest); if (!resp) { net_arp_ask(ipdest, nic); unsigned long s, ss; relative_time(0, 1000, &s, &ss); sleep_until((process_t *)this_core->current_process, s, ss); switch_task(0); resp = net_arp_cache_get(ipdest); } } /* Pass the packet to the next stage */ net_eth_send(enic, ntohs(response->length), response, ETHERNET_TYPE_IPV4, resp ? resp->hwaddr : ETHERNET_BROADCAST_MAC); return 0; } static void sock_ipv4_control_common(sock_t * sock, struct msghdr * msg, struct ipv4_packet * src, int proto) { /* TODO Other options; priv32[2] should be for flags? */ if (sock->priv32[2] && msg->msg_controllen > sizeof(struct cmsghdr) + 1) { struct cmsghdr * out = msg->msg_control; out->cmsg_len = sizeof(struct cmsghdr) + 1; out->cmsg_level = IPPROTO_IP; out->cmsg_type = IP_RECVTTL; out->cmsg_data[0] = src->ttl; msg->msg_controllen = sizeof(struct cmsghdr) + 1; } else { msg->msg_controllen = 0; } } static void icmp_handle(struct ipv4_packet * packet, const char * src, const char * dest, fs_node_t * nic) { struct icmp_header * header = (void*)&packet->payload; /* Is this a PING request? */ if (header->type == 8 && header->code == 0) { printf("net: ping with %d bytes of payload\n", ntohs(packet->length)); if (ntohs(packet->length) & 1) { packet->length = htons(ntohs(packet->length) + 1); } struct ipv4_packet * response = malloc(ntohs(packet->length)); memcpy(response, packet, ntohs(packet->length)); response->length = packet->length; response->destination = packet->source; response->source = ((struct EthernetDevice*)nic->device)->ipv4_addr; response->ttl = 64; response->protocol = 1; response->ident = packet->ident; response->flags_fragment = htons(0x4000); response->version_ihl = 0x45; response->dscp_ecn = 0; response->checksum = 0; response->checksum = htons(calculate_ipv4_checksum(response)); struct icmp_header * ping_reply = (void*)&response->payload; ping_reply->csum = 0; ping_reply->type = 0; ping_reply->csum = htons(icmp_checksum(response)); /* send ipv4... */ net_ipv4_send(response,nic); free(response); } else if (header->type == 0 && header->code == 0) { /* Did we have a client waiting for this? */ sock_t * handler = hashmap_get(icmp_sockets, (void*)(uintptr_t)ntohs(header->identifier)); if (handler) { net_sock_add(handler, packet, ntohs(packet->length)); } } else { printf("net: ipv4: %s: %s -> %s ICMP %d (code = %d)\n", nic->name, src, dest, header->type, header->code); } } static void sock_icmp_close(sock_t * sock) { hashmap_remove(icmp_sockets, (void*)(uintptr_t)sock->priv32[0]); } static long sock_icmp_recv(sock_t * sock, struct msghdr * msg, int flags) { if (msg->msg_iovlen > 1) { return -ENOTSUP; } if (msg->msg_iovlen == 0) return 0; if (!sock->rx_queue->length && sock->nonblocking) return -EAGAIN; char * packet = net_sock_get(sock); if (!packet) return -EINTR; size_t packet_size = *(size_t*)packet - sizeof(struct ipv4_packet); struct ipv4_packet * src = (struct ipv4_packet*)(packet + sizeof(size_t)); if (packet_size > msg->msg_iov[0].iov_len) { dprintf("ICMP recv too big for vector\n"); packet_size = msg->msg_iov[0].iov_len; } if (msg->msg_namelen == sizeof(struct sockaddr_in)) { if (msg->msg_name) { ((struct sockaddr_in*)msg->msg_name)->sin_family = AF_INET; ((struct sockaddr_in*)msg->msg_name)->sin_port = 0; ((struct sockaddr_in*)msg->msg_name)->sin_addr.s_addr = src->source; } } sock_ipv4_control_common(sock,msg,src,IPPROTO_ICMP); memcpy(msg->msg_iov[0].iov_base, src->payload, packet_size); free(packet); return packet_size; } static long sock_icmp_send(sock_t * sock, const struct msghdr *msg, int flags) { if (msg->msg_iovlen > 1) return -ENOTSUP; if (msg->msg_iovlen == 0) return 0; if (msg->msg_namelen != sizeof(struct sockaddr_in)) return -EINVAL; if (msg->msg_iov[0].iov_len < sizeof(struct icmp_header)) return -EINVAL; struct icmp_header * icmp = msg->msg_iov[0].iov_base; if (icmp->type != 8 || icmp->code != 0) return -EINVAL; if (icmp->identifier != 0) return -EINVAL; struct sockaddr_in * name = msg->msg_name; fs_node_t * nic = net_if_route(name->sin_addr.s_addr); if (!nic) return -ENONET; size_t total_length = sizeof(struct ipv4_packet) + msg->msg_iov[0].iov_len; struct ipv4_packet * response = malloc(total_length); response->length = htons(total_length); response->destination = name->sin_addr.s_addr; response->source = ((struct EthernetDevice*)nic->device)->ipv4_addr; response->ttl = 64; response->protocol = 1; response->ident = 0; response->flags_fragment = htons(0x4000); response->version_ihl = 0x45; response->dscp_ecn = 0; response->checksum = 0; response->checksum = htons(calculate_ipv4_checksum(response)); memcpy(response->payload, msg->msg_iov[0].iov_base, msg->msg_iov[0].iov_len); struct icmp_header * micmp = (struct icmp_header*)response->payload; micmp->identifier = htons(sock->priv32[0]); micmp->csum = 0; micmp->csum = htons(icmp_checksum(response)); net_ipv4_send(response,nic); free(response); return 0; } static int icmp_socket(void) { if (hashmap_has(icmp_sockets, (void*)(uintptr_t)this_core->current_process->id)) return -EINVAL; sock_t * sock = net_sock_create(); sock->sock_recv = sock_icmp_recv; sock->sock_send = sock_icmp_send; sock->sock_close = sock_icmp_close; sock->priv32[0] = this_core->current_process->id; hashmap_set(icmp_sockets, (void*)(uintptr_t)sock->priv32[0], sock); return process_append_fd((process_t *)this_core->current_process, (fs_node_t *)sock); } #define TCP_FLAGS_FIN (1 << 0) #define TCP_FLAGS_SYN (1 << 1) #define TCP_FLAGS_RST (1 << 2) #define TCP_FLAGS_PSH (1 << 3) #define TCP_FLAGS_ACK (1 << 4) #define TCP_FLAGS_URG (1 << 5) #define TCP_FLAGS_ECE (1 << 6) #define TCP_FLAGS_CWR (1 << 7) #define TCP_FLAGS_NS (1 << 8) #define DATA_OFFSET_5 (0x5 << 12) static int tcp_ack(fs_node_t * nic, sock_t * sock, struct ipv4_packet * packet, int isSynAck, size_t payload_len) { struct tcp_header * tcp = (struct tcp_header*)&packet->payload; int retval = 1; int window_size = DEFAULT_TCP_WINDOW_SIZE; int send_thrice = 0; #if 0 /* XXX: This means the header is bigger than we expect... */ if ((ntohs(tcp->flags) & 0xF000) != 0x5000) { int _debug __attribute__((unused)) = 1; printf("tcp: uh, weird flags? %#4x\n", ntohs(tcp->flags)); } #endif if (sock->priv32[1] != 0 && !isSynAck && sock->priv32[1] != ntohl(tcp->seq_number)) { #if 0 int _debug __attribute__((unused)) = 1; printf("tcp: suspicious of their seq number?\n"); printf("tcp: their seq = %u our ack = %u\n", ntohl(tcp->seq_number), sock->priv32[1]); #endif //window_size = 300; retval = 0; send_thrice = 1; } else { sock->priv32[0] = isSynAck ? 1 : sock->priv32[0]; sock->priv32[1] = (ntohl(tcp->seq_number) + payload_len) & 0xFFFFFFFF; sock->priv[1] = 2; } sock->priv[2]++; #if 0 printf("tcp: their ack = %u our seq = %u\n", ntohl(tcp->ack_number), sock->priv32[0]); printf("tcp: their seq = %u our ack = %u\n", ntohl(tcp->seq_number), sock->priv32[1]); #endif size_t total_length = sizeof(struct ipv4_packet) + sizeof(struct tcp_header); struct ipv4_packet * response = malloc(total_length); response->length = htons(total_length); response->destination = packet->source; response->source = ((struct EthernetDevice*)nic->device)->ipv4_addr; response->ttl = 64; response->protocol = IPV4_PROT_TCP; response->ident = htons(sock->priv[2]); response->flags_fragment = htons(0x0); response->version_ihl = 0x45; response->dscp_ecn = 0; response->checksum = 0; response->checksum = htons(calculate_ipv4_checksum(response)); int flags = TCP_FLAGS_ACK; if (ntohs(tcp->flags) & TCP_FLAGS_FIN) { /* Other side is closed now */ sock->priv32[1]++; sock->priv[1] = 3; } /* Stick TCP header into payload */ struct tcp_header * tcp_header = (struct tcp_header*)&response->payload; tcp_header->source_port = htons(sock->priv[0]); tcp_header->destination_port = tcp->source_port; tcp_header->seq_number = htonl(sock->priv32[0]); tcp_header->ack_number = htonl(sock->priv32[1]); tcp_header->flags = htons(flags | 0x5000); tcp_header->window_size = htons(window_size); tcp_header->checksum = 0; tcp_header->urgent = 0; /* Calculate checksum */ struct tcp_check_header check_hd = { .source = response->source, .destination = response->destination, .zeros = 0, .protocol = IPV4_PROT_TCP, .tcp_len = htons(sizeof(struct tcp_header)), }; tcp_header->checksum = htons(calculate_tcp_checksum(&check_hd, tcp_header, NULL, 0)); net_ipv4_send(response,nic); if (send_thrice) { net_ipv4_send(response,nic); net_ipv4_send(response,nic); } free(response); return retval; } void net_ipv4_handle(struct ipv4_packet * packet, fs_node_t * nic, size_t size) { if (size < sizeof(struct ipv4_packet)) { dprintf("ipv4: Incoming packet is too small.\n"); } char dest[16]; char src[16]; ip_ntoa(ntohl(packet->destination), dest); ip_ntoa(ntohl(packet->source), src); switch (packet->protocol) { case 1: icmp_handle(packet, src, dest, nic); break; case IPV4_PROT_UDP: { uint16_t dest_port = ntohs(((uint16_t*)&packet->payload)[1]); printf("net: ipv4: %s: %s -> %s udp %d to %d\n", nic->name, src, dest, ntohs(((uint16_t*)&packet->payload)[0]), dest_port); if (hashmap_has(udp_sockets, (void*)(uintptr_t)dest_port)) { printf("net: udp: received and have a waiting endpoint!\n"); sock_t * sock = hashmap_get(udp_sockets, (void*)(uintptr_t)dest_port); net_sock_add(sock, packet, ntohs(packet->length)); } break; } case IPV4_PROT_TCP: { uint16_t dest_port = ntohs(((uint16_t*)&packet->payload)[1]); printf("net: ipv4: %s: %s -> %s tcp %d to %d\n", nic->name, src, dest, ntohs(((uint16_t*)&packet->payload)[0]), dest_port); sock_t * sock = hashmap_get(tcp_sockets, (void*)(uintptr_t)dest_port); if (sock) { printf("net: tcp: received and have a waiting endpoint!\n"); /* What kind of packet is this? Is it something we were expecting? */ struct tcp_header * tcp = (struct tcp_header*)&packet->payload; if (sock->priv[1] == 1) { /* Awaiting SYN ACK, is this one? */ if ((ntohs(tcp->flags) & (TCP_FLAGS_SYN | TCP_FLAGS_ACK)) == (TCP_FLAGS_SYN | TCP_FLAGS_ACK)) { printf("tcp: synack\n"); if (tcp_ack(nic, sock, packet, 1, 1)) { net_sock_add(sock, packet, ntohs(packet->length)); } } else if ((ntohs(tcp->flags) & (TCP_FLAGS_RST))) { sock->priv[1] = 0; net_sock_alert(sock); } } else if (sock->priv[1] == 2) { size_t packet_len = ntohs(packet->length) - sizeof(struct ipv4_packet); size_t hlen = ((ntohs(tcp->flags) & 0xF000) >> 12) * 4; size_t payload_len = packet_len - hlen; if (payload_len) { printf("tcp: acking because payload_len = %zu (hlen=%zu, packet_len=%zu)\n", payload_len, hlen, packet_len); if (tcp_ack(nic, sock, packet, 0, payload_len)) { net_sock_add(sock, packet, ntohs(packet->length)); } } else if (ntohs(tcp->flags) & TCP_FLAGS_FIN) { tcp_ack(nic, sock, packet, 0, 0); } } } break; } } } static spin_lock_t udp_port_lock = {0}; static int next_port = 12345; static int udp_get_port(sock_t * sock) { spin_lock(udp_port_lock); int out = next_port++; hashmap_set(udp_sockets, (void*)(uintptr_t)out, sock); sock->priv[0] = out; spin_unlock(udp_port_lock); return out; } long sock_udp_getsockname(sock_t * sock, struct sockaddr *addr, socklen_t * addrlen) { if (!sock->priv[0]) return -EINVAL; /* TODO do we even record the "bound" address? */ struct sockaddr_in out = { AF_INET, htons(sock->priv[0]), { 0 }, {0}, }; memcpy(addr, &out, *addrlen < sizeof(struct sockaddr_in) ? *addrlen : sizeof(struct sockaddr_in)); if (*addrlen < sizeof(struct sockaddr_in)) *addrlen = sizeof(struct sockaddr_in); return 0; } static long sock_udp_send(sock_t * sock, const struct msghdr *msg, int flags) { printf("udp: send called\n"); if (msg->msg_iovlen > 1) { printf("net: todo: can't send multiple iovs\n"); return -ENOTSUP; } if (msg->msg_iovlen == 0) return 0; if (msg->msg_namelen != sizeof(struct sockaddr_in)) { printf("udp: invalid destination address size %ld\n", msg->msg_namelen); return -EINVAL; } struct sockaddr_in * name = msg->msg_name; if (!name->sin_port) return -EADDRNOTAVAIL; /* 0 is still 0 in both endians */ if (sock->priv[0] == 0) { udp_get_port(sock); printf("udp: assigning port %d to socket\n", sock->priv[0]); } char dest[16]; ip_ntoa(ntohl(name->sin_addr.s_addr), dest); printf("udp: want to send to %s\n", dest); /* Routing: We need a device to send this on... */ fs_node_t * nic = net_if_route(name->sin_addr.s_addr); if (!nic) return 0; size_t total_length = sizeof(struct ipv4_packet) + msg->msg_iov[0].iov_len + sizeof(struct udp_packet); struct ipv4_packet * response = malloc(total_length); response->length = htons(total_length); response->destination = name->sin_addr.s_addr; response->source = ((struct EthernetDevice*)nic->device)->ipv4_addr; response->ttl = 64; response->protocol = IPV4_PROT_UDP; response->ident = 0; response->flags_fragment = htons(0x4000); response->version_ihl = 0x45; response->dscp_ecn = 0; response->checksum = 0; response->checksum = htons(calculate_ipv4_checksum(response)); /* Stick UDP header into payload */ struct udp_packet * udp_packet = (struct udp_packet*)&response->payload; udp_packet->source_port = htons(sock->priv[0]); udp_packet->destination_port = name->sin_port; udp_packet->length = htons(sizeof(struct udp_packet) + msg->msg_iov[0].iov_len); udp_packet->checksum = 0; memcpy(response->payload + sizeof(struct udp_packet), msg->msg_iov[0].iov_base, msg->msg_iov[0].iov_len); net_ipv4_send(response,nic); free(response); return msg->msg_iov[0].iov_len; } static long sock_udp_recv(sock_t * sock, struct msghdr * msg, int flags) { printf("udp: recv called\n"); if (!sock->priv[0]) { printf("udp: recv() but socket has no port\n"); return -EINVAL; } if (msg->msg_iovlen > 1) { printf("net: todo: can't recv multiple iovs\n"); return -ENOTSUP; } if (msg->msg_iovlen == 0) return 0; if (!sock->rx_queue->length && sock->nonblocking) return -EAGAIN; char * packet = net_sock_get(sock); if (!packet) return -EINTR; struct ipv4_packet * data = (struct ipv4_packet*)(packet + sizeof(size_t)); struct udp_packet * udp_packet = (struct udp_packet*)&data->payload; printf("udp: got response, size is %u - sizeof(ipv4) - sizeof(udp) = %lu\n", ntohs(data->length), ntohs(data->length) - sizeof(struct ipv4_packet) - sizeof(struct udp_packet)); memcpy(msg->msg_iov[0].iov_base, udp_packet->payload, ntohs(data->length) - sizeof(struct ipv4_packet) - sizeof(struct udp_packet)); if (msg->msg_namelen == sizeof(struct sockaddr_in)) { if (msg->msg_name) { ((struct sockaddr_in*)msg->msg_name)->sin_family = AF_INET; ((struct sockaddr_in*)msg->msg_name)->sin_port = udp_packet->source_port; ((struct sockaddr_in*)msg->msg_name)->sin_addr.s_addr = data->source; } } sock_ipv4_control_common(sock,msg,data,IPPROTO_UDP); printf("udp: data copied to iov 0, return length?\n"); long resp = ntohs(data->length) - sizeof(struct ipv4_packet) - sizeof(struct udp_packet); free(packet); return resp; } static void sock_udp_close(sock_t * sock) { if (sock->priv[0]) { printf("udp: removing port %d from bound map\n", sock->priv[0]); spin_lock(udp_port_lock); hashmap_remove(udp_sockets, (void*)(uintptr_t)sock->priv[0]); spin_unlock(udp_port_lock); } } static long sock_udp_bind(sock_t * sock, const struct sockaddr *addr, socklen_t addrlen) { if (sock->priv[0]) return -EINVAL; /* Already bound */ /* Get port */ const struct sockaddr_in * addr_in = (const struct sockaddr_in *)addr; int port = ntohs(addr_in->sin_port); if (port == 0) { /* Pick one */ spin_lock(udp_port_lock); port = next_port++; spin_unlock(udp_port_lock); } else if (port < 1024 && this_core->current_process->user != 0) { /* Only superuser can bind to lower ports */ return -EACCES; } spin_lock(udp_port_lock); if (hashmap_has(udp_sockets, (void*)(uintptr_t)port)) { spin_unlock(udp_port_lock); return -EADDRINUSE; } hashmap_set(udp_sockets, (void*)(uintptr_t)port, sock); sock->priv[0] = port; spin_unlock(udp_port_lock); /* Totally ignore the NIC stuff */ return 0; } static int udp_socket(void) { printf("udp socket...\n"); sock_t * sock = net_sock_create(); sock->sock_recv = sock_udp_recv; sock->sock_send = sock_udp_send; sock->sock_close = sock_udp_close; sock->sock_bind = sock_udp_bind; sock->sock_getsockname = sock_udp_getsockname; return process_append_fd((process_t *)this_core->current_process, (fs_node_t *)sock); } static spin_lock_t tcp_port_lock = {0}; static void sock_tcp_close(sock_t * sock) { if (sock->priv[0]) { printf("tcp: removing port %d from bound map\n", sock->priv[0]); spin_lock(tcp_port_lock); hashmap_remove(tcp_sockets, (void*)(uintptr_t)sock->priv[0]); spin_unlock(tcp_port_lock); size_t total_length = sizeof(struct ipv4_packet) + sizeof(struct tcp_header); fs_node_t * nic = net_if_route(((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr); if (!nic) return; struct ipv4_packet * response = malloc(total_length); response->length = htons(total_length); response->destination = ((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr; response->source = ((struct EthernetDevice*)nic->device)->ipv4_addr; response->ttl = 64; response->protocol = IPV4_PROT_TCP; sock->priv[2]++; response->ident = htons(sock->priv[2]); response->flags_fragment = htons(0x0); response->version_ihl = 0x45; response->dscp_ecn = 0; response->checksum = 0; response->checksum = htons(calculate_ipv4_checksum(response)); /* Stick TCP header into payload */ struct tcp_header * tcp_header = (struct tcp_header*)&response->payload; tcp_header->source_port = htons(sock->priv[0]); tcp_header->destination_port = ((struct sockaddr_in*)&sock->dest)->sin_port; tcp_header->seq_number = htonl(sock->priv32[0]); tcp_header->ack_number = htonl(sock->priv32[1]); tcp_header->flags = htons(TCP_FLAGS_FIN | TCP_FLAGS_ACK | 0x5000); tcp_header->window_size = htons(DEFAULT_TCP_WINDOW_SIZE); tcp_header->checksum = 0; tcp_header->urgent = 0; /* Calculate checksum */ struct tcp_check_header check_hd = { .source = response->source, .destination = response->destination, .zeros = 0, .protocol = IPV4_PROT_TCP, .tcp_len = htons(sizeof(struct tcp_header)), }; tcp_header->checksum = htons(calculate_tcp_checksum(&check_hd, tcp_header, tcp_header->payload, 0)); net_ipv4_send(response,nic); free(response); } } static int next_tcp_port = 49152; static int tcp_get_port(sock_t * sock) { spin_lock(tcp_port_lock); int out = next_tcp_port++; hashmap_set(tcp_sockets, (void*)(uintptr_t)out, sock); sock->priv[0] = out; spin_unlock(tcp_port_lock); return out; } static long sock_tcp_recv(sock_t * sock, struct msghdr * msg, int flags) { if (!sock->priv[0]) { printf("tcp: recv() but socket has no port\n"); return -EINVAL; } if (msg->msg_iovlen > 1) { printf("net: todo: can't recv multiple iovs\n"); return -ENOTSUP; } if (msg->msg_iovlen == 0) return 0; if (sock->unread) { if (sock->unread > msg->msg_iov[0].iov_len) { unsigned long out = msg->msg_iov[0].iov_len; sock->unread -= out; memcpy(msg->msg_iov[0].iov_base, sock->buf, out); char * x = malloc(sock->unread); memcpy(x, sock->buf + out, sock->unread); free(sock->buf); sock->buf = x; return out; } else { unsigned long out = sock->unread; sock->unread = 0; memcpy(msg->msg_iov[0].iov_base, sock->buf, out); free(sock->buf); sock->buf = NULL; return out; } } if (!sock->rx_queue->length && sock->priv[1] == 3) { return 0; /* EOF */ } if (!sock->rx_queue->length && sock->nonblocking) return -EAGAIN; while (!sock->rx_queue->length) { int r = process_wait_nodes((process_t *)this_core->current_process, (fs_node_t*[]){(fs_node_t*)sock,NULL}, 200); if (r == -EINTR) return -ERESTARTSYS; if (!sock->rx_queue->length) { if (sock->priv[1] == 3) { /* Socket was closed while waiting */ return 0; } } } char * packet = net_sock_get(sock); if (!packet) return -EINTR; struct ipv4_packet * data = (struct ipv4_packet*)(packet + sizeof(size_t)); size_t packet_size = *(size_t*)packet; unsigned long resp = ntohs(data->length); if (resp != packet_size) { dprintf("packet size does not match: %zu %zu\n", resp, packet_size); resp = packet_size; } if (resp < sizeof(struct ipv4_packet) + sizeof(struct tcp_header)) { dprintf("Invalid receive data?\n"); assert(0); } resp -= sizeof(struct ipv4_packet) + sizeof(struct tcp_header); if (resp > (unsigned long)msg->msg_iov[0].iov_len) { memcpy(msg->msg_iov[0].iov_base, data->payload + sizeof(struct tcp_header),msg->msg_iov[0].iov_len); resp -= msg->msg_iov[0].iov_len; if (resp == 0xFFFFffffFFFFffff) printf("what\n"); sock->unread = resp; sock->buf = malloc(resp); memcpy(sock->buf, data->payload + sizeof(struct tcp_header) + msg->msg_iov[0].iov_len, resp); free(packet); return msg->msg_iov[0].iov_len; } memcpy(msg->msg_iov[0].iov_base, data->payload + sizeof(struct tcp_header), resp); free(packet); return resp; } extern uint32_t rand(void); static long sock_tcp_connect(sock_t * sock, const struct sockaddr *addr, socklen_t addrlen) { const struct sockaddr_in * dest = (const struct sockaddr_in *)addr; char deststr[16]; ip_ntoa(ntohl(dest->sin_addr.s_addr), deststr); printf("tcp: connect requested to %s port %d\n", deststr, ntohs(dest->sin_port)); if (sock->priv[1] != 0) { printf("tcp: socket is already connected?\n"); return -EINVAL; } if (!dest->sin_port) return -EADDRNOTAVAIL; /* 0 is still 0 in both endians */ /* Get a port */ tcp_get_port(sock); printf("tcp: connecting from ephemeral port %d\n", (int)sock->priv[0]); /* Mark as awaiting connection, send initial SYN */ sock->priv[1] = 1; memcpy(&sock->dest, addr, addrlen); fs_node_t * nic = net_if_route(dest->sin_addr.s_addr); if (!nic) return -ENONET; size_t total_length = sizeof(struct ipv4_packet) + sizeof(struct tcp_header); struct ipv4_packet * response = malloc(total_length); response->length = htons(total_length); response->destination = dest->sin_addr.s_addr; response->source = ((struct EthernetDevice*)nic->device)->ipv4_addr; response->ttl = 64; response->protocol = IPV4_PROT_TCP; sock->priv[2] = rand(); response->ident = htons(sock->priv[2]); response->flags_fragment = htons(0x0); response->version_ihl = 0x45; response->dscp_ecn = 0; response->checksum = 0; response->checksum = htons(calculate_ipv4_checksum(response)); /* Stick TCP header into payload */ struct tcp_header * tcp_header = (struct tcp_header*)&response->payload; tcp_header->source_port = htons(sock->priv[0]); tcp_header->destination_port = dest->sin_port; tcp_header->seq_number = 0; tcp_header->ack_number = 0; tcp_header->flags = htons((1 << 1) | 0x5000); tcp_header->window_size = htons(DEFAULT_TCP_WINDOW_SIZE); tcp_header->checksum = 0; tcp_header->urgent = 0; /* Calculate checksum */ struct tcp_check_header check_hd = { .source = response->source, .destination = response->destination, .zeros = 0, .protocol = IPV4_PROT_TCP, .tcp_len = htons(sizeof(struct tcp_header)), }; tcp_header->checksum = htons(calculate_tcp_checksum(&check_hd, tcp_header, NULL, 0)); net_ipv4_send(response,nic); //int _debug __attribute__((unused)) = 1; printf("tcp: waiting for connect to finish; queue = %ld\n", sock->rx_queue->length); unsigned long s, ss; unsigned long ns, nss; relative_time(1,0,&s,&ss); int attempts = 0; while (!sock->rx_queue->length) { int result = process_wait_nodes((process_t *)this_core->current_process, (fs_node_t*[]){(fs_node_t*)sock,NULL}, 200); if (result == -EINTR) { free(response); return -EINTR; } relative_time(0,0,&ns,&nss); if (sock->priv[1] == 0) { free(response); return -ECONNREFUSED; } if (result != 0 && (ns > s || (ns == s && nss > ss))) { if (attempts++ == 3) { printf("tcp: connect timed out\n"); free(response); return -ETIMEDOUT; } printf("tcp: retrying...\n"); net_ipv4_send(response,nic); relative_time(1,0,&s,&ss); } } free(response); printf("tcp: queue should have data now (len = %lu), trying to read\n", sock->rx_queue->length); /* wait for signal that we connected or timed out */ char * packet = net_sock_get(sock); if (!packet) return -EINTR; //struct ipv4_packet * data = packet + sizeof(size_t); printf("tcp: connect complete\n"); free(packet); return 0; } ssize_t sock_tcp_read(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { printf("tcp: read into buffer of %zu bytes\n", size); struct iovec _iovec = { buffer, size }; struct msghdr _header = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &_iovec, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; return sock_tcp_recv((sock_t*)node, &_header, 0); } static void delay_yield(size_t subticks) { unsigned long s, ss; relative_time(0, subticks, &s, &ss); sleep_until((process_t *)this_core->current_process, s, ss); switch_task(0); } static long sock_tcp_send(sock_t * sock, const struct msghdr *msg, int flags) { printf("tcp: send called\n"); if (msg->msg_iovlen > 1) { printf("net: todo: can't send multiple iovs\n"); return -ENOTSUP; } if (msg->msg_iovlen == 0) return 0; size_t size_into = 0; size_t size_remaining = msg->msg_iov[0].iov_len; size_t last = arch_perf_timer(); while (size_remaining) { size_t size_to_send = size_remaining > 1024 ? 1024 : size_remaining; size_t total_length = sizeof(struct ipv4_packet) + sizeof(struct tcp_header) + size_to_send; fs_node_t * nic = net_if_route(((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr); if (!nic) return -ENONET; struct ipv4_packet * response = malloc(total_length); response->length = htons(total_length); response->destination = ((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr; response->source = ((struct EthernetDevice*)nic->device)->ipv4_addr; response->ttl = 64; response->protocol = IPV4_PROT_TCP; sock->priv[2]++; response->ident = htons(sock->priv[2]); response->flags_fragment = htons(0x0); response->version_ihl = 0x45; response->dscp_ecn = 0; response->checksum = 0; response->checksum = htons(calculate_ipv4_checksum(response)); /* Stick TCP header into payload */ struct tcp_header * tcp_header = (struct tcp_header*)&response->payload; tcp_header->source_port = htons(sock->priv[0]); tcp_header->destination_port = ((struct sockaddr_in*)&sock->dest)->sin_port; tcp_header->seq_number = htonl(sock->priv32[0]); tcp_header->ack_number = htonl(sock->priv32[1]); tcp_header->flags = htons(TCP_FLAGS_PSH | TCP_FLAGS_ACK | 0x5000); tcp_header->window_size = htons(DEFAULT_TCP_WINDOW_SIZE); tcp_header->checksum = 0; tcp_header->urgent = 0; sock->priv32[0] += size_to_send; /* Calculate checksum */ struct tcp_check_header check_hd = { .source = response->source, .destination = response->destination, .zeros = 0, .protocol = IPV4_PROT_TCP, .tcp_len = htons(sizeof(struct tcp_header) + size_to_send), }; memcpy(tcp_header->payload, (char*)msg->msg_iov[0].iov_base + size_into, size_to_send); tcp_header->checksum = htons(calculate_tcp_checksum(&check_hd, tcp_header, tcp_header->payload, size_to_send)); net_ipv4_send(response,nic); free(response); size_remaining -= size_to_send; size_into += size_to_send; if (size_remaining) { /* Keep us away from the BSP... */ if (processor_count > 1) { if (this_core->cpu_id == 0) { delay_yield(0); } } else { if (arch_perf_timer() - last > 10000UL * arch_cpu_mhz()) { delay_yield(0); last = arch_perf_timer(); } } } } return size_into; } ssize_t sock_tcp_write(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { printf("tcp: write of %zu bytes\n", size); struct iovec _iovec = { (void*)buffer, size }; struct msghdr _header = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &_iovec, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; return sock_tcp_send((sock_t*)node, &_header, 0); } long sock_tcp_getsockname(sock_t * sock, struct sockaddr *addr, socklen_t * addrlen) { in_addr_t ip4_addr = 0; fs_node_t * nic = net_if_route(((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr); if (nic) { ip4_addr = ((struct EthernetDevice*)nic->device)->ipv4_addr; } struct sockaddr_in out = { AF_INET, htons(sock->priv[0]), { ip4_addr }, {0}, }; memcpy(addr, &out, *addrlen < sizeof(struct sockaddr_in) ? *addrlen : sizeof(struct sockaddr_in)); if (*addrlen < sizeof(struct sockaddr_in)) *addrlen = sizeof(struct sockaddr_in); return 0; } long sock_tcp_getpeername(sock_t * sock, struct sockaddr *addr, socklen_t * addrlen) { in_addr_t ip4_addr = ((struct sockaddr_in*)&sock->dest)->sin_addr.s_addr; struct sockaddr_in out = { AF_INET, ((struct sockaddr_in*)&sock->dest)->sin_port, { ip4_addr }, {0}, }; memcpy(addr, &out, *addrlen < sizeof(struct sockaddr_in) ? *addrlen : sizeof(struct sockaddr_in)); if (*addrlen < sizeof(struct sockaddr_in)) *addrlen = sizeof(struct sockaddr_in); return 0; } static int tcp_socket(void) { printf("tcp socket...\n"); sock_t * sock = net_sock_create(); sock->sock_recv = sock_tcp_recv; sock->sock_send = sock_tcp_send; sock->sock_close = sock_tcp_close; sock->sock_connect = sock_tcp_connect; sock->sock_getsockname = sock_tcp_getsockname; sock->sock_getpeername = sock_tcp_getpeername; sock->_fnode.read = sock_tcp_read; sock->_fnode.write = sock_tcp_write; int fd = process_append_fd((process_t *)this_core->current_process, (fs_node_t *)sock); FD_MODE(fd) = 03; return fd; } long net_ipv4_socket(int type, int protocol) { /* Ignore protocol, make socket for 'type' only... */ switch (type) { case SOCK_DGRAM: if (!protocol || protocol == IPPROTO_UDP) return udp_socket(); if (protocol == IPPROTO_ICMP) return icmp_socket(); return -EINVAL; case SOCK_STREAM: return tcp_socket(); default: return -EINVAL; } } long net_so_ipv4_socket(struct SockData * sock, int optname, const void *optval, socklen_t optlen) { switch (optname) { case IP_RECVTTL: if (optlen != sizeof(int)) return -EINVAL; /* TODO ugh bad */ sock->priv32[2] = *(int*)optval; return 0; default: return -ENOPROTOOPT; } } ================================================ FILE: kernel/net/loop.c ================================================ /** * @file kernel/net/loop.c * @brief Loopback interface * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include struct loop_nic { struct EthernetDevice eth; netif_counters_t counts; }; static int ioctl_loop(fs_node_t * node, unsigned long request, void * argp) { struct loop_nic * nic = node->device; switch (request) { case SIOCGIFHWADDR: return 1; case SIOCGIFADDR: if (nic->eth.ipv4_addr == 0) return -ENOENT; memcpy(argp, &nic->eth.ipv4_addr, sizeof(nic->eth.ipv4_addr)); return 0; case SIOCSIFADDR: memcpy(&nic->eth.ipv4_addr, argp, sizeof(nic->eth.ipv4_addr)); return 0; case SIOCGIFNETMASK: if (nic->eth.ipv4_subnet == 0) return -ENOENT; memcpy(argp, &nic->eth.ipv4_subnet, sizeof(nic->eth.ipv4_subnet)); return 0; case SIOCSIFNETMASK: memcpy(&nic->eth.ipv4_subnet, argp, sizeof(nic->eth.ipv4_subnet)); return 0; case SIOCGIFGATEWAY: if (nic->eth.ipv4_subnet == 0) return -ENOENT; memcpy(argp, &nic->eth.ipv4_gateway, sizeof(nic->eth.ipv4_gateway)); return 0; case SIOCSIFGATEWAY: memcpy(&nic->eth.ipv4_gateway, argp, sizeof(nic->eth.ipv4_gateway)); net_arp_ask(nic->eth.ipv4_gateway, node); return 0; case SIOCGIFADDR6: return -ENOENT; case SIOCSIFADDR6: memcpy(&nic->eth.ipv6_addr, argp, sizeof(nic->eth.ipv6_addr)); return 0; case SIOCGIFFLAGS: { uint32_t * flags = argp; *flags = IFF_RUNNING; *flags |= IFF_UP; *flags |= IFF_LOOPBACK; return 0; } case SIOCGIFMTU: { uint32_t * mtu = argp; *mtu = nic->eth.mtu; return 0; } case SIOCGIFCOUNTS: { memcpy(argp, &nic->counts, sizeof(netif_counters_t)); return 0; } default: return -EINVAL; } } static ssize_t write_loop(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct loop_nic * nic = node->device; nic->counts.rx_count++; nic->counts.tx_count++; nic->counts.rx_bytes += size; nic->counts.tx_bytes += size; net_eth_handle((void*)buffer, node, size); return size; } static void loop_init(struct loop_nic * nic) { nic->eth.device_node = calloc(sizeof(fs_node_t),1); snprintf(nic->eth.device_node->name, 100, "%s", nic->eth.if_name); nic->eth.device_node->flags = FS_BLOCKDEVICE; nic->eth.device_node->mask = 0666; nic->eth.device_node->ioctl = ioctl_loop; nic->eth.device_node->write = write_loop; nic->eth.device_node->device = nic; nic->eth.mtu = 65536; /* guess */ nic->eth.ipv4_addr = 0x0100007F; nic->eth.ipv4_subnet = 0x000000FF; net_add_interface(nic->eth.if_name, nic->eth.device_node); } fs_node_t * loopbook_install(void) { struct loop_nic * nic = calloc(1,sizeof(struct loop_nic)); snprintf(nic->eth.if_name, 31, "lo"); loop_init(nic); return nic->eth.device_node; return 0; } ================================================ FILE: kernel/net/netif.c ================================================ /** * @file kernel/net/netif.c * @brief Network interface manager. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include static hashmap_t * interfaces = NULL; extern list_t * net_raw_sockets_list; static fs_node_t * _if_first = NULL; static fs_node_t * _if_loop = NULL; extern void ipv4_install(void); extern hashmap_t * net_arp_cache; extern fs_node_t * loopbook_install(void); void net_install(void) { /* Set up virtual devices */ map_vfs_directory("/dev/net"); interfaces = hashmap_create(10); net_raw_sockets_list = list_create("raw sockets", NULL); net_arp_cache = hashmap_create_int(10); ipv4_install(); _if_loop = loopbook_install(); _if_first = NULL; } /* kinda temporary for now */ int net_add_interface(const char * name, fs_node_t * deviceNode) { hashmap_set(interfaces, name, deviceNode); char tmp[100]; snprintf(tmp,100,"/dev/net/%s", name); vfs_mount(tmp, deviceNode, "netif", ""); if (!_if_first) _if_first = deviceNode; return 0; } fs_node_t * net_if_lookup(const char * name) { return hashmap_get(interfaces, name); } fs_node_t * net_if_any(void) { return _if_first; } fs_node_t * net_if_route(uint32_t addr) { /* First off, let's do stupid stuff. */ if (addr == 0x0100007F) return _if_loop; return _if_first; } ================================================ FILE: kernel/net/socket.c ================================================ /** * @file kernel/net/socket.c * @brief Top-level socket manager. * * Provides the standard socket interface. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #ifndef MISAKA_DEBUG_NET #define printf(...) #endif /** * TODO: Should we have an interface for modules to install protocol handlers? * Thinking this should work like the VFS, with method tables for different * protocol handlers, but a lot of this stuff is also just generic... */ extern long net_ipv4_socket(int,int); void net_sock_alert(sock_t * sock) { spin_lock(sock->alert_lock); while (sock->alert_wait->head) { node_t * node = list_dequeue(sock->alert_wait); process_t * p = node->value; free(node); spin_unlock(sock->alert_lock); process_alert_node(p, (fs_node_t*)sock); spin_lock(sock->alert_lock); } spin_unlock(sock->alert_lock); } void net_sock_add(sock_t * sock, void * frame, size_t size) { spin_lock(sock->rx_lock); char * bleh = malloc(size + sizeof(size_t)); *(size_t*)bleh = size; memcpy(bleh + sizeof(size_t), frame, size); list_insert(sock->rx_queue, bleh); wakeup_queue(sock->rx_wait); net_sock_alert(sock); spin_unlock(sock->rx_lock); } void * net_sock_get(sock_t * sock) { while (!sock->rx_queue->length) { if (sleep_on(sock->rx_wait)) { if (!sock->rx_queue->length) return NULL; } } spin_lock(sock->rx_lock); node_t * n = list_dequeue(sock->rx_queue); void* value = n->value; free(n); spin_unlock(sock->rx_lock); return value; } int sock_generic_check(fs_node_t *node) { sock_t * sock = (sock_t*)node; if (sock->rx_queue->length) return 0; if (sock->unread) return 0; return 1; } int sock_generic_wait(fs_node_t *node, void * process) { sock_t * sock = (sock_t*)node; spin_lock(sock->alert_lock); if (!list_find(sock->alert_wait, process)) { list_insert(sock->alert_wait, process); } list_insert(((process_t *)process)->node_waits, sock); spin_unlock(sock->alert_lock); return 0; } void sock_generic_close(fs_node_t *node) { sock_t * sock = (sock_t*)node; sock->sock_close(sock); while (sock->rx_queue->length) { node_t * n = list_dequeue(sock->rx_queue); free(n->value); free(n); } printf("net: socket closed\n"); } int sock_generic_ioctl(fs_node_t * node, unsigned long request, void * argp) { sock_t * sock = (sock_t*)node; switch (request) { case FIONBIO: { if (!mmu_validate_user_pointer(argp, sizeof(int), 0)) return -EFAULT; sock->nonblocking = (!!*(int*)argp); return 0; } } return -EINVAL; } sock_t * net_sock_create(void) { sock_t * sock = calloc(sizeof(struct SockData),1); sock->_fnode.flags = FS_SOCKET; /* uh, FS_SOCKET? */ sock->_fnode.mask = 0600; sock->_fnode.device = NULL; sock->_fnode.selectcheck = sock_generic_check; sock->_fnode.selectwait = sock_generic_wait; sock->_fnode.close = sock_generic_close; sock->_fnode.ioctl = sock_generic_ioctl; sock->alert_wait = list_create("socket alert wait", sock); sock->rx_wait = list_create("socket rx wait", sock); sock->rx_queue = list_create("socket rx queue", sock); open_fs((fs_node_t*)sock,0); return sock; } spin_lock_t net_raw_sockets_lock = {0}; list_t * net_raw_sockets_list = NULL; static long sock_raw_recv(sock_t * sock, struct msghdr * msg, int flags) { if (!sock->_fnode.device) return -EINVAL; if (msg->msg_iovlen > 1) { printf("net: todo: can't recv multiple iovs\n"); return -ENOTSUP; } if (msg->msg_iovlen == 0) return 0; char * data = net_sock_get(sock); if (!data) return -EINTR; size_t packet_size = *(size_t*)data; if (msg->msg_iov[0].iov_len < packet_size) { free(data); return -EINVAL; } memcpy(msg->msg_iov[0].iov_base, data + sizeof(size_t), packet_size); free(data); return 4096; } static long sock_raw_send(sock_t * sock, const struct msghdr *msg, int flags) { if (!sock->_fnode.device) return -EINVAL; if (msg->msg_iovlen > 1) { printf("net: todo: can't send multiple iovs\n"); return -ENOTSUP; } if (msg->msg_iovlen == 0) return 0; return write_fs(sock->_fnode.device, 0, msg->msg_iov[0].iov_len, msg->msg_iov[0].iov_base); } static void sock_raw_close(sock_t * sock) { spin_lock(net_raw_sockets_lock); list_delete(net_raw_sockets_list, list_find(net_raw_sockets_list, sock)); spin_unlock(net_raw_sockets_lock); /* free stuff ? */ } /** * Raw sockets */ long net_raw_socket(int type, int protocol) { if (type != SOCK_RAW) return -ESOCKTNOSUPPORT; if (this_core->current_process->user != 0) return -EACCES; /* Make a new raw socket? */ sock_t * sock = net_sock_create(); sock->sock_recv = sock_raw_recv; sock->sock_send = sock_raw_send; sock->sock_close = sock_raw_close; spin_lock(net_raw_sockets_lock); list_insert(net_raw_sockets_list, sock); spin_unlock(net_raw_sockets_lock); int fd = process_append_fd((process_t *)this_core->current_process, (fs_node_t *)sock); return fd; } long net_socket(int domain, int type, int protocol) { switch (domain) { case AF_INET: return net_ipv4_socket(type, protocol); case AF_RAW: return net_raw_socket(type, protocol); default: return -EAFNOSUPPORT; } } long net_so_socket(struct SockData * sock, int optname, const void *optval, socklen_t optlen) { switch (optname) { case SO_BINDTODEVICE: { if (optlen < 1 || optlen > 32 || ((const char*)optval)[optlen-1] != 0) return -EINVAL; fs_node_t * netif = net_if_lookup((const char*)optval); if (!netif) return -ENOENT; sock->_fnode.device = netif; return 0; } default: return -ENOPROTOOPT; } } extern long net_so_ipv4_socket(struct SockData * sock, int optname, const void *optval, socklen_t optlen); static inline int is_socket(int sockfd) { if (!FD_CHECK(sockfd)) return -EBADF; fs_node_t * node = FD_ENTRY(sockfd); if (!(node->flags & FS_SOCKET)) return -ENOTSOCK; return 0; } #define CHECK_SOCK(sockfd) do { int x = is_socket(sockfd); if (x) return x; } while (0) #define ADDR_WR_ADDR 1 #define ADDR_WR_LEN 2 static inline int validate_addr_ptr(const struct sockaddr *addr, socklen_t * addrlen, int flags) { if (!mmu_validate_user_pointer(addrlen, sizeof(socklen_t), (flags & ADDR_WR_LEN) ? MMU_PTR_WRITE : 0)) return -EFAULT; if (!mmu_validate_user_pointer((void*)addr, *addrlen, (flags & ADDR_WR_ADDR) ? MMU_PTR_WRITE : 0)) return -EFAULT; return 0; } #define CHECK_ADDR_ADDRLEN(addr,addrlen,flags) do { int x = validate_addr_ptr(addr,addrlen,flags); if (x) return x; } while (0) long net_setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { CHECK_SOCK(sockfd); PTR_VALIDATE(optval); sock_t * node = (sock_t*)FD_ENTRY(sockfd); switch (level) { case SOL_SOCKET: return net_so_socket(node,optname,optval,optlen); case IPPROTO_IP: return net_so_ipv4_socket(node,optname,optval,optlen); default: return -ENOPROTOOPT; } return -EINVAL; } long net_getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { CHECK_SOCK(sockfd); return -EINVAL; } long net_bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { CHECK_SOCK(sockfd); sock_t * node = (sock_t*)FD_ENTRY(sockfd); if (!node->sock_bind) return -EINVAL; return node->sock_bind(node, addr, addrlen); } long net_accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen) { CHECK_SOCK(sockfd); return -EINVAL; } long net_listen(int sockfd, int backlog) { CHECK_SOCK(sockfd); return -EINVAL; } long net_connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { CHECK_SOCK(sockfd); sock_t * node = (sock_t*)FD_ENTRY(sockfd); if (!node->sock_connect) return -EINVAL; return node->sock_connect(node,addr,addrlen); } static int validate_msg(const struct msghdr * msg, int readonly) { int flags = readonly ? 0 : MMU_PTR_WRITE; if (!mmu_validate_user_pointer(msg,sizeof(struct msghdr),flags)) return 1; if (msg->msg_iovlen) { /* Check msg_name pointer */ if (msg->msg_name && !mmu_validate_user_pointer(msg->msg_name, (size_t)(msg->msg_namelen), flags)) return 1; /* Check iovec structures */ if (!mmu_validate_user_pointer(msg->msg_iov, (size_t)(msg->msg_iovlen * sizeof(struct iovec)),flags)) return 1; /* Check all the buffers in there */ for (size_t i = 0; i < msg->msg_iovlen; ++i) { if (!mmu_validate_user_pointer(msg->msg_iov[i].iov_base, (size_t)(msg->msg_iov[i].iov_len), flags)) return 1; } } /* Check control message space */ if (msg->msg_controllen && !mmu_validate_user_pointer(msg->msg_control, (size_t)(msg->msg_controllen), flags)) return 1; return 0; } long net_recv(int sockfd, struct msghdr * msg, int flags) { CHECK_SOCK(sockfd); PTR_VALIDATE(msg); if (validate_msg(msg,0)) return -EFAULT; sock_t * node = (sock_t*)FD_ENTRY(sockfd); return node->sock_recv(node,msg,flags); } long net_send(int sockfd, const struct msghdr * msg, int flags) { CHECK_SOCK(sockfd); PTR_VALIDATE(msg); if (validate_msg(msg,1)) return -EFAULT; sock_t * node = (sock_t*)FD_ENTRY(sockfd); return node->sock_send(node,msg,flags); } long net_shutdown(int sockfd, int how) { return -EINVAL; } long net_getsockname(int sockfd, struct sockaddr *addr, socklen_t * addrlen) { CHECK_SOCK(sockfd); CHECK_ADDR_ADDRLEN(addr,addrlen,ADDR_WR_ADDR|ADDR_WR_LEN); sock_t * node = (sock_t*)FD_ENTRY(sockfd); if (!node->sock_getsockname) return -EINVAL; return node->sock_getsockname(node, addr, addrlen); } long net_getpeername(int sockfd, struct sockaddr *addr, socklen_t * addrlen) { CHECK_SOCK(sockfd); CHECK_ADDR_ADDRLEN(addr,addrlen,ADDR_WR_ADDR|ADDR_WR_LEN); sock_t * node = (sock_t*)FD_ENTRY(sockfd); if (!node->sock_getpeername) return -EINVAL; return node->sock_getpeername(node, addr, addrlen); } ================================================ FILE: kernel/sys/mutex.c ================================================ /** * Mutex that sleeps... and can be owned across sleeping... * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include extern int wakeup_queue_one(list_t * queue); sched_mutex_t * mutex_init(const char * name) { sched_mutex_t * out = malloc(sizeof(sched_mutex_t)); spin_init(out->inner_lock); out->status = 0; out->owner = NULL; out->waiters = list_create(name, out); return out; } int mutex_acquire(sched_mutex_t * mutex) { spin_lock(mutex->inner_lock); while (mutex->status) { sleep_on_unlocking(mutex->waiters, &mutex->inner_lock); spin_lock(mutex->inner_lock); } mutex->status = 1; mutex->owner = (process_t*)this_core->current_process; spin_unlock(mutex->inner_lock); return 0; } int mutex_release(sched_mutex_t * mutex) { assert(mutex->owner == this_core->current_process); spin_lock(mutex->inner_lock); mutex->owner = NULL; mutex->status = 0; wakeup_queue_one(mutex->waiters); spin_unlock(mutex->inner_lock); return 0; } ================================================ FILE: kernel/sys/process.c ================================================ /** * @file kernel/sys/process.c * @brief Task switch and thread scheduling. * * Implements the primary scheduling primitives for the kernel. * * Generally, what the kernel refers to as a "process" is an individual thread. * The POSIX concept of a "process" is represented in Misaka as a collection of * threads and their shared paging, signal, and file descriptor tables. * * Kernel threads are also "processes", referred to as "tasklets". * * Misaka allows nested kernel preemption, and task switching involves saving * kernel state in a manner similar to setjmp/longjmp, as well as saving the * outer context in the case of a nested task switch. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2021 K. Lange * Copyright (C) 2012 Markus Schober * Copyright (C) 2015 Dale Weiler */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* FIXME: This only needs the size of the regs struct... */ #if defined(__x86_64__) #include #elif defined(__aarch64__) #include #else #error "no regs" #endif tree_t * process_tree; /* Stores the parent-child process relationships; the root of this graph is 'init'. */ list_t * process_list; /* Stores all existing processes. Mostly used for sanity checking or for places where iterating over all processes is useful. */ list_t * process_queue; /* Scheduler ready queue. This the round-robin source. The head is the next process to run. */ list_t * sleep_queue; /* Ordered list of processes waiting to be awoken by timeouts. The head is the earliest thread to awaken. */ list_t * reap_queue; /* Processes that could not be cleaned up and need to be deleted. */ struct ProcessorLocal processor_local_data[32] = {0}; int processor_count = 1; /* The following locks protect access to the process tree, scheduler queue, * sleeping, and the very special wait queue... */ static spin_lock_t tree_lock = { 0 }; static spin_lock_t process_queue_lock = { 0 }; static spin_lock_t wait_lock_tmp = { 0 }; static spin_lock_t sleep_lock = { 0 }; static spin_lock_t reap_lock = { 0 }; /** * Update both the total time and the system time when switching to a new thread * or exiting the current thread. */ void update_process_times(void) { uint64_t pTime = arch_perf_timer(); if (this_core->current_process->time_in && this_core->current_process->time_in < pTime) { this_core->current_process->time_total += pTime - this_core->current_process->time_in; } this_core->current_process->time_in = 0; if (this_core->current_process->time_switch && this_core->current_process->time_switch < pTime) { this_core->current_process->time_sys += pTime - this_core->current_process->time_switch; } this_core->current_process->time_switch = 0; } /** * Add time spent in kernel to time_sys when returning to userspace, such as through * an interrupt return or entry into a signal handler. */ void update_process_times_on_exit(void) { uint64_t pTime = arch_perf_timer(); if (this_core->current_process->time_switch && this_core->current_process->time_switch < pTime) { this_core->current_process->time_sys += pTime - this_core->current_process->time_switch; } this_core->current_process->time_switch = 0; } #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(); } /** * @brief Restore the context of the next available process's kernel thread. * * Loads the next ready process from the scheduler queue and resumes it. * * If no processes are available, the local idle task will be run from the beginning * of its function entry. * * If the next process in the queue has been marked as finished, it will be discard * until a non-finished process is found. * * If the next process is new, it will be marked as started, and its entry point * jumped to. * * For all other cases, the process's stored kernel thread state will be restored * and execution will contain in @ref switch_task with a return value of 1. * * Note that switch_next does not return and should be called only when the current * process has been properly added to a scheduling queue, or marked as awaiting cleanup, * otherwise its return state if resumed is undefined and generally whatever the state * was when that process last entered switch_task. * * @returns never. */ void switch_next(void) { this_core->previous_process = this_core->current_process; update_process_times(); /* Get the next available process, discarded anything in the queue * marked as finished. */ do { this_core->current_process = next_ready_process(); } while (this_core->current_process->flags & PROC_FLAG_FINISHED); this_core->current_process->time_in = arch_perf_timer(); this_core->current_process->time_switch = this_core->current_process->time_in; /* Restore paging and task switch context. */ mmu_set_directory(this_core->current_process->thread.page_directory->directory); arch_set_kernel_stack(this_core->current_process->image.stack); if (this_core->current_process->flags & PROC_FLAG_FINISHED) { arch_fatal_prepare(); printf("Should not have this process...\n"); if (this_core->current_process->flags & PROC_FLAG_FINISHED) printf("It is marked finished.\n"); arch_dump_traceback(); arch_fatal(); __builtin_unreachable(); } /* Mark the process as running and started. */ __sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_STARTED); asm volatile ("" ::: "memory"); /* Jump to next */ arch_restore_context(&this_core->current_process->thread); __builtin_unreachable(); } /** * @brief Yield the processor to the next available task. * * Yields the current process, allowing the next to run. Can be called both as * part of general preemption or from blocking tasks; in the latter case, * the process should be added to a scheduler queue to be awakoen later when the * blocking operation is completed and @p reschedule should be set to 0. * * @param reschedule Non-zero if this process should be added to the ready queue. */ void switch_task(uint8_t reschedule) { /* switch_task() called but the scheduler isn't enabled? Resume... this is probably a bug. */ if (!this_core->current_process) return; if (this_core->current_process == this_core->kernel_idle_task) { arch_fatal_prepare(); printf("Context switch from kernel_idle_task triggered from somewhere other than pre-emption source. Halting.\n"); printf("This generally means that a driver responding to interrupts has attempted to yield in its interrupt context.\n"); printf("Ensure that all device drivers which respond to interrupts do so with non-blocking data structures.\n"); printf(" Return address of switch_task: %p\n", __builtin_return_address(0)); arch_dump_traceback(); arch_fatal(); } /* If a process got to switch_task but was not marked as running, it must be exiting and we don't * want to waste time saving context for it. Also, kidle is always resumed from the top of its * loop function, so we don't save any context for it either. */ if (!(this_core->current_process->flags & PROC_FLAG_RUNNING) || (this_core->current_process == this_core->kernel_idle_task)) { switch_next(); return; } arch_save_floating((process_t*)this_core->current_process); /* 'setjmp' - save the execution context. When this call returns '1' we are back * from a task switch and have been awoken if we were sleeping. */ if (arch_save_context(&this_core->current_process->thread) == 1) { arch_restore_floating((process_t*)this_core->current_process); return; } /* If this is a normal yield, we reschedule. * XXX: Is this going to work okay with SMP? I think this whole thing * needs to be wrapped in a lock, but also what if we put the * thread into a schedule queue previously but a different core * picks it up before we saved the thread context or the FPU state... */ if (reschedule) { make_process_ready((process_t*)this_core->current_process); } /* @ref switch_next() does not return. */ switch_next(); } /** * @brief Initial scheduler datastructures. * * Called by early system startup to allocate trees and lists * the schedule uses to track processes. */ void initialize_process_tree(void) { process_tree = tree_create(); process_list = list_create("global process list",NULL); process_queue = list_create("global scheduler queue",NULL); sleep_queue = list_create("global timed sleep queue",NULL); reap_queue = list_create("processes awaiting later cleanup",NULL); /* TODO: PID bitset? */ } /** * @brief Determines if a process is alive and valid. * * Scans @ref process_list to see if @p process is a valid * process object or not. * * XXX This is horribly inefficient, and its very existence * is likely indicative of bugs whereever it needed to * be called... * * @param process Process object to check. * @returns 1 if the process is valid, 0 if it is not. */ int is_valid_process(process_t * process) { foreach(lnode, process_list) { if (lnode->value == process) { return 1; } } return 0; } /** * @brief Grow the FD table for a process by doubling its capacity. */ static void process_fds_grow(process_t * proc) { proc->fds->capacity *= 2; proc->fds->entries = realloc(proc->fds->entries, sizeof(fs_node_t *) * proc->fds->capacity); proc->fds->modes = realloc(proc->fds->modes, sizeof(int) * proc->fds->capacity); proc->fds->offsets = realloc(proc->fds->offsets, sizeof(uint64_t) * proc->fds->capacity); } /** * @brief Duplicate a file descriptor to a new table entry. */ static void process_fds_copy(process_t * proc, long src, long dest) { proc->fds->entries[dest] = proc->fds->entries[src]; proc->fds->modes[dest] = proc->fds->modes[src]; proc->fds->offsets[dest] = proc->fds->offsets[src]; open_fs(proc->fds->entries[dest], 0); } /** * @brief Allocate a new file descriptor. * * Adds a new entry to the file descriptor table for @p proc * pointing to the file @p node. The file descriptor's offset * and file modes must be set by the caller afterwards. * * @param proc Process whose file descriptor should be modified. * @param node VFS object to add a reference to. * @returns the new file descriptor index */ unsigned long process_append_fd(process_t * proc, fs_node_t * node) { spin_lock(proc->fds->lock); /* Fill gaps */ for (unsigned long i = 0; i < proc->fds->length; ++i) { if (!proc->fds->entries[i]) { proc->fds->entries[i] = node; /* modes, offsets must be set by caller */ proc->fds->modes[i] = 0; proc->fds->offsets[i] = 0; spin_unlock(proc->fds->lock); return i; } } /* No gaps, expand */ if (proc->fds->length == proc->fds->capacity) { process_fds_grow(proc); } proc->fds->entries[proc->fds->length] = node; /* modes, offsets must be set by caller */ proc->fds->modes[proc->fds->length] = 0; proc->fds->offsets[proc->fds->length] = 0; proc->fds->length++; spin_unlock(proc->fds->lock); return proc->fds->length-1; } /** * @brief Duplicate a file descriptor to a minimum value. * * Duplicate the file descriptor at @c oldfd to a new file descriptor * with a number of a least @c newfd - always allocating a new * file descriptor in the process. * * If the current table can not hold @c newfd then the table is * expanded and any unused entries are left empty and will * be filled by @c process_append_fd normally. * * @param proc Process to modify. * @param oldfd File descriptor to copy from. * @param newfd Minimum value of new file descriptor. * @returns The actual newly allocated file descriptor. */ long process_fd_dup_least(process_t * proc, long oldfd, long newfd) { spin_lock(proc->fds->lock); /* Ensure there is at least enough space for for newfd itself */ while (proc->fds->capacity <= (unsigned long)newfd) { process_fds_grow(proc); } /* Then ensure length is at least newfds */ while (proc->fds->length <= (unsigned long)newfd) { proc->fds->entries[proc->fds->length] = NULL; proc->fds->length++; } /* Then check if anything is available already */ for (size_t i = newfd; i < proc->fds->length; ++i) { if (proc->fds->entries[i] == NULL) { /* Found a hit, copy */ process_fds_copy(proc, oldfd, i); spin_unlock(proc->fds->lock); return i; } } /* Otherwise we need to keep expanding */ if (proc->fds->length == proc->fds->capacity) { process_fds_grow(proc); } long i = proc->fds->length; process_fds_copy(proc, oldfd, i); proc->fds->length++; spin_unlock(proc->fds->lock); return i; } /** * @brief Allocate a process identifier. * * Obtains the next available process identifier. * * FIXME This used to use a bitset in Toaru32 so it could * handle overflow of the pid counter. We need to * bring that back. */ pid_t get_next_pid(void) { static pid_t _next_pid = 2; return __sync_fetch_and_add(&_next_pid,1); } /** * @brief The idle task. * * Sits in a loop forever. Scheduled whenever there is nothing * else to do. Actually always enters from the top of the function * whenever scheduled, as we don't both to save its state. */ static void _kidle(void) { while (1) { arch_pause(); switch_next(); } } /** * @brief Release a process's paging data. * * If this is a thread in a POSIX process with other * living threads, the directory is not actually released * but the reference count for it is decremented. * * XXX There's probably no reason for this to take an argument; * we only ever free directories in two places: on exec, or * when a thread exits, and that's always the current thread. */ void process_release_directory(page_directory_t * dir) { spin_lock(dir->lock); dir->refcount--; if (dir->refcount < 1) { mmu_free(dir->directory); free(dir); } else { spin_unlock(dir->lock); } } process_t * spawn_kidle(int bsp) { process_t * idle = calloc(1,sizeof(process_t)); idle->id = -1; idle->name = strdup("[kidle]"); idle->flags = PROC_FLAG_IS_TASKLET | PROC_FLAG_STARTED | PROC_FLAG_RUNNING; idle->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE)+ KERNEL_STACK_SIZE; mmu_frame_allocate( mmu_get_page(idle->image.stack - KERNEL_STACK_SIZE, 0), MMU_FLAG_KERNEL); /* TODO arch_initialize_context(uintptr_t) ? */ idle->thread.context.ip = (uintptr_t)&_kidle; idle->thread.context.sp = idle->image.stack; idle->thread.context.bp = idle->image.stack; /* FIXME Why does the idle thread have wait queues and shm mappings? * Can we make sure these are never referenced and not allocate them? */ idle->wait_queue = list_create("process wait queue (kidle)",idle); idle->shm_mappings = list_create("process shm mappings (kidle)",idle); gettimeofday(&idle->start, NULL); idle->thread.page_directory = malloc(sizeof(page_directory_t)); idle->thread.page_directory->refcount = 1; idle->thread.page_directory->directory = mmu_clone(this_core->current_pml); spin_init(idle->thread.page_directory->lock); return idle; } process_t * spawn_init(void) { process_t * init = calloc(1,sizeof(process_t)); tree_set_root(process_tree, (void*)init); init->tree_entry = process_tree->root; init->id = 1; init->group = 0; init->job = 1; init->session = 1; init->name = strdup("init"); init->cmdline = NULL; init->user = USER_ROOT_UID; init->real_user = USER_ROOT_UID; init->user_group = USER_ROOT_UID; init->real_user_group = USER_ROOT_UID; init->mask = 022; init->status = 0; init->fds = malloc(sizeof(fd_table_t)); init->fds->refs = 1; init->fds->length = 0; init->fds->capacity = 4; init->fds->entries = malloc(init->fds->capacity * sizeof(fs_node_t *)); init->fds->modes = malloc(init->fds->capacity * sizeof(int)); init->fds->offsets = malloc(init->fds->capacity * sizeof(uint64_t)); spin_init(init->fds->lock); init->wd_node = clone_fs(fs_root); init->wd_name = strdup("/"); init->image.entry = 0; init->image.heap = 0; init->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE; mmu_frame_allocate( mmu_get_page(init->image.stack - KERNEL_STACK_SIZE, 0), MMU_FLAG_KERNEL); init->image.shm_heap = USER_SHM_LOW; init->flags = PROC_FLAG_STARTED | PROC_FLAG_RUNNING; init->wait_queue = list_create("process wait queue (init)", init); init->shm_mappings = list_create("process shm mapping (init)", init); init->sched_node.prev = NULL; init->sched_node.next = NULL; init->sched_node.value = init; init->sleep_node.prev = NULL; init->sleep_node.next = NULL; init->sleep_node.value = init; init->timed_sleep_node = NULL; init->thread.page_directory = malloc(sizeof(page_directory_t)); init->thread.page_directory->refcount = 1; init->thread.page_directory->directory = this_core->current_pml; spin_init(init->thread.page_directory->lock); init->description = strdup("[init]"); list_insert(process_list, (void*)init); return init; } process_t * spawn_process(volatile process_t * parent, int flags) { process_t * proc = calloc(1,sizeof(process_t)); proc->id = get_next_pid(); proc->group = proc->id; proc->name = strdup(parent->name); proc->description = NULL; proc->cmdline = parent->cmdline; /* FIXME dup it? */ proc->user = parent->user; proc->real_user = parent->real_user; proc->user_group = parent->user_group; proc->real_user_group = parent->real_user_group; proc->mask = parent->mask; proc->job = parent->job; proc->session = parent->session; if (parent->supplementary_group_count) { proc->supplementary_group_count = parent->supplementary_group_count; proc->supplementary_group_list = malloc(sizeof(gid_t) * proc->supplementary_group_count); for (int i = 0; i < proc->supplementary_group_count; ++i) { proc->supplementary_group_list[i] = parent->supplementary_group_list[i]; } } proc->thread.context.sp = 0; proc->thread.context.bp = 0; proc->thread.context.ip = 0; memcpy((void*)proc->thread.fp_regs, (void*)parent->thread.fp_regs, 512); /* Entry is only stored for reference. */ proc->image.entry = parent->image.entry; proc->image.heap = parent->image.heap; proc->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE; mmu_frame_allocate( mmu_get_page(proc->image.stack - KERNEL_STACK_SIZE, 0), MMU_FLAG_KERNEL); proc->image.shm_heap = USER_SHM_LOW; if (flags & PROC_REUSE_FDS) { spin_lock(parent->fds->lock); proc->fds = parent->fds; proc->fds->refs++; spin_unlock(parent->fds->lock); } else { proc->fds = malloc(sizeof(fd_table_t)); spin_init(proc->fds->lock); proc->fds->refs = 1; spin_lock(parent->fds->lock); proc->fds->length = parent->fds->length; proc->fds->capacity = parent->fds->capacity; proc->fds->entries = malloc(proc->fds->capacity * sizeof(fs_node_t *)); proc->fds->modes = malloc(proc->fds->capacity * sizeof(int)); proc->fds->offsets = malloc(proc->fds->capacity * sizeof(uint64_t)); for (uint32_t i = 0; i < parent->fds->length; ++i) { proc->fds->entries[i] = clone_fs(parent->fds->entries[i]); proc->fds->modes[i] = parent->fds->modes[i]; proc->fds->offsets[i] = parent->fds->offsets[i]; } spin_unlock(parent->fds->lock); } proc->wd_node = clone_fs(parent->wd_node); proc->wd_name = strdup(parent->wd_name); proc->wait_queue = list_create("process wait queue",proc); proc->shm_mappings = list_create("process shm mappings",proc); proc->sched_node.value = proc; proc->sleep_node.value = proc; gettimeofday(&proc->start, NULL); tree_node_t * entry = tree_node_create(proc); proc->tree_entry = entry; spin_lock(tree_lock); tree_node_insert_child_node(process_tree, parent->tree_entry, entry); list_insert(process_list, (void*)proc); spin_unlock(tree_lock); return proc; } extern void tree_remove_reparent_root(tree_t * tree, tree_node_t * node); void process_reap(process_t * proc) { if (proc->tracees) { while (proc->tracees->length) { free(list_pop(proc->tracees)); } free(proc->tracees); } /* Unmark the stack bottom's fault detector */ mmu_frame_allocate( mmu_get_page(proc->image.stack - KERNEL_STACK_SIZE, 0), MMU_FLAG_KERNEL | MMU_FLAG_WRITABLE); free((void *)(proc->image.stack - KERNEL_STACK_SIZE)); process_release_directory(proc->thread.page_directory); free(proc->name); free(proc); } static int process_is_owned(process_t * proc) { for (int i = 0; i < processor_count; ++i) { if (processor_local_data[i].previous_process == proc || processor_local_data[i].current_process == proc) { return 1; } } return 0; } void process_reap_later(process_t * proc) { spin_lock(reap_lock); /* See if we can delete anything */ while (reap_queue->head) { process_t * proc = reap_queue->head->value; if (!process_is_owned(proc)) { free(list_dequeue(reap_queue)); process_reap(proc); } else { break; } } /* And delete this thing later */ list_insert(reap_queue, proc); spin_unlock(reap_lock); } /** * @brief Remove a process from the valid process list. * * Deletes a process from both the valid list and the process tree. * Any the process has any children, they become orphaned and are * moved under 'init', which is awoken if it was blocked on 'waitpid'. * * Finally, the process is freed. */ void process_delete(process_t * proc) { assert(proc != this_core->current_process); tree_node_t * entry = proc->tree_entry; if (!entry) { printf("Tried to delete process with no tree entry?\n"); return; } if (process_tree->root == entry) { printf("Tried to delete process init...\n"); return; } spin_lock(tree_lock); int has_children = entry->children->length; tree_remove_reparent_root(process_tree, entry); list_delete(process_list, list_find(process_list, proc)); spin_unlock(tree_lock); if (has_children) { /* Wake up init */ process_t * init = process_tree->root->value; wakeup_queue(init->wait_queue); } // FIXME bitset_clear(&pid_set, proc->id); proc->tree_entry = NULL; shm_release_all(proc); free(proc->shm_mappings); if (proc->supplementary_group_list) { proc->supplementary_group_count = 0; free(proc->supplementary_group_list); } /* Is someone using this process? */ for (int i = 0; i < processor_count; ++i) { if (i == this_core->cpu_id) continue; if (processor_local_data[i].previous_process == proc || processor_local_data[i].current_process == proc) { process_reap_later(proc); return; } } process_reap(proc); } /** * @brief Place an available process in the ready queue. * * Marks a process as available for general scheduling. * If the process was currently in a sleep queue, it is * marked as having been interrupted and removed from its * owning queue before being moved. * * The process must not otherwise have been in a scheduling * queue before it is placed in the ready queue. */ void make_process_ready(volatile process_t * proc) { int sleep_lock_is_mine = sleep_lock.owner == (this_core->cpu_id + 1); if (!sleep_lock_is_mine) spin_lock(sleep_lock); if (proc->sleep_node.owner != NULL) { if (proc->sleep_node.owner == sleep_queue) { /* The sleep queue is slightly special... */ if (proc->timed_sleep_node) { list_delete(sleep_queue, proc->timed_sleep_node); proc->sleep_node.owner = NULL; free(proc->timed_sleep_node->value); } } else { /* This was blocked on a semaphore we can interrupt. */ __sync_or_and_fetch(&proc->flags, PROC_FLAG_SLEEP_INT); list_delete((list_t*)proc->sleep_node.owner, (node_t*)&proc->sleep_node); } } if (!sleep_lock_is_mine) spin_unlock(sleep_lock); spin_lock(process_queue_lock); if (proc->sched_node.owner) { /* There's only one ready queue, so this means the process was already ready, which * is indicative of a bug somewhere as we shouldn't be added processes to the ready * queue multiple times. */ spin_unlock(process_queue_lock); return; } list_append(process_queue, (node_t*)&proc->sched_node); spin_unlock(process_queue_lock); arch_wakeup_others(); } /** * @brief Pop the next available process from the queue. * * Gets the next available process from the round-robin scheduling * queue. If there is no process to run, the idle task is returned. * * TODO This needs more locking for SMP... */ volatile process_t * next_ready_process(void) { spin_lock(process_queue_lock); if (!process_queue->head) { if (process_queue->length) { arch_fatal_prepare(); printf("Queue has a length but head is NULL\n"); arch_dump_traceback(); arch_fatal(); } spin_unlock(process_queue_lock); return this_core->kernel_idle_task; } node_t * np = list_dequeue(process_queue); if ((uintptr_t)np < 0xFFFFff0000000000UL || (uintptr_t)np > 0xFFFFfff000000000UL) { arch_fatal_prepare(); printf("Suspicious pointer in queue: %#zx\n", (uintptr_t)np); arch_dump_traceback(); arch_fatal(); } volatile process_t * next = np->value; if ((next->flags & PROC_FLAG_RUNNING) && (next->owner != this_core->cpu_id)) { /* We pulled a process too soon, switch to idle for a bit so the * core that marked this process as ready can finish switching away from it. */ list_append(process_queue, (node_t*)&next->sched_node); spin_unlock(process_queue_lock); return this_core->kernel_idle_task; } spin_unlock(process_queue_lock); if (!(next->flags & PROC_FLAG_FINISHED)) { __sync_or_and_fetch(&next->flags, PROC_FLAG_RUNNING); } next->owner = this_core->cpu_id; return next; } /** * @brief Signal a semaphore. * * Okay, so toaru32 used these general-purpose lists of processes * as a sort of sempahore system, so often when you see 'queue' it * can be read as 'semaphore' and be equally valid (outside of the * 'ready queue', I guess). This will awaken all processes currently * in the semaphore @p queue, unless they were marked as finished in * which case they will be discarded. * * Note that these "semaphore queues" are binary semaphores - simple * locks, but with smarter logic than the "spin_lock" primitive also * used throughout the kernel, as that just blindly switches tasks * until its atomic swap succeeds. * * @param queue The semaphore to signal * @returns the number of processes successfully awoken */ int wakeup_queue(list_t * queue) { int awoken_processes = 0; spin_lock(wait_lock_tmp); while (queue->length > 0) { node_t * node = list_pop(queue); spin_unlock(wait_lock_tmp); if (!(((process_t *)node->value)->flags & PROC_FLAG_FINISHED)) { make_process_ready(node->value); } spin_lock(wait_lock_tmp); awoken_processes++; } spin_unlock(wait_lock_tmp); return awoken_processes; } /** * @brief Signal a semaphore, exceptionally. * * Wake up everything in the semaphore @p queue but mark every * waiter as having been interrupted, rather than gracefully awoken. * Generally that means the event they were waiting for did not * happen and may never happen. * * Otherwise, same semantics as @ref wakeup_queue. */ int wakeup_queue_interrupted(list_t * queue) { int awoken_processes = 0; spin_lock(wait_lock_tmp); while (queue->length > 0) { node_t * node = list_pop(queue); spin_unlock(wait_lock_tmp); if (!(((process_t *)node->value)->flags & PROC_FLAG_FINISHED)) { process_t * proc = node->value; __sync_or_and_fetch(&proc->flags, PROC_FLAG_SLEEP_INT); make_process_ready(proc); } spin_lock(wait_lock_tmp); awoken_processes++; } spin_unlock(wait_lock_tmp); return awoken_processes; } int wakeup_queue_one(list_t * queue) { int awoken_processes = 0; spin_lock(wait_lock_tmp); if (queue->length > 0) { node_t * node = list_pop(queue); spin_unlock(wait_lock_tmp); if (!(((process_t *)node->value)->flags & PROC_FLAG_FINISHED)) { make_process_ready(node->value); } spin_lock(wait_lock_tmp); awoken_processes++; } spin_unlock(wait_lock_tmp); return awoken_processes; } /** * @brief Wait for a binary semaphore. * * Wait for an event with everyone else in @p queue. * * @returns 1 if the wait was interrupted (eg. the event did not occur); 0 otherwise. */ int sleep_on(list_t * queue) { if (this_core->current_process->sleep_node.owner) { switch_task(0); return 0; } __sync_and_and_fetch(&this_core->current_process->flags, ~(PROC_FLAG_SLEEP_INT)); spin_lock(wait_lock_tmp); list_append(queue, (node_t*)&this_core->current_process->sleep_node); spin_unlock(wait_lock_tmp); switch_task(0); return !!(this_core->current_process->flags & PROC_FLAG_SLEEP_INT); } int sleep_on_unlocking(list_t * queue, spin_lock_t * release) { __sync_and_and_fetch(&this_core->current_process->flags, ~(PROC_FLAG_SLEEP_INT)); spin_lock(wait_lock_tmp); list_append(queue, (node_t*)&this_core->current_process->sleep_node); spin_unlock(wait_lock_tmp); spin_unlock(*release); switch_task(0); return !!(this_core->current_process->flags & PROC_FLAG_SLEEP_INT); } /** * @brief Indicates whether a process is ready to be run but not currently running. */ int process_is_ready(process_t * proc) { return (proc->sched_node.owner != NULL && !(proc->flags & PROC_FLAG_RUNNING)); } int process_alert_node_locked(process_t * process, void * value); /** * @brief Wake up processes that were sleeping on timers. * * Reschedule all processes whose timed waits have expired as of * the time indicated by @p seconds and @p subseconds. If the sleep * was part of an fswait system call timing out, the call is marked * as timed out before the process is rescheduled. */ void wakeup_sleepers(unsigned long seconds, unsigned long subseconds) { spin_lock(sleep_lock); if (sleep_queue->length) { sleeper_t * proc = ((sleeper_t *)sleep_queue->head->value); while (proc && (proc->end_tick < seconds || (proc->end_tick == seconds && proc->end_subtick <= subseconds))) { if (proc->is_fswait) { proc->is_fswait = -1; process_alert_node_locked(proc->process,proc); } else { process_t * process = proc->process; process->sleep_node.owner = NULL; process->timed_sleep_node = NULL; if (!process_is_ready(process)) { make_process_ready(process); } } free(proc); free(list_dequeue(sleep_queue)); if (sleep_queue->length) { proc = ((sleeper_t *)sleep_queue->head->value); } else { break; } } } spin_unlock(sleep_lock); } /** * @brief Wait until a given time. * * Suspends the current process until the given time. The process may * still be resumed by a signal or other mechanism, in which case the * sleep will not be resumed by the kernel. */ void sleep_until(process_t * process, unsigned long seconds, unsigned long subseconds) { spin_lock(sleep_lock); if (this_core->current_process->sleep_node.owner) { spin_unlock(sleep_lock); /* Can't sleep, sleeping already */ return; } process->sleep_node.owner = sleep_queue; node_t * before = NULL; foreach(node, sleep_queue) { sleeper_t * candidate = ((sleeper_t *)node->value); if (!candidate) { printf("null candidate?\n"); continue; } if (candidate->end_tick > seconds || (candidate->end_tick == seconds && candidate->end_subtick > subseconds)) { break; } before = node; } sleeper_t * proc = malloc(sizeof(sleeper_t)); proc->process = process; proc->end_tick = seconds; proc->end_subtick = subseconds; proc->is_fswait = 0; process->timed_sleep_node = list_insert_after(sleep_queue, before, proc); spin_unlock(sleep_lock); } uint8_t process_compare(void * proc_v, void * pid_v) { pid_t pid = (*(pid_t *)pid_v); process_t * proc = (process_t *)proc_v; return (uint8_t)(proc->id == pid); } process_t * process_from_pid(pid_t pid) { if (pid < 0) return NULL; spin_lock(tree_lock); tree_node_t * entry = tree_find(process_tree,&pid,process_compare); spin_unlock(tree_lock); if (entry) { return (process_t *)entry->value; } return NULL; } long process_move_fd(process_t * proc, long src, long dest) { if ((size_t)src >= proc->fds->length || (dest != -1 && (size_t)dest >= proc->fds->length)) { return -1; } if (dest == -1) { dest = process_append_fd(proc, NULL); } if (proc->fds->entries[dest] != proc->fds->entries[src]) { close_fs(proc->fds->entries[dest]); process_fds_copy(proc, src, dest); } return dest; } void tasking_start(void) { this_core->current_process = spawn_init(); this_core->kernel_idle_task = spawn_kidle(1); } static int wait_candidate(volatile process_t * parent, int pid, int options, volatile process_t * proc) { if (!proc) return 0; if (options & WNOKERN) { /* Skip kernel processes */ if (proc->flags & PROC_FLAG_IS_TASKLET) return 0; } if (pid < -1) { if (proc->job == -pid || proc->id == -pid) return 1; } else if (pid == 0) { /* Matches our group ID */ if (proc->job == parent->id) return 1; } else if (pid > 0) { /* Specific pid */ if (proc->id == pid) return 1; } else { return 1; } return 0; } int waitpid(int pid, int * status, int options) { volatile process_t * volatile proc = (process_t*)this_core->current_process; #if 0 if (proc->group) { proc = process_from_pid(proc->group); } #endif do { volatile process_t * candidate = NULL; int has_children = 0; int is_parent = 0; spin_lock(proc->wait_lock); /* First, find out if there is anyone to reap */ foreach(node, proc->tree_entry->children) { if (!node->value) { continue; } volatile process_t * volatile child = ((tree_node_t *)node->value)->value; if (wait_candidate(proc, pid, options, child)) { has_children = 1; is_parent = 1; if (child->flags & PROC_FLAG_FINISHED) { candidate = child; break; } if ((child->flags & PROC_FLAG_SUSPENDED) && ((child->status & 0xFF) == 0x7F)) { int reason = (child->status >> 16) & 0xFF; if ((options & WSTOPPED) || (reason == 0xFF && (options & WUNTRACED))) { candidate = child; break; } } } } if (!candidate && proc->tracees) { foreach(node, proc->tracees) { process_t * child = node->value; if (wait_candidate(proc,pid,options,child)) { has_children = 1; if (child->flags & (PROC_FLAG_SUSPENDED | PROC_FLAG_FINISHED)) { candidate = child; break; } } } } if (!has_children) { /* No valid children matching this description */ spin_unlock(proc->wait_lock); return -ECHILD; } if (candidate) { spin_unlock(proc->wait_lock); if (status) { *status = candidate->status; } candidate->status &= ~0xFF; int pid = candidate->id; if (is_parent && (candidate->flags & PROC_FLAG_FINISHED)) { while (*((volatile int *)&candidate->flags) & PROC_FLAG_RUNNING); proc->time_children += candidate->time_children + candidate->time_total; proc->time_sys_children += candidate->time_sys_children + candidate->time_sys; process_delete((process_t*)candidate); } return pid; } else { if (options & WNOHANG) { spin_unlock(proc->wait_lock); return 0; } /* Wait */ if (sleep_on_unlocking(proc->wait_queue, &proc->wait_lock) != 0) { return -EINTR; } } } while (1); } int process_timeout_sleep(process_t * process, int timeout) { unsigned long s, ss; relative_time(0, timeout * 1000, &s, &ss); node_t * before = NULL; foreach(node, sleep_queue) { sleeper_t * candidate = ((sleeper_t *)node->value); if (candidate->end_tick > s || (candidate->end_tick == s && candidate->end_subtick > ss)) { break; } before = node; } sleeper_t * proc = malloc(sizeof(sleeper_t)); proc->process = process; proc->end_tick = s; proc->end_subtick = ss; proc->is_fswait = 1; list_insert(((process_t *)process)->node_waits, proc); process->timeout_node = list_insert_after(sleep_queue, before, proc); return 0; } int process_wait_nodes(process_t * process,fs_node_t * nodes[], int timeout) { fs_node_t ** n = nodes; int index = 0; if (*n) { do { int result = selectcheck_fs(*n); if (result < 0) { return -EBADF; } if (result == 0) { return index; } n++; index++; } while (*n); } if (timeout == 0) { return index; } n = nodes; spin_lock(sleep_lock); spin_lock(process->sched_lock); process->node_waits = list_create("process fswaiters",process); if (*n) { do { if (selectwait_fs(*n, process) < 0) { printf("bad selectwait?\n"); } n++; } while (*n); } if (timeout > 0) { process_timeout_sleep(process, timeout); } else { process->timeout_node = NULL; } process->awoken_index = -1; spin_unlock(process->sched_lock); spin_unlock(sleep_lock); /* Wait. */ switch_task(0); return process->awoken_index; } int process_awaken_from_fswait(process_t * process, int index) { must_have_lock(sleep_lock); process->awoken_index = index; list_free(process->node_waits); free(process->node_waits); process->node_waits = NULL; if (process->timeout_node && process->timeout_node->owner == sleep_queue) { sleeper_t * proc = process->timeout_node->value; if (proc->is_fswait != -1) { list_delete(sleep_queue, process->timeout_node); free(process->timeout_node->value); free(process->timeout_node); } } process->timeout_node = NULL; make_process_ready(process); spin_unlock(process->sched_lock); return 0; } void process_awaken_signal(process_t * process) { spin_lock(sleep_lock); spin_lock(process->sched_lock); if (process->node_waits) { process_awaken_from_fswait(process, -EINTR); } else { spin_unlock(process->sched_lock); } spin_unlock(sleep_lock); } int process_alert_node_locked(process_t * process, void * value) { must_have_lock(sleep_lock); if (!is_valid_process(process)) { dprintf("core %d (pid=%d %s) attempted to alert invalid process %#zx\n", this_core->cpu_id, this_core->current_process->id, this_core->current_process->name, (uintptr_t)process); return 0; } spin_lock(process->sched_lock); if (!process->node_waits) { spin_unlock(process->sched_lock); return 0; /* Possibly already returned. Wait for another call. */ } int index = 0; foreach(node, process->node_waits) { if (value == node->value) { return process_awaken_from_fswait(process, index); } index++; } spin_unlock(process->sched_lock); return -1; } int process_alert_node(process_t * process, void * value) { spin_lock(sleep_lock); int result = process_alert_node_locked(process, value); spin_unlock(sleep_lock); return result; } process_t * process_get_parent(process_t * process) { process_t * result = NULL; spin_lock(tree_lock); tree_node_t * entry = process->tree_entry; if (entry->parent) { result = entry->parent->value; } spin_unlock(tree_lock); return result; } void task_exit(int retval) { this_core->current_process->status = retval; /* free whatever we can */ list_free(this_core->current_process->wait_queue); free(this_core->current_process->wait_queue); free(this_core->current_process->wd_name); if (this_core->current_process->node_waits) { list_free(this_core->current_process->node_waits); free(this_core->current_process->node_waits); this_core->current_process->node_waits = NULL; } if (this_core->current_process->fds) { spin_lock(this_core->current_process->fds->lock); this_core->current_process->fds->refs--; if (this_core->current_process->fds->refs == 0) { for (uint32_t i = 0; i < this_core->current_process->fds->length; ++i) { if (this_core->current_process->fds->entries[i]) { close_fs(this_core->current_process->fds->entries[i]); this_core->current_process->fds->entries[i] = NULL; } } free(this_core->current_process->fds->entries); free(this_core->current_process->fds->offsets); free(this_core->current_process->fds->modes); free(this_core->current_process->fds); this_core->current_process->fds = NULL; } else { spin_unlock(this_core->current_process->fds->lock); } } if (this_core->current_process->tracees) { spin_lock(this_core->current_process->wait_lock); while (this_core->current_process->tracees->length) { node_t * n = list_pop(this_core->current_process->tracees); process_t * tracee = n->value; free(n); if (is_valid_process(tracee)) { tracee->tracer = 0; __sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_TRACE_SIGNALS | PROC_FLAG_TRACE_SYSCALLS)); if (tracee->flags & PROC_FLAG_SUSPENDED) { tracee->status = 0; __sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED)); make_process_ready(tracee); } } } spin_unlock(this_core->current_process->wait_lock); } update_process_times(); process_t * parent = process_get_parent((process_t *)this_core->current_process); __sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_FINISHED); if (this_core->current_process->tracer) { process_t * tracer = process_from_pid(this_core->current_process->tracer); if (tracer && tracer != parent) { spin_lock(tracer->wait_lock); wakeup_queue(tracer->wait_queue); spin_unlock(tracer->wait_lock); } } if (parent && !(parent->flags & PROC_FLAG_FINISHED)) { spin_lock(parent->wait_lock); send_signal(parent->group, SIGCHLD, 1); wakeup_queue(parent->wait_queue); spin_unlock(parent->wait_lock); } switch_next(); } #define PUSH(stack, type, item) stack -= sizeof(type); \ *((volatile type *) stack) = item pid_t fork(void) { uintptr_t sp, bp; process_t * parent = (process_t*)this_core->current_process; union PML * directory = mmu_clone(parent->thread.page_directory->directory); process_t * new_proc = spawn_process(parent, 0); new_proc->thread.page_directory = malloc(sizeof(page_directory_t)); new_proc->thread.page_directory->refcount = 1; new_proc->thread.page_directory->directory = directory; spin_init(new_proc->thread.page_directory->lock); memcpy(new_proc->signals, parent->signals, sizeof(struct signal_config) * (NUMSIGNALS+1)); new_proc->blocked_signals = parent->blocked_signals; struct regs r; memcpy(&r, parent->syscall_registers, sizeof(struct regs)); sp = new_proc->image.stack; bp = sp; arch_syscall_return(&r, 0); PUSH(sp, struct regs, r); new_proc->syscall_registers = (void*)sp; new_proc->thread.context.sp = sp; new_proc->thread.context.bp = bp; new_proc->thread.context.tls_base = parent->thread.context.tls_base; new_proc->thread.context.ip = (uintptr_t)&arch_resume_user; arch_save_context(&parent->thread); memcpy(new_proc->thread.context.saved, parent->thread.context.saved, sizeof(parent->thread.context.saved)); #if 0 printf("fork(): resuming with register context\n"); extern void aarch64_regs(struct regs *); aarch64_regs(&r); printf("fork(): and arch context:\n"); extern void aarch64_context(process_t * proc); aarch64_context(new_proc); #endif if (parent->flags & PROC_FLAG_IS_TASKLET) new_proc->flags |= PROC_FLAG_IS_TASKLET; make_process_ready(new_proc); return new_proc->id; } pid_t clone(uintptr_t new_stack, uintptr_t thread_func, uintptr_t arg) { uintptr_t sp, bp; process_t * parent = (process_t *)this_core->current_process; process_t * new_proc = spawn_process(this_core->current_process, 1); new_proc->thread.page_directory = this_core->current_process->thread.page_directory; spin_lock(new_proc->thread.page_directory->lock); new_proc->thread.page_directory->refcount++; spin_unlock(new_proc->thread.page_directory->lock); memcpy(new_proc->signals, parent->signals, sizeof(struct signal_config) * (NUMSIGNALS+1)); new_proc->blocked_signals = parent->blocked_signals; struct regs r; memcpy(&r, parent->syscall_registers, sizeof(struct regs)); sp = new_proc->image.stack; bp = sp; /* Set the gid */ if (this_core->current_process->group) { new_proc->group = this_core->current_process->group; } else { /* We are the session leader */ new_proc->group = this_core->current_process->id; } /* different calling convention */ #if defined(__x86_64__) r.rdi = arg; PUSH(new_stack, uintptr_t, (uintptr_t)0); #elif defined(__aarch64__) r.x0 = arg; r.x30 = 0; #endif PUSH(sp, struct regs, r); new_proc->syscall_registers = (void*)sp; #if defined(__x86_64__) new_proc->syscall_registers->rsp = new_stack; new_proc->syscall_registers->rbp = new_stack; new_proc->syscall_registers->rip = thread_func; #elif defined(__aarch64__) new_proc->syscall_registers->user_sp = new_stack; new_proc->syscall_registers->x29 = new_stack; new_proc->thread.context.saved[10] = thread_func; #endif new_proc->thread.context.sp = sp; new_proc->thread.context.bp = bp; new_proc->thread.context.tls_base = this_core->current_process->thread.context.tls_base; new_proc->thread.context.ip = (uintptr_t)&arch_resume_user; if (parent->flags & PROC_FLAG_IS_TASKLET) new_proc->flags |= PROC_FLAG_IS_TASKLET; make_process_ready(new_proc); return new_proc->id; } process_t * spawn_worker_thread(void (*entrypoint)(void * argp), const char * name, void * argp) { process_t * proc = calloc(1,sizeof(process_t)); proc->flags = PROC_FLAG_IS_TASKLET | PROC_FLAG_STARTED; proc->id = get_next_pid(); proc->group = proc->id; proc->name = strdup(name); proc->description = NULL; proc->cmdline = NULL; /* Are these necessary for tasklets? Should probably all be zero. */ proc->user = 0; proc->real_user = 0; proc->user_group = 0; proc->real_user_group = 0; proc->mask = 0; proc->job = proc->id; proc->session = proc->id; proc->thread.page_directory = malloc(sizeof(page_directory_t)); proc->thread.page_directory->refcount = 1; proc->thread.page_directory->directory = mmu_clone(mmu_get_kernel_directory()); spin_init(proc->thread.page_directory->lock); proc->image.stack = (uintptr_t)valloc(KERNEL_STACK_SIZE) + KERNEL_STACK_SIZE; PUSH(proc->image.stack, uintptr_t, (uintptr_t)entrypoint); PUSH(proc->image.stack, void*, argp); proc->thread.context.sp = proc->image.stack; proc->thread.context.bp = proc->image.stack; proc->thread.context.ip = (uintptr_t)&arch_enter_tasklet; proc->wait_queue = list_create("worker thread wait queue",proc); proc->shm_mappings = list_create("worker thread shm mappings",proc); proc->sched_node.value = proc; proc->sleep_node.value = proc; gettimeofday(&proc->start, NULL); tree_node_t * entry = tree_node_create(proc); proc->tree_entry = entry; spin_lock(tree_lock); tree_node_insert_child_node(process_tree, this_core->current_process->tree_entry, entry); list_insert(process_list, (void*)proc); spin_unlock(tree_lock); make_process_ready(proc); return proc; } static void update_one_process(uint64_t clock_ticks, uint64_t perf_scale, process_t * proc) { proc->usage[3] = proc->usage[2]; proc->usage[2] = proc->usage[1]; proc->usage[1] = proc->usage[0]; proc->usage[0] = (1000 * (proc->time_total - proc->time_prev)) / (clock_ticks * perf_scale); proc->time_prev = proc->time_total; } void update_process_usage(uint64_t clock_ticks, uint64_t perf_scale) { spin_lock(tree_lock); foreach(lnode, process_list) { process_t * proc = lnode->value; update_one_process(clock_ticks, perf_scale, proc); } spin_unlock(tree_lock); /* Now use idle tasks to calculator processor activity? */ for (int i = 0; i < processor_count; ++i) { process_t * proc = processor_local_data[i].kernel_idle_task; update_one_process(clock_ticks, perf_scale, proc); } } ================================================ FILE: kernel/sys/ptrace.c ================================================ /** * @file kernel/sys/ptrace.c * @brief Process tracing functions * * Provides single stepping, cross-process memory inspection, * regiser inspection, poking, and syscall trace events. * * @warning This ptrace implementation is incomplete. * * We are missing a lot of @c ptrace functionality found in other * operating systems, and even some of the functionality we have is * only partially implemented or may not work as it should. * * This implementation was intended primarily to support having a * @c strace command in userspace, and also provides some limited * support for a debugger. * * @see apps/dbg.c * @see apps/strace.c * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #if defined(__x86_64__) #include #elif defined(__aarch64__) #include #else #error "no regs" #endif /** * @brief Internally set the tracer of a tracee process. * * Sets up @p tracer to trace @p tracee and sets @p tracee as * tracing the default events (syscalls and signals). * * A tracer can trace multiple tracees, but a tracee can only be * traced by one tracer. * * @param tracer Process that is the doing the tracing * @param tracee Process that is breing traced */ static void _ptrace_trace(process_t * tracer, process_t * tracee) { spin_lock(tracer->wait_lock); __sync_or_and_fetch(&tracee->flags, (PROC_FLAG_TRACE_SYSCALLS | PROC_FLAG_TRACE_SIGNALS)); if (!tracer->tracees) { tracer->tracees = list_create("debug tracees", tracer); } list_insert(tracer->tracees, tracee); tracee->tracer = tracer->id; spin_unlock(tracer->wait_lock); } /** * @brief Start tracing a process. * * @ref PTRACE_ATTACH * * Sets the current process to be the tracer for the target tracee. * Both the tracer and tracee will resume normally, until the next * ptrace event stops the tracee. * * TODO What happens if the process is already being traced? * * @param pid Tracee ID * @returns 0 on success, -ESRCH if the tracee is invalid, -EPERM if the tracee * is not owned by the same user as the tracer and the tracer is not root. */ long ptrace_attach(pid_t pid) { process_t * tracer = (process_t *)this_core->current_process; process_t * tracee = process_from_pid(pid); if (!tracee) return -ESRCH; if (tracer->user != 0 && tracer->user != tracee->user) return -EPERM; _ptrace_trace(tracer, tracee); return 0; } /** * @brief Set the current process to be traced by its parent. * * @ref PTRACE_TRACEME * * Generally, this is used through the @c ptrace system call by * the debugger or @c strace implementation after forking a child * process and before calling @c exec. * * The calling process will resume immediately. * * TODO What happens if we are already being traced? * * @returns 0 on success, -EINVAL if the parent was not found. */ long ptrace_self(void) { process_t * tracee = (process_t*)this_core->current_process; process_t * tracer = process_get_parent(tracee); if (!tracer) return -EINVAL; _ptrace_trace(tracer, tracee); return 0; } /** * @brief Trigger a ptrace event on the currently executing thread. * * @ref PTRACE_EVENT_SINGLESTEP * @ref PTRACE_EVENT_SYSCALL_ENTER * @ref PTRACE_EVENT_SYSCALL_EXIT * * Called elsewhere in the kernel when a trace event happens that is * not currently being ignored, such as upon entry into a syscall handler, * or exit from a syscall handler, or before a signal would be delivered. * * Runs in the kernel context of the tracee, causes the tracee to be suspended * and awakens the tracer to return from its @c ptrace call. * * When the kernel context for this process is resumed, the signal number * will be checked from the tracee's status and returned to caller that * initiated the ptrace event. * * When resuming from a signal event, the new signal number will replace the * old signal number. In this case, if the new signal number is 0 it will * be discarded and the tracee will continue as if it had ignored it. * * When resuming from other events, signals are generally sent directly * and the process will act on the signal when it has an opportunity to * return to userspace. * * @param signal Signal number if @p reason is 0. * @param reason PTRACE_EVENT value describing the event; 0 for signal delivery. * @returns Signal number from tracee status upon resumption. */ long ptrace_signal(int signal, int reason) { this_core->current_process->status = 0x7F | (signal << 8) | (reason << 16); __sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_SUSPENDED); process_t * parent = process_from_pid(this_core->current_process->tracer); if (parent && !(parent->flags & PROC_FLAG_FINISHED)) { spin_lock(parent->wait_lock); wakeup_queue(parent->wait_queue); spin_unlock(parent->wait_lock); } switch_task(0); int signum = (this_core->current_process->status >> 8); this_core->current_process->status = 0; return signum; } /** * @brief Resume a traced process. * * Unsuspends the traced process, sending an appropriate signal if one * was currently pending or if one was sent by the tracer through either * of @ref ptrace_continue or @ref ptrace_detach. * * @param pid Tracee ID * @param tracee Tracee process object * @param sig Signal number to send, or 0 if none. */ static void signal_and_continue(pid_t pid, process_t * tracee, int sig) { /* Unsuspend */ __sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED)); /* Does the process have a pending signal? */ if ((tracee->status >> 8) & 0xFF && (!(tracee->status >> 16) || ((tracee->status >> 16) == 0xFF))) { tracee->status = (sig << 8); make_process_ready(tracee); } else if (sig) { send_signal(pid, sig,1); } else { make_process_ready(tracee); } } /** * @brief Resume the tracee until the next event. * * @ref PTRACE_CONT * * Allows the tracee to resume execution, while optionally sending * a signal. This signal may be the one that triggered the ptrace * event from which the process is being resumed, or a new signal, * or no signal at all. * * @param pid Tracee ID * @param sig Signal to send to tracee on resume, or 0 for none. * @returns 0 on success, -ESRCH if tracee is invalid. */ long ptrace_continue(pid_t pid, int sig) { process_t * tracee = process_from_pid(pid); if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; signal_and_continue(pid,tracee,sig); return 0; } /** * @brief Stop tracing a tracee. * * @ref PTRACE_DETACH * * Marks the tracee as no longer being traced and resumes it. * * @param pid Tracee ID * @param sig Signal to send to tracee on resume, or 0 for none. * @returns 0 on success, -ESRCH if tracee is invalid. */ long ptrace_detach(pid_t pid, int sig) { process_t * tracee = process_from_pid(pid); if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; /* Mark us not the tracer. */ tracee->tracer = 0; signal_and_continue(pid,tracee,sig); return 0; } /** * @brief Obtain the register context of the tracee. * * @ref PTRACE_GETREGS * * Copies the interrupt register context of the tracee into a tracer-provided * address. The size, meaning, and layout of the data copied is architecture-dependent. * * On AArch64 we also add ELR, which isn't in the interrupt or syscall register contexts, * but pushed somewhere else... * * TODO We should support reading FPU regs as well. * * @param pid Tracee ID * @param data Address in tracer to write data into. * @returns 0 on success, -ESRCH if tracee is invalid. */ long ptrace_getregs(pid_t pid, void * data) { if (!data || ptr_validate(data, "ptrace")) return -EFAULT; process_t * tracee = process_from_pid(pid); if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; /* Copy registers */ memcpy(data, tracee->syscall_registers, sizeof(struct regs)); #ifdef __aarch64__ memcpy((char*)data + sizeof(struct regs), &tracee->thread.context.saved[10], sizeof(uintptr_t)); #endif return 0; } /** * @brief Modify the registers of the tracee. * * @ref PTRACE_SETREGS * * @param pid Tracee ID * @param data Address in tracer to read data from. * @returns 0 on success, -ESRCH if tracee is invalid. */ long ptrace_setregs(pid_t pid, void * data) { if (!data || ptr_validate(data, "ptrace")) return -EFAULT; process_t * tracee = process_from_pid(pid); if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; /* Copy registers */ memcpy(tracee->syscall_registers, data, sizeof(struct regs)); #ifdef __aarch64__ memcpy(&tracee->thread.context.saved[10], (char*)data + sizeof(struct regs), sizeof(uintptr_t)); #endif return 0; } /** * @brief Read one byte from the tracee's memory. * * @ref PTRACE_PEEKDATA * * Reads one byte of data from the tracee process's memory space. * Other implementations of @c PTRACE_PEEKDATA may write other sizes of data, * but to make this as straightforward as possible, we only support single * bytes. Maybe in the future we'll support other sizes... * * @param pid Tracee ID * @param addr Virtual address in the tracee context to write to. * @param data Address in the tracer to store the read byte into. * @returns 0 on success, -EFAULT if the requested address is not mapped and readable in the tracee, -ESRCH if tracee is invalid. */ long ptrace_peek(pid_t pid, void * addr, void * data) { if (!data || ptr_validate(data, "ptrace")) return -EFAULT; process_t * tracee = process_from_pid(pid); if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; union PML * page_entry = mmu_get_page_other(tracee->thread.page_directory->directory, (uintptr_t)addr); if (!page_entry) return -EFAULT; if (!mmu_page_is_user_readable(page_entry)) return -EFAULT; uintptr_t mapped_address = mmu_map_to_physical(tracee->thread.page_directory->directory, (uintptr_t)addr); if ((intptr_t)mapped_address < 0 && (intptr_t)mapped_address > -10) return -EFAULT; uintptr_t blarg = (uintptr_t)mmu_map_from_physical(mapped_address); /* Yeah, uh, one byte. That works. */ *(char*)data = *(char*)blarg; return 0; } /** * @brief Place a byte of data into the tracee's memory. * * @ref PTRACE_POKEDATA * * Writes one byte of data into the tracee process's memory space. * Other implementations of @c PTRACE_POKEDATA may write other sizes of data, * but to make this as straightforward as possible, we only support single * bytes. Maybe in the future we'll support other sizes... * * TODO This uses mmu_map_from_physical and doesn't do any cache maintenance? * It will probably break when, eg., poking instructions on ARM... * * @param pid Tracee ID * @param addr Virtual address in the tracee context to write to. * @param data Address in the tracer context to read one byte from. * @returns 0 on success, -ESRCH if tracee is invalid, -EFAULT if the tracee address is not mapped or not writable. */ long ptrace_poke(pid_t pid, void * addr, void * data) { if (!data || ptr_validate(data, "ptrace")) return -EFAULT; process_t * tracee = process_from_pid(pid); if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; union PML * page_entry = mmu_get_page_other(tracee->thread.page_directory->directory, (uintptr_t)addr); if (!page_entry) return -EFAULT; if (!mmu_page_is_user_writable(page_entry)) return -EFAULT; uintptr_t mapped_address = mmu_map_to_physical(tracee->thread.page_directory->directory, (uintptr_t)addr); if ((intptr_t)mapped_address < 0 && (intptr_t)mapped_address > -10) return -EFAULT; uintptr_t blarg = (uintptr_t)mmu_map_from_physical(mapped_address); /* Yeah, uh, one byte. That works. */ *(char*)blarg = *(char*)data; return 0; } /** * @brief Disable tracing of syscalls in the tracee. * * @ref PTRACE_SIGNALS_ONLY_PLZ * * Turns off tracing of syscalls in the tracee. Only signals will be * traced. To turn syscall tracing back on, restart tracing by detaching * and re-attaching to the tracee. * * TODO We need a better interface to configure tracing, so we can offer * more complex options than just signals and syscalls... * * @param pid Tracee ID * @returns 0 on success, -ESRCH if the tracee was not found or the current process is not its tracer. */ long ptrace_signals_only(pid_t pid) { process_t * tracee = process_from_pid(pid); if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; __sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_TRACE_SYSCALLS)); return 0; } /** * @brief Enable single-stepping for a process. * * @ref PTRACE_SINGLESTEP * * Enables an architecture-specific mechanism for single step debugging * in the requested process. When the process resumes, it will execute * one instruction and then fault back to the kernel, and the tracer * will be alerted. * * Single stepping will be disabled again when the process returns from * the fault, and must be re-enabled by another call to @c ptrace_singlstep. * * @param pid ID of the process to enable single-step for * @param sig Signal number to hand to the process when it resumes, or 0. * @returns 0 on success, -ESRCH if the process could not be found or is not a tracee of the current process. */ long ptrace_singlestep(pid_t pid, int sig) { process_t * tracee = process_from_pid(pid); if (!tracee || (tracee->tracer != this_core->current_process->id) || !(tracee->flags & PROC_FLAG_SUSPENDED)) return -ESRCH; /* arch_set_singlestep? */ #if defined(__x86_64__) struct regs * target = tracee->syscall_registers; target->rflags |= (1 << 8); #elif defined(__aarch64__) tracee->thread.context.saved[11] |= (1 << 21); #endif __sync_and_and_fetch(&tracee->flags, ~(PROC_FLAG_SUSPENDED)); tracee->status = (sig << 8); make_process_ready(tracee); return 0; } /** * @brief Handle ptrace system call requests. * * Internal interface for dispatching @c ptrace system calls. Maps * arguments from the system call to the various ptrace functions. * * @note This is the direct system call implementation. Data coming * in here is directly from the arguments of the system call. * * @param request Request type * @param pid Tracee ID * @param addr Address to peek or poke * @param data Place to put or read data, depending on the function * @returns Generally, status codes. -EINVAL for an invalid request. */ long ptrace_handle(long request, pid_t pid, void * addr, void * data) { switch (request) { case PTRACE_ATTACH: return ptrace_attach(pid); case PTRACE_TRACEME: return ptrace_self(); case PTRACE_GETREGS: return ptrace_getregs(pid,data); case PTRACE_CONT: return ptrace_continue(pid,(uintptr_t)data); case PTRACE_PEEKDATA: return ptrace_peek(pid,addr,data); case PTRACE_POKEDATA: return ptrace_poke(pid,addr,data); case PTRACE_SIGNALS_ONLY_PLZ: return ptrace_signals_only(pid); case PTRACE_SINGLESTEP: return ptrace_singlestep(pid,(uintptr_t)data); case PTRACE_DETACH: return ptrace_detach(pid,(uintptr_t)data); case PTRACE_SETREGS: return ptrace_setregs(pid,data); default: return -EINVAL; } } ================================================ FILE: kernel/sys/shm.c ================================================ /** * @file kernel/sys/shm.c * @brief Shared memory subsystem * * Provides shared memory mappings for userspace processes and * manages their allocation/deallocation for process cleanup. * Used primarily to implement text buffers for the compositor. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2021 K. Lange * Copyright (C) 2012 Markus Schober */ #include #include #include #include #include #include #include #include #include #include #include //static volatile uint8_t bsl; // big shm lock static spin_lock_t bsl; // big shm lock tree_t * shm_tree = NULL; void shm_install(void) { shm_tree = tree_create(); tree_set_root(shm_tree, NULL); } /* Accessors */ static shm_node_t * _get_node(char * shm_path, int create, tree_node_t * from) { char *pch, *save; pch = strtok_r(shm_path, SHM_PATH_SEPARATOR, &save); tree_node_t * tnode = from; foreach(node, tnode->children) { tree_node_t * _node = (tree_node_t *)node->value; shm_node_t * snode = (shm_node_t *)_node->value; if (!strcmp(snode->name, pch)) { if (*save == '\0') { return snode; } return _get_node(save, create, _node); } } /* The next node in sequence was not found */ if (create) { shm_node_t * nsnode = malloc(sizeof(shm_node_t)); memcpy(nsnode->name, pch, strlen(pch) + 1); nsnode->chunk = NULL; tree_node_t * nnode = tree_node_insert_child(shm_tree, from, nsnode); if (*save == '\0') { return nsnode; } return _get_node(save, create, nnode); } else { return NULL; } } static shm_node_t * get_node (char * shm_path, int create) { char * _path = malloc(strlen(shm_path)+1); memcpy(_path, shm_path, strlen(shm_path)+1); shm_node_t * node = _get_node(_path, create, shm_tree->root); free(_path); return node; } /* Create and Release */ static shm_chunk_t * create_chunk (shm_node_t * parent, size_t size) { if (!size) return NULL; shm_chunk_t *chunk = malloc(sizeof(shm_chunk_t)); if (chunk == NULL) { return NULL; } chunk->parent = parent; chunk->lock = 0; chunk->ref_count = 1; chunk->num_frames = (size / 0x1000) + ((size % 0x1000) ? 1 : 0); chunk->frames = malloc(sizeof(uintptr_t) * chunk->num_frames); if (chunk->frames == NULL) { free(chunk); return NULL; } /* Now grab some frames for this guy. */ for (uint32_t i = 0; i < chunk->num_frames; i++) { /* Allocate frame */ uintptr_t index = mmu_allocate_a_frame(); chunk->frames[i] = index; } return chunk; } static int release_chunk (shm_chunk_t * chunk) { if (chunk) { chunk->ref_count--; /* Does the chunk need to be freed? */ if (chunk->ref_count < 1) { #if 0 debug_print(INFO, "Freeing chunk with name %s", chunk->parent->name); #endif /* First, free the frames used by this chunk */ for (uint32_t i = 0; i < chunk->num_frames; i++) { mmu_frame_release(chunk->frames[i] << 12); } /* Then, get rid of the damn thing */ chunk->parent->chunk = NULL; free(chunk->frames); free(chunk); } return 0; } return -1; } /* Mapping and Unmapping */ static uintptr_t proc_sbrk(uint32_t num_pages, volatile process_t * volatile proc) { uintptr_t initial = proc->image.shm_heap; if (initial & 0xFFF) { initial += 0x1000 - (initial & 0xFFF); proc->image.shm_heap = initial; } proc->image.shm_heap += num_pages << 12; return initial; } static void * map_in (shm_chunk_t * chunk, volatile process_t * volatile proc) { if (!chunk) { return NULL; } shm_mapping_t * mapping = malloc(sizeof(shm_mapping_t)); mapping->chunk = chunk; mapping->num_vaddrs = chunk->num_frames; mapping->vaddrs = malloc(sizeof(uintptr_t) * mapping->num_vaddrs); uintptr_t last_address = USER_SHM_LOW; foreach(node, proc->shm_mappings) { shm_mapping_t * m = node->value; if (m->vaddrs[0] > last_address) { size_t gap = (uintptr_t)m->vaddrs[0] - last_address; if (gap >= mapping->num_vaddrs * 0x1000) { /* Map the gap */ for (unsigned int i = 0; i < chunk->num_frames; ++i) { union PML * page = mmu_get_page(last_address + (i << 12), MMU_GET_MAKE); page->bits.page = chunk->frames[i]; mmu_frame_allocate(page, MMU_FLAG_WRITABLE); mapping->vaddrs[i] = last_address + (i << 12); } /* Insert us before this node */ list_insert_before(proc->shm_mappings, node, mapping); return (void *)mapping->vaddrs[0]; } } last_address = m->vaddrs[0] + m->num_vaddrs * 0x1000; } if (proc->image.shm_heap > last_address) { size_t gap = proc->image.shm_heap - last_address; if (gap >= mapping->num_vaddrs * 0x1000) { for (unsigned int i = 0; i < chunk->num_frames; ++i) { union PML * page = mmu_get_page(last_address + (i << 12), MMU_GET_MAKE); page->bits.page = chunk->frames[i]; mmu_frame_allocate(page, MMU_FLAG_WRITABLE); mapping->vaddrs[i] = last_address + (i << 12); } list_insert(proc->shm_mappings, mapping); return (void *)mapping->vaddrs[0]; } } for (uint32_t i = 0; i < chunk->num_frames; i++) { uintptr_t new_vpage = proc_sbrk(1, proc); union PML * page = mmu_get_page(new_vpage, MMU_GET_MAKE); page->bits.page = chunk->frames[i]; mmu_frame_allocate(page, MMU_FLAG_WRITABLE); mapping->vaddrs[i] = new_vpage; } list_insert(proc->shm_mappings, mapping); return (void *)mapping->vaddrs[0]; } static size_t chunk_size (shm_chunk_t * chunk) { return (size_t)(chunk->num_frames * 0x1000); } /* Kernel-Facing Functions and Syscalls */ void * shm_obtain (char * path, size_t * size) { spin_lock(bsl); volatile process_t * volatile proc = this_core->current_process; if (proc->group != 0) { proc = process_from_pid(proc->group); } shm_node_t * node = get_node(path, 1); // (if it exists, just get it) shm_chunk_t * chunk = node->chunk; if (chunk == NULL) { /* There's no chunk for that key -- we need to allocate it! */ if (!size) { // The process doesn't want a chunk...? spin_unlock(bsl); return NULL; } chunk = create_chunk(node, *size); if (chunk == NULL) { spin_unlock(bsl); return NULL; } node->chunk = chunk; } else { /* New accessor! */ chunk->ref_count++; } void * vshm_start = map_in(chunk, proc); *size = chunk_size(chunk); spin_unlock(bsl); return vshm_start; } int shm_release (char * path) { spin_lock(bsl); process_t * proc = (process_t *)this_core->current_process; if (proc->group != 0) { proc = process_from_pid(proc->group); } /* First, find the right chunk */ shm_node_t * _node = get_node(path, 0); if (!_node) { spin_unlock(bsl); return 1; } shm_chunk_t * chunk = _node->chunk; /* Next, find the proc's mapping for that chunk */ node_t * node = NULL; foreach (n, proc->shm_mappings) { shm_mapping_t * m = (shm_mapping_t *)n->value; if (m->chunk == chunk) { node = n; break; } } if (node == NULL) { spin_unlock(bsl); return 1; } shm_mapping_t * mapping = (shm_mapping_t *)node->value; /* Clear the mappings from the process's address space */ for (uint32_t i = 0; i < mapping->num_vaddrs; i++) { union PML * page = mmu_get_page(mapping->vaddrs[i], 0); page->bits.present = 0; mmu_invalidate(mapping->vaddrs[i]); } /* Clean up */ release_chunk(chunk); list_delete(proc->shm_mappings, node); free(node); free(mapping); spin_unlock(bsl); return 0; } /* This function should only be called if the process's address space * is about to be destroyed -- chunks will not be unmounted therefrom ! */ void shm_release_all (process_t * proc) { spin_lock(bsl); node_t * node; while ((node = list_pop(proc->shm_mappings)) != NULL) { shm_mapping_t * mapping = node->value; release_chunk(mapping->chunk); free(mapping); free(node); } /* Empty, but don't free, the mappings list */ list_free(proc->shm_mappings); proc->shm_mappings->head = proc->shm_mappings->tail = NULL; proc->shm_mappings->length = 0; spin_unlock(bsl); } ================================================ FILE: kernel/sys/signal.c ================================================ /** * @file kernel/sys/signal.c * @brief Signal handling. * * Provides signal entry and delivery; also handles suspending * and resuming jobs (SIGTSTP, SIGCONT). * * As of Misaka 2.1, signal delivery has been largely rewritten: * - Signals can only be delivered a times when we would be * normally returning to userspace. This matches behavior in * a number of other kernels. * - Signals should cause kernel sleeps to return with an error * state, ending any blocking system calls and allowing them * to either gracefully return or bubble up -ERESTARTSYS to * be restarted. * - Userspace signal handlers now push context on the userspace * stack. This is arch-specific behavior. * - Signal handler returns work the same as previously, injecting * a special "magic" return address that should fault. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2022 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include static spin_lock_t sig_lock; #define SIG_DISP_Ign 0 #define SIG_DISP_Term 1 #define SIG_DISP_Core 2 #define SIG_DISP_Stop 3 #define SIG_DISP_Cont 4 static char sig_defaults[] = { 0, /* 0? */ [SIGHUP ] = SIG_DISP_Term, [SIGINT ] = SIG_DISP_Term, [SIGQUIT ] = SIG_DISP_Core, [SIGILL ] = SIG_DISP_Core, [SIGTRAP ] = SIG_DISP_Core, [SIGABRT ] = SIG_DISP_Core, [SIGEMT ] = SIG_DISP_Core, [SIGFPE ] = SIG_DISP_Core, [SIGKILL ] = SIG_DISP_Term, [SIGBUS ] = SIG_DISP_Core, [SIGSEGV ] = SIG_DISP_Core, [SIGSYS ] = SIG_DISP_Core, [SIGPIPE ] = SIG_DISP_Term, [SIGALRM ] = SIG_DISP_Term, [SIGTERM ] = SIG_DISP_Term, [SIGUSR1 ] = SIG_DISP_Term, [SIGUSR2 ] = SIG_DISP_Term, [SIGCHLD ] = SIG_DISP_Ign, [SIGPWR ] = SIG_DISP_Ign, [SIGWINCH ] = SIG_DISP_Ign, [SIGURG ] = SIG_DISP_Ign, [SIGPOLL ] = SIG_DISP_Ign, [SIGSTOP ] = SIG_DISP_Stop, [SIGTSTP ] = SIG_DISP_Stop, [SIGCONT ] = SIG_DISP_Cont, [SIGTTIN ] = SIG_DISP_Stop, [SIGTTOUT ] = SIG_DISP_Stop, [SIGTTOU ] = SIG_DISP_Stop, [SIGVTALRM ] = SIG_DISP_Term, [SIGPROF ] = SIG_DISP_Term, [SIGXCPU ] = SIG_DISP_Core, [SIGXFSZ ] = SIG_DISP_Core, [SIGWAITING ] = SIG_DISP_Ign, [SIGDIAF ] = SIG_DISP_Term, [SIGHATE ] = SIG_DISP_Ign, [SIGWINEVENT] = SIG_DISP_Ign, [SIGCAT ] = SIG_DISP_Ign, }; #define shift_signal(signum) (1ULL << signum) /** * @brief If a system call returned -ERESTARTSYS, restart it. * * Called by both @c handle_signal and @c return_from_signal_handler depending * on how the signal was handled. * * @param r Registers after restoration from signal return. */ static void maybe_restart_system_call(struct regs * r, int signum, int no_handler) { if (signum < 0 || signum >= NUMSIGNALS) return; if (this_core->current_process->interrupted_system_call && arch_syscall_number(r) == -ERESTARTSYS) { if (sig_defaults[signum] == SIG_DISP_Cont || (this_core->current_process->signals[signum].flags & SA_RESTART)) { arch_syscall_return(r, this_core->current_process->interrupted_system_call); this_core->current_process->interrupted_system_call = 0; syscall_handler(r); } else { this_core->current_process->interrupted_system_call = 0; arch_syscall_return(r, -EINTR); } } else if (this_core->current_process->interrupted_system_call && arch_syscall_number(r) == -ERESTARTSIGSUSPEND) { arch_syscall_return(r, this_core->current_process->interrupted_system_call); this_core->current_process->interrupted_system_call = 0; if (no_handler) { syscall_handler(r); } else { arch_syscall_return(r, -EINTR); } if (this_core->current_process->flags & PROC_FLAG_RESTORE_SIGMASK) { __sync_and_and_fetch(&this_core->current_process->flags, ~(PROC_FLAG_RESTORE_SIGMASK)); this_core->current_process->blocked_signals = this_core->current_process->restored_signals; } } } #define PENDING (this_core->current_process->pending_signals & ((~this_core->current_process->blocked_signals) | shift_signal(SIGSTOP) | shift_signal(SIGKILL))) /** * @brief Examine the pending signal and perform an appropriate action. * * This is called by @c process_check_signals below. It should not be called * directly by other parts of the kernel. Previously, it was called through * process switching... * * When a signal handler is called, this does not return. The userspace * process is resumed in the signal handler context, and any future calls * into the kernel are "from scratch". * * @param proc should be the current active process, which should generally * always be this_core->current_process. * @param sig is the signal node from the pending queue. Currently, this * just contains the signal number and nothing else. It used to * also contain the handler to call, but that led to TOCTOU bugs. * @param r Userspace registers at time of signal entry. This gets passed * forward to @c arch_enter_signal_handler * @returns 0 if another signal needs to be handled, 1 otherwise. */ int handle_signal(process_t * proc, int signum, struct regs *r) { struct signal_config config = proc->signals[signum]; /* Are we being traced? */ if (this_core->current_process->flags & PROC_FLAG_TRACE_SIGNALS) { signum = ptrace_signal(signum, 0); } if (proc->flags & PROC_FLAG_FINISHED) { return 1; } if (signum == 0 || signum >= NUMSIGNALS) { goto _ignore_signal; } if (!config.handler) { char dowhat = sig_defaults[signum]; if (dowhat == SIG_DISP_Term || dowhat == SIG_DISP_Core) { task_exit(((128 + signum) << 8) | signum); __builtin_unreachable(); } else if (dowhat == SIG_DISP_Stop) { __sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_SUSPENDED); this_core->current_process->status = 0x7F | (signum << 8) | 0xFF0000; process_t * parent = process_get_parent((process_t *)this_core->current_process); if (parent && !(parent->flags & PROC_FLAG_FINISHED)) { wakeup_queue(parent->wait_queue); } do { switch_task(0); } while (!PENDING); return 0; /* Return and handle another */ } else if (dowhat == SIG_DISP_Cont) { /* Continue doesn't actually do anything different at this stage. */ goto _ignore_signal; } goto _ignore_signal; } /* If the handler value is 1 we treat it as IGN. */ if (config.handler == 1) goto _ignore_signal; if (config.flags & SA_RESETHAND) { proc->signals[signum].handler = 0; } arch_enter_signal_handler(config.handler, signum, r); return 1; /* Should not be reachable */ _ignore_signal: /* we still need to check if we need to restart something */ maybe_restart_system_call(r, signum, 1); return !PENDING; } /** * @brief Deliver a signal to another process. * * Called by both system calls like @c kill as well as by some things * that want to trigger SIGSEGV, SIGPIPE, and so on. * * @param process PID to deliver to. Must be a single PID, not a group specifier. * @param signal Signal number to deliver. * @param force_root If the current process isn't root, it can't send signals to * processes owned by other users, which means we can't send soft * signals as part operations like SIGPIPE or SIGCHLD. Kernel callers * can use this parameter to skip this check. * @returns General status, should be suitable for sys_kill return value. */ int send_signal(pid_t process, int signal, int force_root) { process_t * receiver = process_from_pid(process); if (!receiver) return -ESRCH; if (!force_root && receiver->user != this_core->current_process->user && this_core->current_process->user != USER_ROOT_UID && !(signal == SIGCONT && receiver->session == this_core->current_process->session)) return -EPERM; if (receiver->flags & PROC_FLAG_IS_TASKLET) return -EPERM; if (signal >= NUMSIGNALS || signal < 0) return -EINVAL; if (receiver->flags & PROC_FLAG_FINISHED) return -ESRCH; if (signal == 0) return 0; int awaited = receiver->awaited_signals & shift_signal(signal); int ignored = !receiver->signals[signal].handler && !sig_defaults[signal]; int blocked = (receiver->blocked_signals & shift_signal(signal)) && signal != SIGKILL && signal != SIGSTOP; /* sigcont always unsuspends */ if (sig_defaults[signal] == SIG_DISP_Cont && (receiver->flags & PROC_FLAG_SUSPENDED)) { __sync_and_and_fetch(&receiver->flags, ~(PROC_FLAG_SUSPENDED)); receiver->status = 0; } /* Do nothing if the signal is not being waited for or blocked and the default disposition is to ignore. */ if (!awaited && !blocked && ignored) return 0; /* Mark the signal for delivery. */ spin_lock(sig_lock); receiver->pending_signals |= shift_signal(signal); spin_unlock(sig_lock); /* If the signal is blocked and not being awaited, end here. */ if (blocked && !awaited) return 0; /* Informs any blocking events that the process has been interrupted * by a signal, which should trigger those blocking events to complete * and potentially return -EINTR or -ERESTARTSYS */ process_awaken_signal(receiver); /* Schedule processes awoken by signals to be run. Unless they're us, we'll * jump to the signal handler as part of returning from this call. */ if (receiver != this_core->current_process && !process_is_ready(receiver)) { make_process_ready(receiver); } return 0; } /** * @brief Send a signal to multiple processes. * * Similar to @c send_signal but for when a negative PID needs to be used. * * @param group The group process ID. Positive PID, not negative. * @param signal Signal number to deliver. * @param force_root See explanation in @c send_signal * @returns 0 if something was killed, -ESRCH otherwise. */ int group_send_signal(pid_t group, int signal, int force_root) { int kill_self = 0; int killed_something = 0; if (signal >= NUMSIGNALS || signal < 0) return -EINVAL; foreach(node, process_list) { process_t * proc = node->value; if (proc->group == proc->id && proc->job == group) { /* Only thread group leaders */ if (proc->group == this_core->current_process->group) { kill_self = 1; } else { if (send_signal(proc->group, signal, force_root) == 0) { killed_something = 1; } } } } if (kill_self) { if (send_signal(this_core->current_process->group, signal, force_root) == 0) { killed_something = 1; } } return killed_something ? 0 : -ESRCH; } /** * @brief Examine the signal delivery queue of the current process, and handle signals. * * Should be called before a userspace return would happen. If a signal handler is to be * run in userspace, then process_check_signals will not return, similar to exec. * * @param r Userspace registers before signal entry. */ void process_check_signals(struct regs * r) { _tryagain: spin_lock(sig_lock); if (this_core->current_process && !(this_core->current_process->flags & PROC_FLAG_FINISHED)) { /* Set an pending signals that were previously blocked */ sigset_t active_signals = PENDING; int signal = 0; while (active_signals && signal < NUMSIGNALS) { if (active_signals & 1) { this_core->current_process->pending_signals &= ~shift_signal(signal); spin_unlock(sig_lock); if (handle_signal((process_t*)this_core->current_process, signal, r)) return; goto _tryagain; } active_signals >>= 1; signal++; } } spin_unlock(sig_lock); } /** * @brief Restore pre-signal context and possibly restart system calls. * * To be called by the platform's fault handler when it determines that * a signal handler return has been triggered. Calls platform code to restore * the previous userspace context (before the signal) from the userspace stack * and restarts an interrupted system call if there was one. * * @param r Registers at fault, passed to platform code for restoration and * then to @c maybe_restart_system_call to handle system call restarts. */ void return_from_signal_handler(struct regs *r) { int signum = arch_return_from_signal_handler(r); if (PENDING) { process_check_signals(r); } maybe_restart_system_call(r,signum,0); } /** * @brief Synchronously wait for specified signals to become pending. * * The signals in @c awaited are set as the current "awaited set". Delivery * of these signals will ignore the blocked and ignored states and always * result in the process be awoken with the signal marked pending if it is * sleeping. When the process awakens from @c switch_task the awaiting set * will be cleared. * * If no unblocked signal is pending and an awaited, blocked signal is pending, * its signal number will be placed in @p sig and it will be unmarked as * pending, returning 0. If a unblocked signal is received, @c -EINTR is * returned, and under normal circumstances the caller should raise this * return status up and allow normal signal handling to occur. * * Otherwise, if the process is reawoken by some other means and no unblocked * signals or awaited signals are pending, it will apply the awaited set and * sleep again. This will repeat until either of these conditions are met. * * If a signal specified in @p awaited is not currently blocked, but is pending * upon entering signal_await, it will be marked as not pending and the call * will return immediately; if an unblocked signal is not pending, it will not * be awaited: signal_await will return with -EINTR. * * @param awaited Signals to wait for, should all be blocked by caller. * @param sig Will be set to the awaited signal, if one arrives. * @returns 0 if an awaited signal arrives, -EINTR if another signal arrives. */ int signal_await(sigset_t awaited, int * sig) { do { sigset_t maybe = awaited & this_core->current_process->pending_signals; if (maybe) { int signal = 0; while (maybe && signal < NUMSIGNALS) { if (maybe & 1) { spin_lock(sig_lock); this_core->current_process->pending_signals &= ~shift_signal(signal); *sig = signal; spin_unlock(sig_lock); return 0; } maybe >>= 1; signal++; } } /* Set awaited signals */ this_core->current_process->awaited_signals = awaited; /* Sleep */ switch_task(0); /* Unset awaited signals. */ this_core->current_process->awaited_signals = 0; } while (!PENDING); return -EINTR; } /** * @brief Unschedule until a pending signal is delivered. */ void signal_wait(void) { while (!PENDING) { switch_task(0); } } ================================================ FILE: kernel/sys/syscall.c ================================================ /** * @file kernel/sys/syscall.c * @brief System call handlers. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char hostname[256]; static size_t hostname_len = 0; int ptr_validate(void * ptr, const char * syscall) { if (ptr) { if (!PTR_INRANGE(ptr)) { send_signal(this_core->current_process->id, SIGSEGV, 1); return 1; } if (!mmu_validate_user_pointer(ptr,1,0)) return 1; } return 0; } #define PTRCHECK(addr,size,flags) do { if (!mmu_validate_user_pointer(addr,size,flags)) return -EFAULT; } while (0) long sys_sbrk(ssize_t size) { if (size & 0xFFF) return -EINVAL; volatile process_t * volatile proc = this_core->current_process; if (proc->group != 0) { proc = process_from_pid(proc->group); } if (!proc) return -EINVAL; spin_lock(proc->image.lock); uintptr_t out = proc->image.heap; for (uintptr_t i = out; i < out + size; i += 0x1000) { union PML * page = mmu_get_page(i, MMU_GET_MAKE); if (page->bits.page != 0) { printf("odd, %#zx is already allocated?\n", i); } mmu_frame_allocate(page, MMU_FLAG_WRITABLE); } proc->image.heap += size; spin_unlock(proc->image.lock); return (long)out; } extern int elf_module(char ** args); long sys_sysfunc(long fn, char ** args) { /* FIXME: Most of these should be top-level, many are hacks/broken in Misaka */ switch (fn) { case TOARU_SYS_FUNC_SYNC: /* FIXME: There is no sync ability in the VFS at the moment. * XXX: Should this just be an ioctl on individual devices? * Or possibly even an ioctl we can send to arbitrary files? */ printf("sync: not implemented\n"); return -EINVAL; case TOARU_SYS_FUNC_LOGHERE: /* FIXME: The entire kernel logging system needs to be revamped as * Misaka switched everything to raw printfs, and then also * removed most of them for cleanliness... first task would * be to reintroduce kernel fprintf() to printf to fs_nodes. */ //if (this_core->current_process->user != 0) return -EACCES; printf("\033[32m%s\033[0m", (char*)args); return -EINVAL; case TOARU_SYS_FUNC_KDEBUG: /* FIXME: The kernel debugger is completely deprecated and fully removed * in Misaka, and I'm not sure I want to add it back... */ if (this_core->current_process->user != 0) return -EACCES; printf("kdebug: not implemented\n"); return -EINVAL; case TOARU_SYS_FUNC_CLEARICACHE: #ifdef __aarch64__ PTR_VALIDATE(&args[0]); PTR_VALIDATE(&args[1]); extern void arch_clear_icache(uintptr_t,uintptr_t); arch_clear_icache((uintptr_t)args[0], (uintptr_t)args[1]); #endif return 0; case TOARU_SYS_FUNC_MUNMAP: { extern void mmu_unmap_user(uintptr_t addr, size_t size); PTR_VALIDATE(&args[0]); PTR_VALIDATE(&args[1]); volatile process_t * volatile proc = this_core->current_process; if (proc->group != 0) proc = process_from_pid(proc->group); if (!proc) return -EFAULT; spin_lock(proc->image.lock); mmu_unmap_user((uintptr_t)args[0], (size_t)args[1]); spin_unlock(proc->image.lock); return 0; } case TOARU_SYS_FUNC_INSMOD: /* Linux has init_module as a system call? */ if (this_core->current_process->user != 0) return -EACCES; PTR_VALIDATE(args); if (!args) return -EFAULT; PTR_VALIDATE(args[0]); if (!args[0]) return -EFAULT; for (char ** aa = args; *aa; ++aa) { PTR_VALIDATE(*aa); } return elf_module(args); case TOARU_SYS_FUNC_SETHEAP: { /* I'm not really sure how this should be done... * traditional brk() would be expected to map everything in-between, * but we use this to move the heap in ld.so, and we don't want * the stuff in the middle to be mapped necessarily... */ PTR_VALIDATE(args); if (!args) return -EFAULT; if (!PTR_INRANGE(args[0])) return -EFAULT; if (!args[0]) return -EFAULT; volatile process_t * volatile proc = this_core->current_process; if (proc->group != 0) proc = process_from_pid(proc->group); if (!proc) return -EFAULT; spin_lock(proc->image.lock); proc->image.heap = (uintptr_t)args[0]; spin_unlock(proc->image.lock); return 0; } case TOARU_SYS_FUNC_MMAP: { /* FIXME: This whole thing should be removed; we need a proper mmap interface, * preferrably with all of the file mapping options, too. And it should * probably also interact with the SHM subsystem... */ PTR_VALIDATE(args); if (!args) return -EFAULT; volatile process_t * volatile proc = this_core->current_process; if (proc->group != 0) proc = process_from_pid(proc->group); if (!proc) return -EFAULT; spin_lock(proc->image.lock); /* Align inputs */ uintptr_t start = ((uintptr_t)args[0]) & 0xFFFFffffFFFFf000UL; uintptr_t end = ((uintptr_t)args[0] + (size_t)args[1] + 0xFFF) & 0xFFFFffffFFFFf000UL; if (!PTR_INRANGE(start)) return -EFAULT; if (!PTR_INRANGE(end)) return -EFAULT; for (uintptr_t i = start; i < end; i += 0x1000) { union PML * page = mmu_get_page(i, MMU_GET_MAKE); mmu_frame_allocate(page, MMU_FLAG_WRITABLE); } spin_unlock(proc->image.lock); return 0; } case TOARU_SYS_FUNC_THREADNAME: { /* This should probably be moved to a new system call. */ int count = 0; char **arg = args; PTR_VALIDATE(args); if (!args) return -EFAULT; while (*arg) { PTR_VALIDATE(*arg); count++; arg++; } this_core->current_process->cmdline = malloc(sizeof(char*)*(count+1)); int i = 0; while (i < count) { this_core->current_process->cmdline[i] = strdup(args[i]); i++; } this_core->current_process->cmdline[i] = NULL; return 0; } case TOARU_SYS_FUNC_SETGSBASE: /* This should be a new system call; see what Linux, et al., call it. */ PTR_VALIDATE(args); if (!args) return -EFAULT; PTR_VALIDATE(args[0]); this_core->current_process->thread.context.tls_base = (uintptr_t)args[0]; arch_set_tls_base(this_core->current_process->thread.context.tls_base); return 0; case TOARU_SYS_FUNC_NPROC: return processor_count; default: printf("Bad system function: %ld\n", fn); return -EINVAL; } return -EINVAL; } __attribute__((noreturn)) long sys_exit(long exitcode) { task_exit((exitcode & 0xFF) << 8); __builtin_unreachable(); } long sys_write(int fd, char * ptr, unsigned long len) { #if 0 /* Enable this to force stderr output to always be printed by the kernel. */ if (fd == 2) { printf_output(len,ptr); } #endif if (FD_CHECK(fd)) { PTRCHECK(ptr,len,MMU_PTR_NULL); fs_node_t * node = FD_ENTRY(fd); if (!(FD_MODE(fd) & 2)) return -EACCES; if (len && !ptr) { return -EFAULT; } int64_t out = write_fs(node, FD_OFFSET(fd), len, (uint8_t*)ptr); if (out > 0) { FD_OFFSET(fd) += out; } return out; } return -EBADF; } long sys_pwrite(int fd, void * ptr, size_t count, off_t offset) { if (FD_CHECK(fd)) { if ((FD_ENTRY(fd)->flags & FS_PIPE) || (FD_ENTRY(fd)->flags & FS_CHARDEVICE) || (FD_ENTRY(fd)->flags & FS_SOCKET)) return -ESPIPE; PTRCHECK(ptr,count,MMU_PTR_NULL); fs_node_t * node = FD_ENTRY(fd); if (!(FD_MODE(fd) & 2)) return -EACCES; if (count && !ptr) return -EFAULT; return write_fs(node, offset, count, (uint8_t*)ptr); } return -EBADF; } long sys_pread(int fd, void * ptr, size_t count, off_t offset) { if (FD_CHECK(fd)) { if ((FD_ENTRY(fd)->flags & FS_PIPE) || (FD_ENTRY(fd)->flags & FS_CHARDEVICE) || (FD_ENTRY(fd)->flags & FS_SOCKET)) return -ESPIPE; PTRCHECK(ptr,count,MMU_PTR_NULL|MMU_PTR_WRITE); fs_node_t * node = FD_ENTRY(fd); if (!(FD_MODE(fd) & 01)) return -EACCES; if (count && !ptr) return -EFAULT; return read_fs(node, offset, count, (uint8_t *)ptr); } return -EBADF; } static long stat_node(fs_node_t * fn, uintptr_t st) { struct stat * f = (struct stat *)st; if (!fn) { /* XXX: Does this need to zero the stat struct when returning -ENOENT? */ memset(f, 0x00, sizeof(struct stat)); return -ENOENT; } f->st_dev = (uint16_t)(((uint64_t)fn->device & 0xFFFF0) >> 8); f->st_ino = fn->inode; uint32_t flags = 0; if (fn->flags & FS_FILE) { flags |= _IFREG; } if (fn->flags & FS_DIRECTORY) { flags |= _IFDIR; } if (fn->flags & FS_CHARDEVICE) { flags |= _IFCHR; } if (fn->flags & FS_BLOCKDEVICE) { flags |= _IFBLK; } if (fn->flags & FS_PIPE) { flags |= _IFIFO; } if (fn->flags & FS_SYMLINK) { flags |= _IFLNK; } if (fn->flags & FS_SOCKET) { flags |= _IFSOCK; } f->st_mode = fn->mask | flags; f->st_nlink = fn->nlink; f->st_uid = fn->uid; f->st_gid = fn->gid; f->st_rdev = 0; f->st_size = fn->length; f->st_atim.tv_sec = fn->atime; f->st_atim.tv_nsec = 0; f->st_mtim.tv_sec = fn->mtime; f->st_mtim.tv_nsec = 0; f->st_ctim.tv_sec = fn->ctime; f->st_ctim.tv_nsec = 0; f->st_blksize = 512; /* whatever */ if (fn->get_size) { f->st_size = fn->get_size(fn); } return 0; } long sys_stat(int fd, uintptr_t st) { PTR_VALIDATE(st); if (!st) return -EFAULT; if (FD_CHECK(fd)) { return stat_node(FD_ENTRY(fd), st); } return -EBADF; } long sys_statf(char * file, uintptr_t st) { int result; PTR_VALIDATE(file); PTR_VALIDATE(st); if (!file || !st) return -EFAULT; fs_node_t * fn = kopen(file, 0); result = stat_node(fn, st); if (fn) { close_fs(fn); } return result; } long sys_symlink(char * target, char * name) { PTR_VALIDATE(target); PTR_VALIDATE(name); if (!target || !name) return -EFAULT; return symlink_fs(target, name); } long sys_readlink(const char * file, char * ptr, long len) { PTR_VALIDATE(file); PTRCHECK(ptr,len,0); if (!file) return -EFAULT; fs_node_t * node = kopen((char *) file, O_PATH | O_NOFOLLOW); if (!node) { return -ENOENT; } long rv = readlink_fs(node, ptr, len); close_fs(node); return rv; } long sys_lstat(char * file, uintptr_t st) { PTR_VALIDATE(file); PTR_VALIDATE(st); if (!file || !st) return -EFAULT; fs_node_t * fn = kopen(file, O_PATH | O_NOFOLLOW); long result = stat_node(fn, st); if (fn) { close_fs(fn); } return result; } long sys_open(const char * file, long flags, long mode) { PTR_VALIDATE(file); if (!file) return -EFAULT; fs_node_t * node = kopen((char *)file, flags); int access_bits = 0; if (node && (flags & O_CREAT) && (flags & O_EXCL)) { close_fs(node); return -EEXIST; } if (!node && (flags & O_CREAT)) { int result = create_file_fs((char *)file, mode); /* * This still potentially has an issue, particularly with * O_EXCL, where another process can replace this file, * or otherwise create a different file, and then the * file we created above is not the one we are opening. * * In the new VFS, creating a file should return an * inode/dirent representing the new file, so we don't * have to go and immediately try to open it. */ if (!result) { node = kopen((char *)file, flags); } else { return result; } } if (!(flags & O_WRONLY) || (flags & O_RDWR)) { if (node && !has_permission(node, 04)) { close_fs(node); return -EACCES; } else { access_bits |= 01; } } if ((flags & O_RDWR) || (flags & O_WRONLY)) { if (node && !has_permission(node, 02)) { close_fs(node); return -EACCES; } if (node && (node->flags & FS_DIRECTORY)) { return -EISDIR; } if ((flags & O_RDWR) || (flags & O_WRONLY)) { /* truncate doesn't grant write permissions */ access_bits |= 02; } } if (node && (flags & O_DIRECTORY)) { if (!(node->flags & FS_DIRECTORY)) { return -ENOTDIR; } } if (node && (flags & O_TRUNC)) { if (!(access_bits & 02)) { close_fs(node); return -EINVAL; } truncate_fs(node, 0); } if (!node) { return -ENOENT; } if (node && (flags & O_CREAT) && (node->flags & FS_DIRECTORY)) { close_fs(node); return -EISDIR; } int fd = process_append_fd((process_t *)this_core->current_process, node); FD_MODE(fd) = access_bits; if (flags & O_APPEND) { FD_OFFSET(fd) = node->length; } else { FD_OFFSET(fd) = 0; } return fd; } long sys_close(int fd) { if (FD_CHECK(fd)) { close_fs(FD_ENTRY(fd)); FD_ENTRY(fd) = NULL; return 0; } return -EBADF; } long sys_seek(int fd, long offset, long whence) { if (FD_CHECK(fd)) { if ((FD_ENTRY(fd)->flags & FS_PIPE) || (FD_ENTRY(fd)->flags & FS_CHARDEVICE) || (FD_ENTRY(fd)->flags & FS_SOCKET)) return -ESPIPE; switch (whence) { case 0: FD_OFFSET(fd) = offset; break; case 1: FD_OFFSET(fd) += offset; break; case 2: FD_OFFSET(fd) = FD_ENTRY(fd)->length + offset; break; default: return -EINVAL; } return FD_OFFSET(fd); } return -EBADF; } long sys_read(int fd, char * ptr, unsigned long len) { if (FD_CHECK(fd)) { PTRCHECK(ptr,len,MMU_PTR_NULL|MMU_PTR_WRITE); if (len && !ptr) { return -EFAULT; } fs_node_t * node = FD_ENTRY(fd); if (!(FD_MODE(fd) & 01)) { return -EACCES; } uint64_t out = read_fs(node, FD_OFFSET(fd), len, (uint8_t *)ptr); FD_OFFSET(fd) += out; return out; } return -EBADF; } long sys_ioctl(int fd, unsigned long request, void * argp) { if (FD_CHECK(fd)) { PTR_VALIDATE(argp); return ioctl_fs(FD_ENTRY(fd), request, argp); } return -EBADF; } long sys_readdir(int fd, long index, struct dirent * entry) { if (FD_CHECK(fd)) { PTR_VALIDATE(entry); if (!entry) return -EFAULT; struct dirent * kentry = readdir_fs(FD_ENTRY(fd), (uint64_t)index); if (kentry) { memcpy(entry, kentry, sizeof *entry); free(kentry); return 1; } else { return 0; } } return -EBADF; } long sys_mkdir(char * path, uint64_t mode) { PTR_VALIDATE(path); if (!path) return -EFAULT; return mkdir_fs(path, mode); } long sys_access(const char * file, long flags) { PTR_VALIDATE(file); if (!file) return -EFAULT; fs_node_t * node = kopen((char *)file, 0); if (!node) return -ENOENT; close_fs(node); return 0; } long sys_chmod(char * file, long mode) { PTR_VALIDATE(file); if (!file) return -EFAULT; fs_node_t * fn = kopen(file, 0); if (fn) { /* Can group members change bits? I think it's only owners. */ if (this_core->current_process->user != 0 && this_core->current_process->user != fn->uid) { close_fs(fn); return -EACCES; } long result = chmod_fs(fn, mode); close_fs(fn); return result; } else { return -ENOENT; } } long sys_fchmod(int fd, long mode) { if (!FD_CHECK(fd)) return -EBADF; if (this_core->current_process->user != 0 && this_core->current_process->user != FD_ENTRY(fd)->uid) return -EACCES; return chmod_fs(FD_ENTRY(fd), mode); } long sys_rename(const char * src, const char * dest) { PTR_VALIDATE(src); if (!src) return -EFAULT; PTR_VALIDATE(dest); if (!dest) return -EFAULT; extern int rename_file_fs(const char * src, const char * dest); return rename_file_fs(src, dest); } static int current_group_matches(gid_t gid) { if (gid == this_core->current_process->user_group) return 1; for (int i = 0; i < this_core->current_process->supplementary_group_count; ++i) { if (gid == this_core->current_process->supplementary_group_list[i]) return 1; } return 0; } long sys_chown(char * file, uid_t uid, uid_t gid) { PTR_VALIDATE(file); if (!file) return -EFAULT; fs_node_t * fn = kopen(file, 0); if (fn) { /* Only a privileged user can change the owner of a file. */ if (this_core->current_process->user != USER_ROOT_UID && uid != -1) { goto _access; } if (this_core->current_process->user != USER_ROOT_UID && gid != -1) { /* The owner of a file... */ if (this_core->current_process->user != fn->uid) { goto _access; } /* May change the group of the file to one that the owner is a member of... */ if (!current_group_matches(gid)) { goto _access; } } if ((uid != -1 || gid != -1) && (fn->mask & 0x800)) { /* Whenever the owner or group of a setuid executable is changed, it * loses the setuid bit. */ chmod_fs(fn, fn->mask & (~0x800)); } long result = chown_fs(fn, uid, gid); close_fs(fn); return result; } else { return -ENOENT; } _access: close_fs(fn); return -EACCES; } long sys_fchown(int fd, uid_t uid, uid_t gid) { if (!FD_CHECK(fd)) return -EBADF; if (this_core->current_process->user != USER_ROOT_UID && uid != -1) return -EACCES; if (this_core->current_process->user != USER_ROOT_UID && gid != -1) { if (this_core->current_process->user != FD_ENTRY(fd)->uid) return -EACCES; if (!current_group_matches(gid)) return -EACCES; } if ((uid != -1 || gid != -1) && (FD_ENTRY(fd)->mask & 0x800)) { /* Whenever the owner or group of a setuid executable is changed, it * loses the setuid bit. */ chmod_fs(FD_ENTRY(fd), FD_ENTRY(fd)->mask & (~0x800)); } return chown_fs(FD_ENTRY(fd), uid, gid); } long sys_truncate(char * file, off_t size) { PTR_VALIDATE(file); if (!file) return -EFAULT; if (size < 0) return -EINVAL; fs_node_t * fn = kopen(file, 0); if (!fn) return -ENOENT; /* Need write permission */ if (!has_permission(fn, 04)) { close_fs(fn); return -EACCES; } if (!fn->truncate) { close_fs(fn); return -ENOTSUP; } long out = fn->truncate(fn, size); close_fs(fn); return out; } long sys_ftruncate(int fd, off_t size) { if (!FD_CHECK(fd)) return -EBADF; if (!(FD_MODE(fd) & 2)) return -EACCES; if (size < 0) return -EINVAL; if (!FD_ENTRY(fd)->truncate) return -ENOTSUP; return FD_ENTRY(fd)->truncate(FD_ENTRY(fd), size); } long sys_gettimeofday(struct timeval * tv, void * tz) { PTR_VALIDATE(tv); PTR_VALIDATE(tz); if (!tv) return -EFAULT; return gettimeofday(tv, tz); } long sys_settimeofday(struct timeval * tv, void * tz) { extern int settimeofday(struct timeval * t, void *z); if (this_core->current_process->user != USER_ROOT_UID) return -EPERM; PTR_VALIDATE(tv); PTR_VALIDATE(tz); return settimeofday(tv,tz); } long sys_getuid(void) { return (long)this_core->current_process->real_user; } long sys_geteuid(void) { return (long)this_core->current_process->user; } long sys_setuid(uid_t new_uid) { if (this_core->current_process->user == USER_ROOT_UID) { this_core->current_process->user = new_uid; this_core->current_process->real_user = new_uid; return 0; } return -EPERM; } long sys_getgid(void) { return (long)this_core->current_process->real_user_group; } long sys_getegid(void) { return (long)this_core->current_process->user_group; } long sys_setgid(gid_t new_gid) { if (this_core->current_process->user == USER_ROOT_UID) { this_core->current_process->user_group = new_gid; this_core->current_process->real_user_group = new_gid; return 0; } return -EPERM; } long sys_getgroups(int size, gid_t list[]) { if (size == 0) { return this_core->current_process->supplementary_group_count; } else if (size < this_core->current_process->supplementary_group_count) { return -EINVAL; } else { PTR_VALIDATE(list); if (!list) return -EFAULT; for (int i = 0; i < this_core->current_process->supplementary_group_count; ++i) { PTR_VALIDATE(list + i); list[i] = this_core->current_process->supplementary_group_list[i]; } return this_core->current_process->supplementary_group_count; } } long sys_setgroups(int size, const gid_t list[]) { if (this_core->current_process->user != USER_ROOT_UID) return -EPERM; if (size < 0) return -EINVAL; if (size > 32) return -EINVAL; /* Arbitrary decision */ /* Free the current set. */ if (this_core->current_process->supplementary_group_count) { free(this_core->current_process->supplementary_group_list); this_core->current_process->supplementary_group_list = NULL; } this_core->current_process->supplementary_group_count = size; if (size == 0) return 0; this_core->current_process->supplementary_group_list = malloc(sizeof(gid_t) * size); PTR_VALIDATE(list); if (!list) return -EFAULT; for (int i = 0; i < size; ++i) { PTR_VALIDATE(list + i); this_core->current_process->supplementary_group_list[i] = list[i]; } return 0; } long sys_getpid(void) { /* The user actually wants the pid of the originating thread (which can be us). */ return this_core->current_process->group ? (long)this_core->current_process->group : (long)this_core->current_process->id; } long sys_gettid(void) { return (long)this_core->current_process->id; } long sys_setsid(void) { if (this_core->current_process->job == this_core->current_process->group) { return -EPERM; } this_core->current_process->session = this_core->current_process->group; this_core->current_process->job = this_core->current_process->group; return this_core->current_process->session; } long sys_setpgid(pid_t pid, pid_t pgid) { if (pgid < 0) { return -EINVAL; } process_t * proc = NULL; if (pid == 0) { proc = (process_t*)this_core->current_process; } else { proc = process_from_pid(pid); } if (!proc) { return -ESRCH; } if (proc->session != this_core->current_process->session || proc->session == proc->group) { return -EPERM; } if (pgid == 0) { proc->job = proc->group; } else { process_t * pgroup = process_from_pid(pgid); if (!pgroup || pgroup->session != proc->session) { return -EPERM; } proc->job = pgid; } return 0; } long sys_getpgid(pid_t pid) { process_t * proc; if (pid == 0) { proc = (process_t*)this_core->current_process; } else { proc = process_from_pid(pid); } if (!proc) { return -ESRCH; } return proc->job; } long sys_uname(struct utsname * name) { PTR_VALIDATE(name); if (!name) return -EFAULT; char version_number[256]; snprintf(version_number, 255, __kernel_version_format, __kernel_version_major, __kernel_version_minor, __kernel_version_lower, __kernel_version_suffix); char version_string[256]; snprintf(version_string, 255, "%s %s %s", __kernel_version_codename, __kernel_build_date, __kernel_build_time); strcpy(name->sysname, __kernel_name); strcpy(name->nodename, hostname); strcpy(name->release, version_number); strcpy(name->version, version_string); strcpy(name->machine, __kernel_arch); strcpy(name->domainname, ""); /* TODO */ return 0; } long sys_chdir(char * newdir) { PTR_VALIDATE(newdir); if (!newdir) return -EFAULT; char * path = canonicalize_path(this_core->current_process->wd_name, newdir); fs_node_t * chd = kopen(path, 0); if (chd) { if ((chd->flags & FS_DIRECTORY) == 0) { close_fs(chd); return -ENOTDIR; } if (!has_permission(chd, 01)) { close_fs(chd); return -EACCES; } close_fs(chd); free(this_core->current_process->wd_name); this_core->current_process->wd_name = malloc(strlen(path) + 1); memcpy(this_core->current_process->wd_name, path, strlen(path) + 1); return 0; } else { return -ENOENT; } } long sys_getcwd(char * buf, size_t size) { if (buf) { PTR_VALIDATE(buf); size_t len = strlen(this_core->current_process->wd_name) + 1; return (long)memcpy(buf, this_core->current_process->wd_name, size < len ? size : len); } return 0; } long sys_dup2(int old, int new) { return process_move_fd((process_t *)this_core->current_process, old, new); } long sys_fcntl(int fd, int cmd, long arg) { if (!FD_CHECK(fd)) return -EBADF; switch (cmd) { case F_GETFD: return 0; /* We don't support any flags. CLOEXEC is the only thing in here. */ case F_SETFD: return 0; /* We don't support any flags, so can't set any flags. */ case F_GETFL: { int mode = 0; if (FD_MODE(fd) & 03) mode = O_RDWR; else if (FD_MODE(fd) & 01) mode = O_RDONLY; else if (FD_MODE(fd) & 02) mode = O_WRONLY; /* TODO we don't persist O_APPEND and there are other flags we don't support */ return mode; } case F_SETFL: { return 0; /* TODO NONBLOCK, APPEND, SYNC... */ } case F_DUPFD: { if (arg < 0 || arg > 256) return -EINVAL; /* We expect a value of, like, 10 from dash. */ extern long process_fd_dup_least(process_t *, long, long); return process_fd_dup_least((process_t*)this_core->current_process, fd, arg); } case F_GETLK: case F_SETLK: case F_SETLKW: /* No lock support */ return -EINVAL; } return -EINVAL; } long sys_sethostname(char * new_hostname) { if (this_core->current_process->user == USER_ROOT_UID) { PTR_VALIDATE(new_hostname); if (!new_hostname) return -EFAULT; size_t len = strlen(new_hostname) + 1; if (len > 256) { return -ENAMETOOLONG; } hostname_len = len; memcpy(hostname, new_hostname, hostname_len); return 0; } else { return -EPERM; } } long sys_gethostname(char * buffer) { PTR_VALIDATE(buffer); if (!buffer) return -EFAULT; memcpy(buffer, hostname, hostname_len); return hostname_len; } long sys_mount(char * arg, char * mountpoint, char * type, unsigned long flags, void * data) { /* TODO: Make use of flags and data from mount command. */ (void)flags; (void)data; if (this_core->current_process->user != USER_ROOT_UID) { return -EPERM; } if (PTR_INRANGE(arg) && PTR_INRANGE(mountpoint) && PTR_INRANGE(type)) { return vfs_mount_type(type, arg, mountpoint); } return -EFAULT; } long sys_umask(long mode) { this_core->current_process->mask = mode & 0777; return 0; } long sys_unlink(char * file) { PTR_VALIDATE(file); if (!file) return -EFAULT; return unlink_fs(file); } long sys_execve(const char * filename, char *const argv[], char *const envp[]) { PTR_VALIDATE(filename); PTR_VALIDATE(argv); PTR_VALIDATE(envp); if (!filename || !argv) return -EFAULT; int argc = 0; int envc = 0; while (argv[argc]) { PTR_VALIDATE(argv[argc]); ++argc; } if (envp) { while (envp[envc]) { PTR_VALIDATE(envp[envc]); ++envc; } } char **argv_ = malloc(sizeof(char*) * (argc + 1)); for (int j = 0; j < argc; ++j) { argv_[j] = malloc(strlen(argv[j]) + 1); memcpy(argv_[j], argv[j], strlen(argv[j]) + 1); } argv_[argc] = 0; char ** envp_; if (envp && envc) { envp_ = malloc(sizeof(char*) * (envc + 1)); for (int j = 0; j < envc; ++j) { envp_[j] = malloc(strlen(envp[j]) + 1); memcpy(envp_[j], envp[j], strlen(envp[j]) + 1); } envp_[envc] = 0; } else { envp_ = malloc(sizeof(char*)); envp_[0] = NULL; } /** * FIXME: For legacy reasons, we're just going to close everything >2 for now, * but we should really implement proper CLOEXEC semantics... */ for (unsigned int i = 3; i < this_core->current_process->fds->length; ++i) { if (this_core->current_process->fds->entries[i]) { close_fs(this_core->current_process->fds->entries[i]); this_core->current_process->fds->entries[i] = NULL; } } shm_release_all((process_t *)this_core->current_process); this_core->current_process->cmdline = argv_; return exec(filename, argc, argv_, envp_, 0); } long sys_fork(void) { return fork(); } long sys_clone(uintptr_t new_stack, uintptr_t thread_func, uintptr_t arg) { if (!new_stack || !PTR_INRANGE(new_stack)) return -EINVAL; if (!thread_func || !PTR_INRANGE(thread_func)) return -EINVAL; return (int)clone(new_stack, thread_func, arg); } long sys_waitpid(int pid, int * status, int options) { if (status && !PTR_INRANGE(status)) return -EINVAL; return waitpid(pid, status, options); } long sys_yield(void) { switch_task(1); return 1; } long sys_sleepabs(unsigned long seconds, unsigned long subseconds) { /* Mark us as asleep until */ sleep_until((process_t *)this_core->current_process, seconds, subseconds); /* Switch without adding us to the queue */ //printf("process %p (pid=%d) entering sleep until %ld.%06ld\n", current_process, current_process->id, seconds, subseconds); switch_task(0); unsigned long timer_ticks = 0, timer_subticks = 0; relative_time(0,0,&timer_ticks,&timer_subticks); //printf("process %p (pid=%d) resumed from sleep at %ld.%06ld\n", current_process, current_process->id, timer_ticks, timer_subticks); if (seconds > timer_ticks || (seconds == timer_ticks && subseconds >= timer_subticks)) { return 1; } else { return 0; } } long sys_sleep(unsigned long seconds, unsigned long subseconds) { unsigned long s, ss; relative_time(seconds, subseconds * 10000, &s, &ss); return sys_sleepabs(s, ss); } long sys_pipe(int pipes[2]) { if (!pipes || !PTR_INRANGE(pipes)) { return -EFAULT; } fs_node_t * outpipes[2]; make_unix_pipe(outpipes); open_fs(outpipes[0], 0); open_fs(outpipes[1], 0); pipes[0] = process_append_fd((process_t *)this_core->current_process, outpipes[0]); pipes[1] = process_append_fd((process_t *)this_core->current_process, outpipes[1]); FD_MODE(pipes[0]) = 03; FD_MODE(pipes[1]) = 03; return 0; } long sys_signal(long signum, uintptr_t handler) { if (signum >= NUMSIGNALS || signum < 0) return -EINVAL; if (signum == SIGKILL || signum == SIGSTOP) return -EINVAL; uintptr_t old = this_core->current_process->signals[signum].handler; this_core->current_process->signals[signum].handler = handler; this_core->current_process->signals[signum].flags = SA_RESTART; return old; } long sys_sigaction(int signum, struct sigaction *act, struct sigaction *oldact) { if (act) PTRCHECK(act,sizeof(struct sigaction),0); if (oldact) PTRCHECK(oldact,sizeof(struct sigaction),MMU_PTR_WRITE); if (signum >= NUMSIGNALS || signum < 0) return -EINVAL; if (signum == SIGKILL || signum == SIGSTOP) return -EINVAL; if (oldact) { oldact->sa_handler = (_sig_func_ptr)this_core->current_process->signals[signum].handler; oldact->sa_mask = this_core->current_process->signals[signum].mask; oldact->sa_flags = this_core->current_process->signals[signum].flags; } if (act) { this_core->current_process->signals[signum].handler = (uintptr_t)act->sa_handler; this_core->current_process->signals[signum].mask = act->sa_mask; this_core->current_process->signals[signum].flags = act->sa_flags; } return 0; } long sys_sigpending(sigset_t * set) { PTRCHECK(set,sizeof(sigset_t),MMU_PTR_WRITE); *set = this_core->current_process->pending_signals; return 0; } long sys_sigprocmask(int how, sigset_t *restrict set, sigset_t * restrict oset) { if (oset) { PTRCHECK(oset,sizeof(sigset_t),MMU_PTR_WRITE); *oset = this_core->current_process->blocked_signals; } if (set) { PTRCHECK(set,sizeof(sigset_t),0); switch (how) { case SIG_SETMASK: this_core->current_process->blocked_signals = *set; break; case SIG_BLOCK: this_core->current_process->blocked_signals |= *set; break; case SIG_UNBLOCK: this_core->current_process->blocked_signals &= ~*set; break; default: return -EINVAL; } } return 0; } extern void signal_wait(void); long sys_sigsuspend(const sigset_t *set) { PTRCHECK(set,sizeof(sigset_t),0); this_core->current_process->restored_signals = this_core->current_process->blocked_signals; this_core->current_process->blocked_signals = *set; signal_wait(); __sync_or_and_fetch(&this_core->current_process->flags, PROC_FLAG_RESTORE_SIGMASK); return -ERESTARTSIGSUSPEND; } long sys_sigwait(sigset_t * set, int * sig) { PTRCHECK(set,sizeof(sigset_t),0); PTRCHECK(sig,sizeof(int),MMU_PTR_WRITE); /* Silently ignore attempts to wait on KILL or STOP */ sigset_t awaited = *set & ~((1 << SIGKILL) | (1 << SIGSTOP)); /* Don't let processes wait on unblocked signals */ if (awaited & ~this_core->current_process->blocked_signals) return -EINVAL; return signal_await(awaited, sig); } long sys_fswait(int c, int fds[]) { PTR_VALIDATE(fds); if (!fds || c < 0) return -EFAULT; for (int i = 0; i < c; ++i) { if (!FD_CHECK(fds[i])) return -EBADF; } fs_node_t ** nodes = malloc(sizeof(fs_node_t *)*(c+1)); for (int i = 0; i < c; ++i) { nodes[i] = FD_ENTRY(fds[i]); } nodes[c] = NULL; int result = process_wait_nodes((process_t *)this_core->current_process, nodes, -1); free(nodes); return result; } long sys_fswait_timeout(int c, int fds[], int timeout) { PTR_VALIDATE(fds); if (!fds || c < 0) return -EFAULT; for (int i = 0; i < c; ++i) { if (!FD_CHECK(fds[i])) return -EBADF; } fs_node_t ** nodes = malloc(sizeof(fs_node_t *)*(c+1)); for (int i = 0; i < c; ++i) { nodes[i] = FD_ENTRY(fds[i]); } nodes[c] = NULL; int result = process_wait_nodes((process_t *)this_core->current_process, nodes, timeout); free(nodes); return result; } long sys_fswait_multi(int c, int fds[], int timeout, int out[]) { PTR_VALIDATE(fds); PTR_VALIDATE(out); if (!fds || !out || c < 0) return -EFAULT; int has_match = -1; for (int i = 0; i < c; ++i) { if (!FD_CHECK(fds[i])) { return -EBADF; } if (selectcheck_fs(FD_ENTRY(fds[i])) == 0) { out[i] = 1; has_match = (has_match == -1) ? i : has_match; } else { out[i] = 0; } } /* Already found a match, return immediately with the first match */ if (has_match != -1) return has_match; int result = sys_fswait_timeout(c, fds, timeout); if (result >= 0 && result < c) out[result] = 1; return result; } long sys_shm_obtain(char * path, size_t * size) { PTR_VALIDATE(path); PTR_VALIDATE(size); if (!path || !size) return -EFAULT; return (long)shm_obtain(path, size); } long sys_shm_release(char * path) { PTR_VALIDATE(path); if (!path) return -EFAULT; return shm_release(path); } long sys_openpty(int * master, int * slave, char * name, void * _ign0, void * size) { /* We require a place to put these when we are done. */ if (!master || !slave) return -EINVAL; if (master && !PTR_INRANGE(master)) return -EINVAL; if (slave && !PTR_INRANGE(slave)) return -EINVAL; if (size && !PTR_INRANGE(size)) return -EINVAL; /* Create a new pseudo terminal */ fs_node_t * fs_master; fs_node_t * fs_slave; pty_create(size, &fs_master, &fs_slave); /* Append the master and slave to the calling process */ *master = process_append_fd((process_t *)this_core->current_process, fs_master); *slave = process_append_fd((process_t *)this_core->current_process, fs_slave); FD_MODE(*master) = 03; FD_MODE(*slave) = 03; open_fs(fs_master, 0); open_fs(fs_slave, 0); /* Return success */ return 0; } long sys_kill(pid_t process, int signal) { if (process < -1) { return group_send_signal(-process, signal, 0); } else if (process == 0) { return group_send_signal(this_core->current_process->job, signal, 0); } else { return send_signal(process, signal, 0); } } long sys_reboot(void) { if (this_core->current_process->user != USER_ROOT_UID) { return -EPERM; } return arch_reboot(); } long sys_times(struct tms *buf) { if (buf) { PTR_VALIDATE(buf); buf->tms_utime = (this_core->current_process->time_total - this_core->current_process->time_sys) / arch_cpu_mhz(); buf->tms_stime = this_core->current_process->time_sys / arch_cpu_mhz(); buf->tms_cutime = (this_core->current_process->time_children - this_core->current_process->time_sys_children) / arch_cpu_mhz(); buf->tms_cstime = this_core->current_process->time_sys_children / arch_cpu_mhz(); } return arch_perf_timer() / arch_cpu_mhz(); } extern long ptrace_handle(long,pid_t,void*,void*); typedef long (*scall_func)(long,long,long,long,long); static scall_func syscalls[] = { /* System Call Table */ [SYS_EXT] = (scall_func)(uintptr_t)sys_exit, [SYS_GETEUID] = (scall_func)(uintptr_t)sys_geteuid, [SYS_OPEN] = (scall_func)(uintptr_t)sys_open, [SYS_READ] = (scall_func)(uintptr_t)sys_read, [SYS_WRITE] = (scall_func)(uintptr_t)sys_write, [SYS_CLOSE] = (scall_func)(uintptr_t)sys_close, [SYS_GETTIMEOFDAY] = (scall_func)(uintptr_t)sys_gettimeofday, [SYS_GETPID] = (scall_func)(uintptr_t)sys_getpid, [SYS_SBRK] = (scall_func)(uintptr_t)sys_sbrk, [SYS_UNAME] = (scall_func)(uintptr_t)sys_uname, [SYS_SEEK] = (scall_func)(uintptr_t)sys_seek, [SYS_STAT] = (scall_func)(uintptr_t)sys_stat, [SYS_GETUID] = (scall_func)(uintptr_t)sys_getuid, [SYS_SETUID] = (scall_func)(uintptr_t)sys_setuid, [SYS_READDIR] = (scall_func)(uintptr_t)sys_readdir, [SYS_CHDIR] = (scall_func)(uintptr_t)sys_chdir, [SYS_GETCWD] = (scall_func)(uintptr_t)sys_getcwd, [SYS_SETHOSTNAME] = (scall_func)(uintptr_t)sys_sethostname, [SYS_GETHOSTNAME] = (scall_func)(uintptr_t)sys_gethostname, [SYS_MKDIR] = (scall_func)(uintptr_t)sys_mkdir, [SYS_GETTID] = (scall_func)(uintptr_t)sys_gettid, [SYS_SYSFUNC] = (scall_func)(uintptr_t)sys_sysfunc, [SYS_IOCTL] = (scall_func)(uintptr_t)sys_ioctl, [SYS_ACCESS] = (scall_func)(uintptr_t)sys_access, [SYS_STATF] = (scall_func)(uintptr_t)sys_statf, [SYS_CHMOD] = (scall_func)(uintptr_t)sys_chmod, [SYS_UMASK] = (scall_func)(uintptr_t)sys_umask, [SYS_UNLINK] = (scall_func)(uintptr_t)sys_unlink, [SYS_MOUNT] = (scall_func)(uintptr_t)sys_mount, [SYS_SYMLINK] = (scall_func)(uintptr_t)sys_symlink, [SYS_READLINK] = (scall_func)(uintptr_t)sys_readlink, [SYS_LSTAT] = (scall_func)(uintptr_t)sys_lstat, [SYS_CHOWN] = (scall_func)(uintptr_t)sys_chown, [SYS_SETSID] = (scall_func)(uintptr_t)sys_setsid, [SYS_SETPGID] = (scall_func)(uintptr_t)sys_setpgid, [SYS_GETPGID] = (scall_func)(uintptr_t)sys_getpgid, [SYS_DUP2] = (scall_func)(uintptr_t)sys_dup2, [SYS_EXECVE] = (scall_func)(uintptr_t)sys_execve, [SYS_FORK] = (scall_func)(uintptr_t)sys_fork, [SYS_WAITPID] = (scall_func)(uintptr_t)sys_waitpid, [SYS_YIELD] = (scall_func)(uintptr_t)sys_yield, [SYS_SLEEPABS] = (scall_func)(uintptr_t)sys_sleepabs, [SYS_SLEEP] = (scall_func)(uintptr_t)sys_sleep, [SYS_PIPE] = (scall_func)(uintptr_t)sys_pipe, [SYS_FSWAIT] = (scall_func)(uintptr_t)sys_fswait, [SYS_FSWAIT2] = (scall_func)(uintptr_t)sys_fswait_timeout, [SYS_FSWAIT3] = (scall_func)(uintptr_t)sys_fswait_multi, [SYS_CLONE] = (scall_func)(uintptr_t)sys_clone, [SYS_OPENPTY] = (scall_func)(uintptr_t)sys_openpty, [SYS_SHM_OBTAIN] = (scall_func)(uintptr_t)sys_shm_obtain, [SYS_SHM_RELEASE] = (scall_func)(uintptr_t)sys_shm_release, [SYS_SIGNAL] = (scall_func)(uintptr_t)sys_signal, [SYS_KILL] = (scall_func)(uintptr_t)sys_kill, [SYS_REBOOT] = (scall_func)(uintptr_t)sys_reboot, [SYS_GETGID] = (scall_func)(uintptr_t)sys_getgid, [SYS_GETEGID] = (scall_func)(uintptr_t)sys_getegid, [SYS_SETGID] = (scall_func)(uintptr_t)sys_setgid, [SYS_GETGROUPS] = (scall_func)(uintptr_t)sys_getgroups, [SYS_SETGROUPS] = (scall_func)(uintptr_t)sys_setgroups, [SYS_TIMES] = (scall_func)(uintptr_t)sys_times, [SYS_PTRACE] = (scall_func)(uintptr_t)ptrace_handle, [SYS_SETTIMEOFDAY] = (scall_func)(uintptr_t)sys_settimeofday, [SYS_SIGACTION] = (scall_func)(uintptr_t)sys_sigaction, [SYS_SIGPENDING] = (scall_func)(uintptr_t)sys_sigpending, [SYS_SIGPROCMASK] = (scall_func)(uintptr_t)sys_sigprocmask, [SYS_SIGSUSPEND] = (scall_func)(uintptr_t)sys_sigsuspend, [SYS_SIGWAIT] = (scall_func)(uintptr_t)sys_sigwait, [SYS_PREAD] = (scall_func)(uintptr_t)sys_pread, [SYS_PWRITE] = (scall_func)(uintptr_t)sys_pwrite, [SYS_RENAME] = (scall_func)(uintptr_t)sys_rename, [SYS_FCNTL] = (scall_func)(uintptr_t)sys_fcntl, [SYS_FCHMOD] = (scall_func)(uintptr_t)sys_fchmod, [SYS_FCHOWN] = (scall_func)(uintptr_t)sys_fchown, [SYS_TRUNCATE] = (scall_func)(uintptr_t)sys_truncate, [SYS_FTRUNCATE] = (scall_func)(uintptr_t)sys_ftruncate, [SYS_SOCKET] = (scall_func)(uintptr_t)net_socket, [SYS_SETSOCKOPT] = (scall_func)(uintptr_t)net_setsockopt, [SYS_BIND] = (scall_func)(uintptr_t)net_bind, [SYS_ACCEPT] = (scall_func)(uintptr_t)net_accept, [SYS_LISTEN] = (scall_func)(uintptr_t)net_listen, [SYS_CONNECT] = (scall_func)(uintptr_t)net_connect, [SYS_GETSOCKOPT] = (scall_func)(uintptr_t)net_getsockopt, [SYS_RECV] = (scall_func)(uintptr_t)net_recv, [SYS_SEND] = (scall_func)(uintptr_t)net_send, [SYS_SHUTDOWN] = (scall_func)(uintptr_t)net_shutdown, [SYS_GETSOCKNAME] = (scall_func)(uintptr_t)net_getsockname, [SYS_GETPEERNAME] = (scall_func)(uintptr_t)net_getpeername, }; static long num_syscalls = sizeof(syscalls) / sizeof(*syscalls); void syscall_handler(struct regs * r) { this_core->current_process->syscall_registers = r; if (this_core->current_process->flags & PROC_FLAG_TRACE_SYSCALLS) { ptrace_signal(SIGTRAP, PTRACE_EVENT_SYSCALL_ENTER); } long result; if (arch_syscall_number(r) < 0 || arch_syscall_number(r) >= num_syscalls) { result = -EINVAL; goto _finish_syscall; } scall_func func = syscalls[arch_syscall_number(r)]; result = func( arch_syscall_arg0(r), arch_syscall_arg1(r), arch_syscall_arg2(r), arch_syscall_arg3(r), arch_syscall_arg4(r)); if (result == -ERESTARTSYS || result == -ERESTARTSIGSUSPEND) { this_core->current_process->interrupted_system_call = arch_syscall_number(r); } _finish_syscall: arch_syscall_return(r, result); if (this_core->current_process->flags & PROC_FLAG_TRACE_SYSCALLS) { ptrace_signal(SIGTRAP, PTRACE_EVENT_SYSCALL_EXIT); } } ================================================ FILE: kernel/sys/version.c ================================================ /** * @file kernel/sys/version.c * @brief Kernel version macros. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2021 K. Lange */ #include #define STR(x) #x #define STRSTR(x) STR(x) /* Kernel name. If you change this, you're not * my friend any more. */ const char * __kernel_name = "Misaka"; /* This really shouldn't change, and if it does, * always ensure it still has the correct arguments * when used as a vsprintf() format. */ const char * __kernel_version_format = "%d.%d.%d-%s"; /* Version numbers X.Y.Z */ int __kernel_version_major = 2; int __kernel_version_minor = 3; int __kernel_version_lower = 0; /* Kernel build suffix, which doesn't necessarily * mean anything, but can be used to distinguish * between different features included while * building multiple kernels. */ #ifdef KERNEL_GIT_TAG # define KERNEL_VERSION_SUFFIX STRSTR(KERNEL_GIT_TAG) #else # define KERNEL_VERSION_SUFFIX "r" #endif const char * __kernel_version_suffix = KERNEL_VERSION_SUFFIX; /* The release codename. */ const char * __kernel_version_codename = "\"Grow Slowly\""; /* Build architecture */ const char * __kernel_arch = STRSTR(KERNEL_ARCH); /* Rebuild from clean to reset these. */ const char * __kernel_build_date = __DATE__; const char * __kernel_build_time = __TIME__; #if (defined(__GNUC__) || defined(__GNUG__)) && !(defined(__clang__) || defined(__INTEL_COMPILER)) # define COMPILER_VERSION "gcc " __VERSION__ #elif (defined(__clang__)) # define COMPILER_VERSION "clang " __clang_version__ #else # define COMPILER_VERSION "unknown-compiler how-did-you-do-that" #endif const char * __kernel_compiler_version = COMPILER_VERSION; ================================================ FILE: kernel/vfs/console.c ================================================ /** * @file kernel/vfs/console.c * @brief Device file interface to the kernel console. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include static fs_node_t * console_dev = NULL; /** Things we use to determine if the clock is ready. */ extern uint64_t arch_boot_time; /** Things we use for framebuffer output. */ extern uint16_t lfb_resolution_x; extern size_t (*printf_output)(size_t, uint8_t *); static size_t (*console_write)(size_t, uint8_t *) = NULL; static uint8_t tmp_buffer[4096] __attribute__((aligned(4096))); static uint8_t * buffer_start = tmp_buffer; static ssize_t write_console(size_t size, uint8_t *buffer) { if (console_write) return console_write(size,buffer); if (buffer_start + size >= tmp_buffer + sizeof(tmp_buffer)) { return 0; /* uh oh */ } memcpy(buffer_start, buffer, size); buffer_start += size; return size; } struct dprintf_data { int prev_was_lf; int left_width; }; static int cb_printf(void * user, char c) { struct dprintf_data * data = user; if (data->prev_was_lf) { for (int i = 0; i < data->left_width; ++i) write_console(1, (uint8_t*)" "); data->prev_was_lf = 0; } if (c == '\n') data->prev_was_lf = 1; write_console(1, (uint8_t*)&c); return 0; } void console_set_output(size_t (*output)(size_t,uint8_t*)) { console_write = output; if (buffer_start != tmp_buffer) { console_write(buffer_start - tmp_buffer, tmp_buffer); buffer_start = tmp_buffer; } } int dprintf(const char * fmt, ...) { va_list args; va_start(args, fmt); /* Is this a FATAL message? */ /* If it's ready now but wasn't ready previously, are there * things in the queue to dump? */ /* Is this a fresh message for this core that we need to assign a timestamp to? */ struct dprintf_data _data = {0,0}; if (*fmt == '\a') { fmt++; } else { char timestamp[32]; unsigned long timer_ticks, timer_subticks; relative_time(0,0,&timer_ticks,&timer_subticks); size_t ts_len = snprintf(timestamp, 31, "[%5lu.%06lu] ", timer_ticks, timer_subticks); _data.left_width = ts_len; write_console(ts_len, (uint8_t*)timestamp); } int out = xvasprintf(cb_printf, &_data, fmt, args); va_end(args); return out; } static ssize_t write_fs_console(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { if (size > 0x1000) return -EINVAL; size_t size_in = size; if (size && *buffer == '\r') { write_console(1,(uint8_t*)"\r"); buffer++; size--; } if (size) dprintf("%*s", (unsigned int)size, buffer); return size_in; } static fs_node_t * console_device_create(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "console"); fnode->uid = 0; fnode->gid = 0; fnode->mask = 0660; fnode->flags = FS_CHARDEVICE; fnode->write = write_fs_console; return fnode; } void console_initialize(void) { console_dev = console_device_create(); vfs_mount("/dev/console", console_dev, "console", ""); } ================================================ FILE: kernel/vfs/packetfs.c ================================================ /** * @file kernel/vfs/packetfs.c * @brief Packet-based multiple-client IPC mechanism. aka PEX * * Provides a server-client packet-based IPC socket system for * userspace applications. Primarily used by the compositor to * communicate with clients. * * Care must be taken to ensure that this is backed by an atomic * stream; the legacy pseudo-pipe interface is used at the moment. * * @bug We leak kernel heap addresses directly to userspace as the * client identifiers in PEX messages. We should probably do * something else. I'm also reasonably certain a server can * just throw a random address at the PEX API? * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include extern void pipe_destroy(fs_node_t * node); #include #define MAX_PACKET_SIZE 1024 #define debug_print(x, ...) do { if (0) {printf("packetfs.c [%s] ", #x); printf(__VA_ARGS__); printf("\n"); } } while (0) typedef struct packet_manager { /* uh, nothing, lol */ list_t * exchanges; spin_lock_t lock; } pex_t; typedef struct packet_exchange { char * name; char fresh; spin_lock_t lock; fs_node_t * server_pipe; list_t * clients; pex_t * parent; } pex_ex_t; typedef struct packet_client { pex_ex_t * parent; fs_node_t * pipe; } pex_client_t; typedef struct packet { pex_client_t * source; size_t size; uint8_t data[]; } packet_t; typedef struct server_write_header { pex_client_t * target; uint8_t data[]; } header_t; static ssize_t receive_packet(pex_ex_t * exchange, fs_node_t * socket, packet_t ** out) { ssize_t r; do { r = read_fs(socket, 0, sizeof(struct packet *), (uint8_t*)out); } while (r == 0); if (r < 0) return r; assert(r == sizeof(struct packet*)); assert((uintptr_t)*out >= 0xFFFFff0000000000UL && (uintptr_t)*out < 0xffffff1fc0000000UL); return r; } static void send_to_server(pex_ex_t * p, pex_client_t * c, size_t size, void * data) { size_t p_size = size + sizeof(struct packet); packet_t * packet = malloc(p_size); if ((uintptr_t)c < 0x800000000) { printf("suspicious pex client received: %p\n", (char*)c); } packet->source = c; packet->size = size; if (size) { memcpy(packet->data, data, size); } write_fs(p->server_pipe, 0, sizeof(struct packet*), (uint8_t*)&packet); } static int send_to_client(pex_ex_t * p, pex_client_t * c, size_t size, void * data) { size_t p_size = size + sizeof(struct packet); /* Verify there is space on the client */ if (pipe_unsize(c->pipe) < (int)sizeof(struct packet*)) { return -1; } if ((uintptr_t)c < 0x800000000) { printf("suspicious pex client received: %p\n", (char*)c); } packet_t * packet = malloc(p_size); memcpy(packet->data, data, size); packet->source = NULL; packet->size = size; write_fs(c->pipe, 0, sizeof(struct packet*), (uint8_t*)&packet); return size; } static pex_client_t * create_client(pex_ex_t * p) { pex_client_t * out = malloc(sizeof(pex_client_t)); out->parent = p; out->pipe = make_pipe(4096); return out; } static ssize_t read_server(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { pex_ex_t * p = (pex_ex_t *)node->device; debug_print(INFO, "[pex] server read(...)"); packet_t * packet = NULL; ssize_t response_size = receive_packet(p, p->server_pipe, &packet); if (response_size < 0) return response_size; if (!packet) return -1; debug_print(INFO, "Server recevied packet of size %zu, was waiting for at most %lu", packet->size, size); if (packet->size + sizeof(packet_t) > size) { printf("pex: read in server would be incomplete\n"); return -1; } memcpy(buffer, packet, packet->size + sizeof(packet_t)); ssize_t out = packet->size + sizeof(packet_t); free(packet); return out; } static ssize_t write_server(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { pex_ex_t * p = (pex_ex_t *)node->device; debug_print(INFO, "[pex] server write(...)"); header_t * head = (header_t *)buffer; if (size - sizeof(header_t) > MAX_PACKET_SIZE) { printf("pex: server write is too big\n"); return -1; } if (head->target == NULL) { /* Brodcast packet */ spin_lock(p->lock); foreach(f, p->clients) { debug_print(INFO, "Sending to client %p", f->value); send_to_client(p, (pex_client_t *)f->value, size - sizeof(header_t), head->data); } spin_unlock(p->lock); debug_print(INFO, "Done broadcasting to clients."); return size; } else if (head->target->parent != p) { debug_print(WARNING, "[pex] Invalid packet from server? (pid=%d)", this_core->current_process->id); return -1; } return send_to_client(p, head->target, size - sizeof(header_t), head->data) + sizeof(header_t); } static int ioctl_server(fs_node_t * node, unsigned long request, void * argp) { pex_ex_t * p = (pex_ex_t *)node->device; switch (request) { case IOCTL_PACKETFS_QUEUED: return pipe_size(p->server_pipe); default: return -1; } } static ssize_t read_client(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { pex_client_t * c = (pex_client_t *)node->inode; if (c->parent != node->device) { printf("pex: Invalid device endpoint on client read?\n"); return -EINVAL; } debug_print(INFO, "[pex] client read(...)"); packet_t * packet = NULL; ssize_t response_size = receive_packet(c->parent, c->pipe, &packet); if (response_size < 0) return response_size; if (!packet) return -EIO; if (packet->size > size) { printf("pex: Client is not reading enough bytes to hold packet of size %zu\n", packet->size); return -EINVAL; } memcpy(buffer, &packet->data, packet->size); ssize_t out = packet->size; debug_print(INFO, "[pex] Client received packet of size %zu", packet->size); if (out == 0) { printf("pex: packet is empty?\n"); } free(packet); return out; } static ssize_t write_client(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { pex_client_t * c = (pex_client_t *)node->inode; if (c->parent != node->device) { debug_print(WARNING, "[pex] Invalid device endpoint on client write?"); return -EINVAL; } debug_print(INFO, "[pex] client write(...)"); if (size > MAX_PACKET_SIZE) { debug_print(WARNING, "Size of %lu is too big.", size); return -EINVAL; } debug_print(INFO, "Sending packet of size %lu to parent", size); send_to_server(c->parent, c, size, buffer); return size; } static int ioctl_client(fs_node_t * node, unsigned long request, void * argp) { pex_client_t * c = (pex_client_t *)node->inode; switch (request) { case IOCTL_PACKETFS_QUEUED: return pipe_size(c->pipe); default: return -1; } } static void close_client(fs_node_t * node) { pex_client_t * c = (pex_client_t *)node->inode; pex_ex_t * p = c->parent; if (p) { debug_print(WARNING, "Closing packetfs client: %p:%p", (void*)p, (void*)c); spin_lock(p->lock); node_t * n = list_find(p->clients, c); if (n && n->owner == p->clients) { list_delete(p->clients, n); free(n); } spin_unlock(p->lock); char tmp[1]; send_to_server(p, c, 0, tmp); } pipe_destroy(c->pipe); free(c->pipe); free(c); } static int wait_server(fs_node_t * node, void * process) { pex_ex_t * p = (pex_ex_t *)node->device; return selectwait_fs(p->server_pipe, process); } static int check_server(fs_node_t * node) { pex_ex_t * p = (pex_ex_t *)node->device; return selectcheck_fs(p->server_pipe); } static int wait_client(fs_node_t * node, void * process) { pex_client_t * c = (pex_client_t *)node->inode; return selectwait_fs(c->pipe, process); } static int check_client(fs_node_t * node) { pex_client_t * c = (pex_client_t *)node->inode; return selectcheck_fs(c->pipe); } static void close_server(fs_node_t * node) { pex_ex_t * ex = (pex_ex_t *)node->device; pex_t * p = ex->parent; spin_lock(p->lock); node_t * lnode = list_find(p->exchanges, ex); /* Remove from exchange list */ if (lnode) { list_delete(p->exchanges, lnode); free(lnode); } /* Tell all clients we have disconnected */ spin_lock(ex->lock); while (ex->clients->length) { node_t * f = list_pop(ex->clients); pex_client_t * client = (pex_client_t*)f->value; send_to_client(ex, client, 0, NULL); client->parent = NULL; free(f); } spin_unlock(ex->lock); free(ex->clients); pipe_destroy(ex->server_pipe); free(ex->server_pipe); node->device = NULL; free(ex); spin_unlock(p->lock); } static void open_pex(fs_node_t * node, unsigned int flags) { pex_ex_t * t = (pex_ex_t *)(node->device); debug_print(NOTICE, "Opening packet exchange %s with flags 0x%x", t->name, flags); if ((flags & O_CREAT) && (flags & O_EXCL)) { if (!t->fresh) { return; /* Address in use; this should be handled by kopen... */ } t->fresh = 0; node->inode = 0; /* Set up the server side */ node->read = read_server; node->write = write_server; node->ioctl = ioctl_server; node->close = close_server; node->selectcheck = check_server; node->selectwait = wait_server; debug_print(INFO, "[pex] Server launched: %s", t->name); debug_print(INFO, "fs_node = %p", (void*)node); } else { pex_client_t * client = create_client(t); node->inode = (uintptr_t)client; node->read = read_client; node->write = write_client; node->ioctl = ioctl_client; node->close = close_client; node->selectcheck = check_client; node->selectwait = wait_client; list_insert(t->clients, client); /* XXX: Send plumbing message to server for new client connection */ debug_print(INFO, "[pex] Client connected: %s:%lx", t->name, node->inode); } return; } static struct dirent * readdir_packetfs(fs_node_t *node, uint64_t index) { pex_t * p = (pex_t *)node->device; unsigned int i = 0; debug_print(INFO, "[pex] readdir(%lu)", index); if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } index -= 2; if (index >= p->exchanges->length) { return NULL; } spin_lock(p->lock); foreach(f, p->exchanges) { if (i == index) { spin_unlock(p->lock); pex_ex_t * t = (pex_ex_t *)f->value; struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = (uint64_t)t; strcpy(out->d_name, t->name); return out; } else { ++i; } } spin_unlock(p->lock); return NULL; } static fs_node_t * file_from_pex(pex_ex_t * pex) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, pex->name); fnode->device = pex; fnode->mask = 0666; fnode->flags = FS_CHARDEVICE; fnode->open = open_pex; return fnode; } static fs_node_t * finddir_packetfs(fs_node_t * node, char * name) { if (!name) return NULL; pex_t * p = (pex_t *)node->device; debug_print(INFO, "[pex] finddir(%s)", name); spin_lock(p->lock); foreach(f, p->exchanges) { pex_ex_t * t = (pex_ex_t *)f->value; if (!strcmp(name, t->name)) { spin_unlock(p->lock); return file_from_pex(t); } } spin_unlock(p->lock); return NULL; } static int create_packetfs(fs_node_t *parent, char *name, mode_t permission) { if (!name) return -EINVAL; pex_t * p = (pex_t *)parent->device; debug_print(NOTICE, "[pex] create(%s)", name); spin_lock(p->lock); foreach(f, p->exchanges) { pex_ex_t * t = (pex_ex_t *)f->value; if (!strcmp(name, t->name)) { spin_unlock(p->lock); /* Already exists */ return -EEXIST; } } /* Make it */ pex_ex_t * new_exchange = malloc(sizeof(pex_ex_t)); new_exchange->name = strdup(name); new_exchange->fresh = 1; new_exchange->clients = list_create("pex clients",new_exchange); new_exchange->server_pipe = make_pipe(4096); new_exchange->parent = p; spin_init(new_exchange->lock); /* XXX Create exchange server pipe */ list_insert(p->exchanges, new_exchange); spin_unlock(p->lock); return 0; } static void destroy_pex(pex_ex_t * p) { /* XXX */ } static int unlink_packetfs(fs_node_t *parent, char *name) { if (!name) return -EINVAL; pex_t * p = (pex_t *)parent->device; debug_print(NOTICE, "[pex] unlink(%s)", name); int i = -1, j = 0; spin_lock(p->lock); foreach(f, p->exchanges) { pex_ex_t * t = (pex_ex_t *)f->value; if (!strcmp(name, t->name)) { destroy_pex(t); i = j; break; } j++; } if (i >= 0) { list_remove(p->exchanges, i); } else { spin_unlock(p->lock); return -ENOENT; } spin_unlock(p->lock); return 0; } static fs_node_t * packetfs_manager(void) { pex_t * pex = malloc(sizeof(pex_t)); pex->exchanges = list_create("pex exchanges",pex); spin_init(pex->lock); fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "pex"); fnode->device = pex; fnode->mask = 0777; fnode->flags = FS_DIRECTORY; fnode->readdir = readdir_packetfs; fnode->finddir = finddir_packetfs; fnode->create = create_packetfs; fnode->unlink = unlink_packetfs; return fnode; } void packetfs_initialize(void) { fs_node_t * packet_mgr = packetfs_manager(); vfs_mount("/dev/pex", packet_mgr, "pex", ""); } ================================================ FILE: kernel/vfs/pipe.c ================================================ /** * @file kernel/vfs/pipe.c * @brief Legacy buffered pipe, used for char devices. * * This is the legacy pipe implementation. If you are looking for * the userspace pipes, @ref read_unixpipe. * * This implements a simple one-direction buffer suitable for use * by, eg., device drivers that want to offer a character-driven * interface to userspace without having to worry too much about * timing or getting blocked. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_PIPES 0 static inline size_t pipe_unread(pipe_device_t * pipe) { if (pipe->read_ptr == pipe->write_ptr) { return 0; } if (pipe->read_ptr > pipe->write_ptr) { return (pipe->size - pipe->read_ptr) + pipe->write_ptr; } else { return (pipe->write_ptr - pipe->read_ptr); } } int pipe_size(fs_node_t * node) { pipe_device_t * pipe = (pipe_device_t *)node->device; spin_lock(pipe->ptr_lock); int out = pipe_unread(pipe); spin_unlock(pipe->ptr_lock); return out; } static inline size_t pipe_available(pipe_device_t * pipe) { if (pipe->read_ptr == pipe->write_ptr) { return pipe->size - 1; } if (pipe->read_ptr > pipe->write_ptr) { return pipe->read_ptr - pipe->write_ptr - 1; } else { return (pipe->size - pipe->write_ptr) + pipe->read_ptr - 1; } } int pipe_unsize(fs_node_t * node) { pipe_device_t * pipe = (pipe_device_t *)node->device; spin_lock(pipe->ptr_lock); int out = pipe_available(pipe); spin_unlock(pipe->ptr_lock); return out; } static inline void pipe_increment_read(pipe_device_t * pipe) { spin_lock(pipe->ptr_lock); pipe->read_ptr++; if (pipe->read_ptr == pipe->size) { pipe->read_ptr = 0; } spin_unlock(pipe->ptr_lock); } static inline void pipe_increment_write(pipe_device_t * pipe) { spin_lock(pipe->ptr_lock); pipe->write_ptr++; if (pipe->write_ptr == pipe->size) { pipe->write_ptr = 0; } spin_unlock(pipe->ptr_lock); } static inline void pipe_increment_write_by(pipe_device_t * pipe, size_t amount) { pipe->write_ptr = (pipe->write_ptr + amount) % pipe->size; } static void pipe_alert_waiters(pipe_device_t * pipe) { spin_lock(pipe->alert_lock); while (pipe->alert_waiters->head) { node_t * node = list_dequeue(pipe->alert_waiters); process_t * p = node->value; free(node); spin_unlock(pipe->alert_lock); process_alert_node(p, pipe); spin_lock(pipe->alert_lock); } spin_unlock(pipe->alert_lock); } ssize_t read_pipe(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { /* Retreive the pipe object associated with this file node */ pipe_device_t * pipe = (pipe_device_t *)node->device; if (pipe->dead) { send_signal(this_core->current_process->id, SIGPIPE, 1); return 0; } size_t collected = 0; while (collected == 0) { spin_lock(pipe->lock_read); if (pipe_unread(pipe) >= size) { while (pipe_unread(pipe) > 0 && collected < size) { buffer[collected] = pipe->buffer[pipe->read_ptr]; pipe_increment_read(pipe); collected++; } } wakeup_queue(pipe->wait_queue_writers); /* Deschedule and switch */ if (collected == 0) { if (sleep_on_unlocking(pipe->wait_queue_readers, &pipe->lock_read)) { if (!collected) return -ERESTARTSYS; break; } } else { spin_unlock(pipe->lock_read); } } return collected; } ssize_t write_pipe(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { /* Retreive the pipe object associated with this file node */ pipe_device_t * pipe = (pipe_device_t *)node->device; if (pipe->dead) { send_signal(this_core->current_process->id, SIGPIPE, 1); return 0; } size_t written = 0; while (written < size) { spin_lock(pipe->lock_read); /* These pipes enforce atomic writes, poorly. */ if (pipe_available(pipe) > size) { while (pipe_available(pipe) > 0 && written < size) { pipe->buffer[pipe->write_ptr] = buffer[written]; pipe_increment_write(pipe); written++; } } wakeup_queue(pipe->wait_queue_readers); pipe_alert_waiters(pipe); if (written < size) { if (sleep_on_unlocking(pipe->wait_queue_writers, &pipe->lock_read)) { if (!written) return -ERESTARTSYS; break; } } else { spin_unlock(pipe->lock_read); } } return written; } void open_pipe(fs_node_t * node, unsigned int flags) { /* Retreive the pipe object associated with this file node */ pipe_device_t * pipe = (pipe_device_t *)node->device; /* Add a reference */ pipe->refcount++; return; } void close_pipe(fs_node_t * node) { /* Retreive the pipe object associated with this file node */ pipe_device_t * pipe = (pipe_device_t *)node->device; /* Drop one reference */ pipe->refcount--; /* Check the reference count number */ if (pipe->refcount == 0) { #if 0 /* No other references exist, free the pipe (but not its buffer) */ free(pipe->buffer); list_free(pipe->wait_queue); free(pipe->wait_queue); free(pipe); /* And let the creator know there are no more references */ node->device = 0; #endif } return; } static int pipe_check(fs_node_t * node) { pipe_device_t * pipe = (pipe_device_t *)node->device; if (pipe_unread(pipe) > 0) { return 0; } return 1; } static int pipe_wait(fs_node_t * node, void * process) { pipe_device_t * pipe = (pipe_device_t *)node->device; spin_lock(pipe->alert_lock); if (!list_find(pipe->alert_waiters, process)) { list_insert(pipe->alert_waiters, process); } spin_unlock(pipe->alert_lock); spin_lock(pipe->wait_lock); list_insert(((process_t *)process)->node_waits, pipe); spin_unlock(pipe->wait_lock); return 0; } void pipe_destroy(fs_node_t * node) { pipe_device_t * pipe = (pipe_device_t *)node->device; spin_lock(pipe->ptr_lock); pipe->dead = 1; pipe_alert_waiters(pipe); wakeup_queue(pipe->wait_queue_writers); wakeup_queue(pipe->wait_queue_readers); free(pipe->alert_waiters); free(pipe->wait_queue_writers); free(pipe->wait_queue_readers); free(pipe->buffer); spin_unlock(pipe->ptr_lock); free(pipe); } fs_node_t * make_pipe(size_t size) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); pipe_device_t * pipe = malloc(sizeof(pipe_device_t)); memset(fnode, 0, sizeof(fs_node_t)); memset(pipe, 0, sizeof(pipe_device_t)); fnode->device = 0; fnode->name[0] = '\0'; snprintf(fnode->name, 100, "[pipe]"); fnode->uid = 0; fnode->gid = 0; fnode->mask = 0666; fnode->flags = FS_PIPE; fnode->read = read_pipe; fnode->write = write_pipe; fnode->open = open_pipe; fnode->close = close_pipe; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = NULL; /* TODO ioctls for pipes? maybe */ fnode->get_size = pipe_size; fnode->selectcheck = pipe_check; fnode->selectwait = pipe_wait; fnode->atime = now(); fnode->mtime = fnode->atime; fnode->ctime = fnode->atime; fnode->device = pipe; pipe->buffer = malloc(size); pipe->write_ptr = 0; pipe->read_ptr = 0; pipe->size = size; pipe->refcount = 0; pipe->dead = 0; spin_init(pipe->lock_read); spin_init(pipe->alert_lock); spin_init(pipe->wait_lock); spin_init(pipe->ptr_lock); pipe->wait_queue_writers = list_create("pipe writers",pipe); pipe->wait_queue_readers = list_create("pip readers",pipe); pipe->alert_waiters = list_create("pipe alert waiters",pipe); return fnode; } ================================================ FILE: kernel/vfs/portio.c ================================================ /** * @file kernel/vfs/portio.c * @brief File-based interface to x86 CPU port I/O. * * Provides a seek/read/write interface to x86 ports at /dev/port * * @copyright * 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-2021 K. Lange */ #include #include #include #include static ssize_t read_port(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { switch (size) { case 1: buffer[0] = inportb(offset); break; case 2: ((uint16_t *)buffer)[0] = inports(offset); break; case 4: ((uint32_t *)buffer)[0] = inportl(offset); break; default: for (unsigned int i = 0; i < size; ++i) { buffer[i] = inportb(offset + i); } break; } return size; } static ssize_t write_port(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { switch (size) { case 1: outportb(offset, buffer[0]); break; case 2: outports(offset, ((uint16_t*)buffer)[0]); break; case 4: outportl(offset, ((uint32_t*)buffer)[0]); break; default: for (unsigned int i = 0; i < size; ++i) { outportb(offset +i, buffer[i]); } break; } return size; } static fs_node_t * port_device_create(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "port"); fnode->uid = 0; fnode->gid = 0; fnode->mask = 0660; fnode->flags = FS_BLOCKDEVICE; fnode->read = read_port; fnode->write = write_port; fnode->open = NULL; fnode->close = NULL; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = NULL; return fnode; } void portio_initialize(void) { vfs_mount("/dev/port", port_device_create(), "port", ""); } ================================================ FILE: kernel/vfs/procfs.c ================================================ /** * @file kernel/vfs/procfs.c * @brief Extensible file-based information interface. * * Provides /proc and its contents, which allow userspace tools * to query kernel status through directory and text file interfaces. * * When a procfs entry is opened, a dynamic buffer is allocated and * the bound function is called. The function can then print into * the buffer, which will expand as necessary. Reads on the device * will then return data from that buffer. When the file node for * the entry is later closed, the dynamic buffer is freed. This * resolves a long-standing issue with the previous implementation * where subsequent reads could return corrupted data if offsets * changed from newly generated data. * * @copyright * 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-2023 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define PROCFS_STANDARD_ENTRIES (sizeof(std_entries) / sizeof(struct procfs_entry)) #define PROCFS_PROCDIR_ENTRIES (sizeof(procdir_entries) / sizeof(struct procfs_entry)) typedef struct procfs_entry_node { fs_node_t fnode; char * buf; size_t avail; size_t used; procfs_populate_t func; } procfs_entry_t; static ssize_t procfs_entry_read(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { procfs_entry_t * entry = (void*)node; if ((size_t)offset > entry->used) return 0; if (size > entry->used - offset) size = entry->used - offset; memcpy(buffer, (uint8_t*)entry->buf + offset, size); return size; } /** * Dynamic reallocating printf thingy */ static int procfs_cb(void * user, char c) { procfs_entry_t * entry = user; if (entry->used >= entry->avail) { entry->avail += 64; entry->buf = realloc(entry->buf, entry->avail); } entry->buf[entry->used] = c; entry->used++; return 0; } int procfs_printf(fs_node_t * node, const char * fmt, ...) { va_list args; va_start(args, fmt); int out = xvasprintf(procfs_cb, node, fmt, args); va_end(args); return out; } static void procfs_entry_open(fs_node_t * node, unsigned int flags) { procfs_entry_t * entry = (void*)node; entry->func(node); node->length = entry->used; } static void procfs_entry_close(fs_node_t * node) { procfs_entry_t * entry = (void*)node; if (entry->avail) free(entry->buf); entry->buf = NULL; entry->avail = 0; } static fs_node_t * procfs_generic_create(const char * name, procfs_populate_t read_func) { procfs_entry_t * entry = malloc(sizeof(procfs_entry_t)); memset(entry, 0x00, sizeof(procfs_entry_t)); entry->fnode.inode = 0; strcpy(entry->fnode.name, name); entry->buf = NULL; entry->avail = 0; entry->used = 0; entry->func = read_func; entry->fnode.uid = 0; entry->fnode.gid = 0; entry->fnode.mask = 0444; entry->fnode.flags = FS_FILE; entry->fnode.read = procfs_entry_read; entry->fnode.write = NULL; entry->fnode.open = procfs_entry_open; entry->fnode.close = procfs_entry_close; entry->fnode.readdir = NULL; entry->fnode.finddir = NULL; entry->fnode.ctime = now(); entry->fnode.mtime = now(); entry->fnode.atime = now(); return &entry->fnode; } static void proc_cmdline_func(fs_node_t *node) { process_t * proc = process_from_pid(node->inode); if (!proc) { /* wat */ return; } if (!proc->cmdline) { procfs_printf(node, "%s", proc->name); return; } char ** args = proc->cmdline; while (*args) { procfs_printf(node, "%s", *args); if (*(args+1)) { procfs_printf(node, "\036"); } args++; } } static void proc_status_func(fs_node_t *node) { process_t * proc = process_from_pid(node->inode); process_t * parent = process_get_parent(proc); if (!proc) { /* wat */ return; } char state = 'S'; /* Base state */ if ((proc->flags & PROC_FLAG_RUNNING) || process_is_ready(proc)) { state = 'R'; /* Running or runnable */ } else if ((proc->flags & PROC_FLAG_FINISHED)) { state = 'Z'; /* Zombie - exited but not yet reaped */ } else if ((proc->flags & PROC_FLAG_SUSPENDED)) { state = 'T'; /* Stopped; TODO can we differentiate stopped tracees correctly? */ } char * name = proc->name + strlen(proc->name) - 1; while (1) { if (*name == '/') { name++; break; } if (name == proc->name) break; name--; } /* Calculate process memory usage */ long mem_usage = mmu_count_user(proc->thread.page_directory->directory) * 4; long shm_usage = mmu_count_shm(proc->thread.page_directory->directory) * 4; long mem_permille = 1000 * (mem_usage + shm_usage) / mmu_total_memory(); procfs_printf(node, "Name:\t%s\n" /* name */ "State:\t%c\n" "Tgid:\t%d\n" /* group ? group : pid */ "Pid:\t%d\n" /* pid */ "PPid:\t%d\n" /* parent pid */ "Pgid:\t%d\n" /* progress group id (job) */ "Sid:\t%d\n" /* session id */ "Uid:\t%d\n" "Ueip:\t%#zx\n" "SCid:\t%zu\n" "SC0:\t%#zx\n" "SC1:\t%#zx\n" "SC2:\t%#zx\n" "SC3:\t%#zx\n" "SC4:\t%#zx\n" "UserStack:\t%#zx\n" "Path:\t%s\n" "VmSize:\t %ld kB\n" "RssShmem:\t %ld kB\n" "MemPermille:\t %ld\n" "LastCore:\t %d\n" "TotalTime:\t %ld us\n" "SysTime:\t %ld us\n" "CpuPermille:\t %d %d %d %d\n" "UserBrk:\t%#zx\n" , name, state, proc->group ? proc->group : proc->id, proc->id, parent ? parent->id : 0, proc->job, proc->session, proc->user, proc->syscall_registers ? arch_user_ip(proc->syscall_registers) : 0, proc->syscall_registers ? arch_syscall_number(proc->syscall_registers) : 0, proc->syscall_registers ? arch_syscall_arg0(proc->syscall_registers) : 0, proc->syscall_registers ? arch_syscall_arg1(proc->syscall_registers) : 0, proc->syscall_registers ? arch_syscall_arg2(proc->syscall_registers) : 0, proc->syscall_registers ? arch_syscall_arg3(proc->syscall_registers) : 0, proc->syscall_registers ? arch_syscall_arg4(proc->syscall_registers) : 0, proc->syscall_registers ? arch_stack_pointer(proc->syscall_registers) : 0, proc->cmdline ? proc->cmdline[0] : "(none)", mem_usage, shm_usage, mem_permille, proc->owner, proc->time_total / arch_cpu_mhz(), proc->time_sys / arch_cpu_mhz(), proc->usage[0], proc->usage[1], proc->usage[2], proc->usage[3], proc->image.heap ); } static struct procfs_entry procdir_entries[] = { {1, "cmdline", proc_cmdline_func}, {2, "status", proc_status_func}, }; static struct dirent * readdir_procfs_procdir(fs_node_t *node, uint64_t index) { if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } index -= 2; if (index < PROCFS_PROCDIR_ENTRIES) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = procdir_entries[index].id; strcpy(out->d_name, procdir_entries[index].name); return out; } return NULL; } static fs_node_t * finddir_procfs_procdir(fs_node_t * node, char * name) { if (!name) return NULL; for (unsigned int i = 0; i < PROCFS_PROCDIR_ENTRIES; ++i) { if (!strcmp(name, procdir_entries[i].name)) { fs_node_t * out = procfs_generic_create(procdir_entries[i].name, procdir_entries[i].func); out->inode = node->inode; return out; } } return NULL; } static fs_node_t * procfs_procdir_create(process_t * process) { pid_t pid = process->id; fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = pid; snprintf(fnode->name, 100, "%d", pid); fnode->uid = 0; fnode->gid = 0; fnode->mask = 0555; fnode->flags = FS_DIRECTORY; fnode->read = NULL; fnode->write = NULL; fnode->open = NULL; fnode->close = NULL; fnode->readdir = readdir_procfs_procdir; fnode->finddir = finddir_procfs_procdir; fnode->nlink = 1; fnode->ctime = process->start.tv_sec; fnode->mtime = process->start.tv_sec; fnode->atime = process->start.tv_sec; return fnode; } static void cpuinfo_func(fs_node_t *node) { #ifdef __x86_64__ for (int i = 0; i < processor_count; ++i) { procfs_printf(node, "Processor: %d\n" "Manufacturer: %s\n" "MHz: %zd\n" "Family: %d\n" "Model: %d\n" "Model name: %s\n" "LAPIC id: %d\n" "\n", processor_local_data[i].cpu_id, processor_local_data[i].cpu_manufacturer, arch_cpu_mhz(), /* TODO Should this be per-cpu? */ processor_local_data[i].cpu_family, processor_local_data[i].cpu_model, processor_local_data[i].cpu_model_name, processor_local_data[i].lapic_id ); } #elif defined(__aarch64__) for (int i = 0; i < processor_count; ++i) { procfs_printf(node, "Processor: %d\n" "Implementer: %#x\n" "Variant: %#x\n" "Architecture: %#x\n" "PartNum: %#x\n" "Revision: %#x\n" "\n", processor_local_data[i].cpu_id, (unsigned int)(processor_local_data[i].midr >> 24) & 0xFF, (unsigned int)(processor_local_data[i].midr >> 20) & 0xF, (unsigned int)(processor_local_data[i].midr >> 16) & 0xF, (unsigned int)(processor_local_data[i].midr >> 4) & 0xFFF, (unsigned int)(processor_local_data[i].midr >> 0) & 0xF ); } #endif } static void meminfo_func(fs_node_t *node) { size_t total = mmu_total_memory(); size_t free = total - mmu_used_memory(); size_t kheap = ((uintptr_t)sbrk(0) - 0xffffff0000000000UL) / 1024; procfs_printf(node, "MemTotal: %zu kB\n" "MemFree: %zu kB\n" "KHeapUse: %zu kB\n" , total, free, kheap); } #ifdef __x86_64__ static void pat_func(fs_node_t *node) { uint32_t pat_value_low, pat_value_high; asm volatile ( "rdmsr" : "=a" (pat_value_low), "=d" (pat_value_high): "c" (0x277) ); uint64_t pat_values = ((uint64_t)pat_value_high << 32) | (pat_value_low); const char * pat_names[] = { "uncacheable (UC)", "write combining (WC)", "Reserved", "Reserved", "write through (WT)", "write protected (WP)", "write back (WB)", "uncached (UC-)" }; int pa_0 = (pat_values >> 0) & 0x7; int pa_1 = (pat_values >> 8) & 0x7; int pa_2 = (pat_values >> 16) & 0x7; int pa_3 = (pat_values >> 24) & 0x7; int pa_4 = (pat_values >> 32) & 0x7; int pa_5 = (pat_values >> 40) & 0x7; int pa_6 = (pat_values >> 48) & 0x7; int pa_7 = (pat_values >> 56) & 0x7; procfs_printf(node, "PA0: %d %s\n" "PA1: %d %s\n" "PA2: %d %s\n" "PA3: %d %s\n" "PA4: %d %s\n" "PA5: %d %s\n" "PA6: %d %s\n" "PA7: %d %s\n", pa_0, pat_names[pa_0], pa_1, pat_names[pa_1], pa_2, pat_names[pa_2], pa_3, pat_names[pa_3], pa_4, pat_names[pa_4], pa_5, pat_names[pa_5], pa_6, pat_names[pa_6], pa_7, pat_names[pa_7] ); } #endif static void uptime_func(fs_node_t *node) { unsigned long timer_ticks, timer_subticks; relative_time(0,0,&timer_ticks,&timer_subticks); procfs_printf(node, "%lu.%06lu\n", timer_ticks, timer_subticks); } static void cmdline_func(fs_node_t *node) { const char * cmdline = arch_get_cmdline(); procfs_printf(node, "%s\n", cmdline ? cmdline : ""); } static void version_func(fs_node_t *node) { procfs_printf(node, "%s ", __kernel_name); procfs_printf(node, __kernel_version_format, __kernel_version_major, __kernel_version_minor, __kernel_version_lower, __kernel_version_suffix); procfs_printf(node, " %s %s %s %s\n", __kernel_version_codename, __kernel_build_date, __kernel_build_time, __kernel_arch); } static void compiler_func(fs_node_t *node) { procfs_printf(node, "%s\n", __kernel_compiler_version); } extern tree_t * fs_tree; /* kernel/fs/vfs.c */ static void mount_recurse(fs_node_t * pnode, tree_node_t * node, size_t height, char * prefix, char *p) { /* End recursion on a blank entry */ if (!node) return; /* Get the current process */ struct vfs_entry * fnode = (struct vfs_entry *)node->value; /* Print the process name */ /* Set up name */ if (node != fs_tree->root) { *p++ = '/'; for (char * n = fnode->name; *n; ++n) { *p++ = *n; } *p = '\0'; } if (fnode->file) procfs_printf(pnode, "%s %s %s\n", prefix, fnode->fs_type, fnode->device); foreach(child, node->children) { /* Recursively print the children */ mount_recurse(pnode, child->value, height + 1, prefix, p); } } static void mounts_func(fs_node_t *node) { char prefix[1024] = {'/',0}; mount_recurse(node, fs_tree->root, 0, prefix, prefix); } static void modules_func(fs_node_t *node) { list_t * hash_keys = hashmap_keys(modules_get_list()); if (!hash_keys || !hash_keys->length) return; foreach(_key, hash_keys) { char * key = (char *)_key->value; struct LoadedModule * mod_info = hashmap_get(modules_get_list(), key); procfs_printf(node, "%#zx %zu %zu %s\n", mod_info->baseAddress, mod_info->fileSize, mod_info->loadedSize, key); } free(hash_keys); } extern hashmap_t * fs_types; /* from kernel/fs/vfs.c */ static void filesystems_func(fs_node_t *node) { list_t * hash_keys = hashmap_keys(fs_types); if (!hash_keys || !hash_keys->length) return; foreach(_key, hash_keys) { char * key = (char *)_key->value; procfs_printf(node, "%s\n", key); } free(hash_keys); } static void loader_func(fs_node_t *node) { procfs_printf(node, "%s\n", arch_get_loader()); } #ifdef __x86_64__ #include #include static void irq_func(fs_node_t *node) { for (int i = 0; i < 16; ++i) { procfs_printf(node, "irq %d: ", i); for (int j = 0; j < 4; ++j) { const char * t = get_irq_handler(i, j); if (!t) break; procfs_printf(node, "%s%s", j ? "," : "", t); } procfs_printf(node, "\n"); } outportb(0x20, 0x0b); outportb(0xa0, 0x0b); procfs_printf(node, "isr=0x%04x\n", (inportb(0xA0) << 8) | inportb(0x20)); outportb(0x20, 0x0a); outportb(0xa0, 0x0a); procfs_printf(node, "irr=0x%04x\n", (inportb(0xA0) << 8) | inportb(0x20)); procfs_printf(node, "imr=0x%04x\n", (inportb(0xA1) << 8) | inportb(0x21)); } #endif /** * Basically the same as the kdebug `pci` command. */ static void scan_hit_list(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) { fs_node_t * node = extra; procfs_printf(node, "%02x:%02x.%d (%04x, %04x:%04x)\n", (int)pci_extract_bus(device), (int)pci_extract_slot(device), (int)pci_extract_func(device), (int)pci_find_type(device), vendorid, deviceid); procfs_printf(node, " BAR0: 0x%08x", pci_read_field(device, PCI_BAR0, 4)); procfs_printf(node, " BAR1: 0x%08x", pci_read_field(device, PCI_BAR1, 4)); procfs_printf(node, " BAR2: 0x%08x", pci_read_field(device, PCI_BAR2, 4)); procfs_printf(node, " BAR3: 0x%08x", pci_read_field(device, PCI_BAR3, 4)); procfs_printf(node, " BAR4: 0x%08x", pci_read_field(device, PCI_BAR4, 4)); procfs_printf(node, " BAR5: 0x%08x\n", pci_read_field(device, PCI_BAR5, 4)); procfs_printf(node, " IRQ Line: %d", pci_read_field(device, 0x3C, 1)); procfs_printf(node, " IRQ Pin: %d", pci_read_field(device, 0x3D, 1)); procfs_printf(node, " Interrupt: %d", pci_get_interrupt(device)); procfs_printf(node, " Status: 0x%04x\n", pci_read_field(device, PCI_STATUS, 2)); } static void pci_func(fs_node_t *node) { pci_scan(&scan_hit_list, -1, node); } static void idle_func(fs_node_t *node) { for (int i = 0; i < processor_count; ++i) { procfs_printf(node, "%d: %4d %4d %4d %4d\n", i, processor_local_data[i].kernel_idle_task->usage[0], processor_local_data[i].kernel_idle_task->usage[1], processor_local_data[i].kernel_idle_task->usage[2], processor_local_data[i].kernel_idle_task->usage[3] ); } } static void kallsyms_func(fs_node_t *fnode) { /* This doesn't include module symbols at the moment... */ list_t * syms = ksym_list(); foreach(node, syms) { procfs_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); } list_free(syms); free(syms); } static struct procfs_entry std_entries[] = { {-1, "cpuinfo", cpuinfo_func}, {-2, "meminfo", meminfo_func}, {-3, "uptime", uptime_func}, {-4, "cmdline", cmdline_func}, {-5, "version", version_func}, {-6, "compiler", compiler_func}, {-7, "mounts", mounts_func}, {-8, "modules", modules_func}, {-9, "filesystems", filesystems_func}, {-10,"loader", loader_func}, {-11,"idle", idle_func}, {-12,"kallsyms", kallsyms_func}, {-13,"pci", pci_func}, #ifdef __x86_64__ {-14,"irq", irq_func}, {-15,"pat", pat_func}, #endif }; static list_t * extended_entries = NULL; static long next_id = 0; int procfs_install(struct procfs_entry * entry) { if (!extended_entries) { extended_entries = list_create("procfs entries",NULL); next_id = -PROCFS_STANDARD_ENTRIES - 1; } entry->id = next_id--; list_insert(extended_entries, entry); return 0; } static struct dirent * readdir_procfs_root(fs_node_t *node, uint64_t index) { if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } if (index == 2) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "self"); return out; } index -= 3; if (index < PROCFS_STANDARD_ENTRIES) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = std_entries[index].id; strcpy(out->d_name, std_entries[index].name); return out; } index -= PROCFS_STANDARD_ENTRIES; if (extended_entries) { if (index < extended_entries->length) { size_t i = 0; node_t * n = extended_entries->head; while (i < index) { n = n->next; i++; } struct procfs_entry * e = n->value; struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = e->id; strcpy(out->d_name, e->name); return out; } index -= extended_entries->length; } int i = index + 1; pid_t pid = 0; foreach(lnode, process_list) { i--; if (i == 0) { process_t * proc = (process_t *)lnode->value; pid = proc->id; break; } } if (pid == 0) { return NULL; } struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = pid; snprintf(out->d_name, 100, "%d", pid); return out; } static ssize_t readlink_self(fs_node_t * node, char * buf, size_t size) { char tmp[30]; size_t req; snprintf(tmp, 100, "/proc/%d", this_core->current_process->id); req = strlen(tmp) + 1; if (size < req) { memcpy(buf, tmp, size); buf[size-1] = '\0'; return size-1; } if (size > req) size = req; memcpy(buf, tmp, size); return size-1; } static fs_node_t * procfs_create_self(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "self"); fnode->mask = 0777; fnode->uid = 0; fnode->gid = 0; fnode->flags = FS_FILE | FS_SYMLINK; fnode->readlink = readlink_self; fnode->length = 1; fnode->nlink = 1; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); return fnode; } static fs_node_t * finddir_procfs_root(fs_node_t * node, char * name) { if (!name) return NULL; if (strlen(name) < 1) return NULL; if (name[0] >= '0' && name[0] <= '9') { /* XXX process entries */ pid_t pid = atoi(name); process_t * proc = process_from_pid(pid); if (!proc) { return NULL; } fs_node_t * out = procfs_procdir_create(proc); return out; } if (!strcmp(name,"self")) { return procfs_create_self(); } for (unsigned int i = 0; i < PROCFS_STANDARD_ENTRIES; ++i) { if (!strcmp(name, std_entries[i].name)) { fs_node_t * out = procfs_generic_create(std_entries[i].name, std_entries[i].func); return out; } } if (extended_entries) { foreach(node, extended_entries) { struct procfs_entry * e = node->value; if (!strcmp(name, e->name)) { fs_node_t * out = procfs_generic_create(e->name, e->func); return out; } } } return NULL; } static fs_node_t * procfs_create(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "proc"); fnode->mask = 0555; fnode->uid = 0; fnode->gid = 0; fnode->flags = FS_DIRECTORY; fnode->read = NULL; fnode->write = NULL; fnode->open = NULL; fnode->close = NULL; fnode->readdir = readdir_procfs_root; fnode->finddir = finddir_procfs_root; fnode->nlink = 1; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); return fnode; } void procfs_initialize(void) { /* TODO Move this to some sort of config */ vfs_mount("/proc", procfs_create(), "procfs", ""); //debug_print_vfs_tree(); } ================================================ FILE: kernel/vfs/ramdisk.c ================================================ /** * @file kernel/vfs/ramdisk.c * @brief VFS wrapper for physical memory blocks. * * Allows raw physical memory blocks provided by the loader to be * used like a block file. Used to provide multiboot payloads * as /dev/ram* files. * * Note that the ramdisk driver really does deal with physical * memory addresses, not virtual address, and once a block of * pages has been handed over to the ramdisk driver it is owned * by the ramdisk driver which may mark those pages as available * (via an ioctl request). * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include static ssize_t read_ramdisk(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer); static ssize_t write_ramdisk(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer); static void open_ramdisk(fs_node_t *node, unsigned int flags); static void close_ramdisk(fs_node_t *node); static ssize_t read_ramdisk(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { if ((size_t)offset > node->length) { return 0; } if ((size_t)offset + size > node->length) { size_t i = node->length - offset; size = i; } memcpy(buffer, (void *)((uintptr_t)mmu_map_from_physical(node->inode) + (uintptr_t)offset), size); return size; } static ssize_t write_ramdisk(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { if ((size_t)offset > node->length) { return 0; } if (offset + size > node->length) { unsigned int i = node->length - offset; size = i; } memcpy((void *)((uintptr_t)mmu_map_from_physical(node->inode) + (uintptr_t)offset), buffer, size); return size; } static void open_ramdisk(fs_node_t * node, unsigned int flags) { return; } static void close_ramdisk(fs_node_t * node) { return; } static int ioctl_ramdisk(fs_node_t * node, unsigned long request, void * argp) { switch (request) { case 0x4001: if (this_core->current_process->user != 0) { return -EPERM; } else { /* Clear all of the memory used by this ramdisk */ if (node->length >= 0x1000) { if (node->length % 0x1000) { /* It would be a very bad idea to wipe the wrong page here. */ node->length -= node->length % 0x1000; } for (uintptr_t i = node->inode; i < (node->inode + node->length); i += 0x1000) { mmu_frame_release(i); } } /* Mark the file length as 0 */ node->length = 0; ((fs_node_t*)node->device)->length = 0; return 0; } default: return -EINVAL; } return -1; } static fs_node_t * ramdisk_device_create(int device_number, uintptr_t location, size_t size) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = location; snprintf(fnode->name, 10, "ram%d", device_number); fnode->device = fnode; /* stupid vfs */ fnode->uid = 0; fnode->gid = 0; fnode->mask = 0770; fnode->length = size; fnode->flags = FS_BLOCKDEVICE; fnode->read = read_ramdisk; fnode->write = write_ramdisk; fnode->open = open_ramdisk; fnode->close = close_ramdisk; fnode->ioctl = ioctl_ramdisk; return fnode; } static int last_device_number = 0; fs_node_t * ramdisk_mount(uintptr_t location, size_t size) { fs_node_t * ramdisk = ramdisk_device_create(last_device_number, location, size); if (ramdisk) { char tmp[64]; snprintf(tmp, 63, "/dev/%s", ramdisk->name); char addr[64]; snprintf(addr, 63, "%p,%zu", (void*)location, size); vfs_mount(tmp, ramdisk, "ramdisk", addr); last_device_number += 1; return ramdisk; } return NULL; } ================================================ FILE: kernel/vfs/random.c ================================================ /** * @file kernel/vfs/random.c * @brief Bad RNG. * * Provides a terrible little xorshift random number generator. * * @copyright * 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-2018 K. Lange */ #include #include #include uint32_t rand(void) { static uint32_t x = 123456789; static uint32_t y = 362436069; static uint32_t z = 521288629; static uint32_t w = 88675123; uint32_t t; t = x ^ (x << 11); x = y; y = z; z = w; return w = w ^ (w >> 19) ^ t ^ (t >> 8); } static ssize_t read_random(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { size_t s = 0; while (s < size) { buffer[s] = rand() % 0xFF; s++; } return size; } static fs_node_t * random_device_create(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "random"); fnode->uid = 0; fnode->gid = 0; fnode->mask = 0444; fnode->length = 1024; fnode->flags = FS_CHARDEVICE; fnode->read = read_random; return fnode; } void random_initialize(void) { vfs_mount("/dev/random", random_device_create(), "random", ""); vfs_mount("/dev/urandom", random_device_create(), "random", ""); } ================================================ FILE: kernel/vfs/tarfs.c ================================================ /** * @file kernel/vfs/tarfs.c * @brief Read-only filesystem driver for ustar archives. * * @copyright * 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 */ #include #include #include #include #include #include #include #define TARFS_LOG_LEVEL WARNING struct tarfs { fs_node_t * device; unsigned int length; }; struct ustar { char filename[100]; char mode[8]; char ownerid[8]; char groupid[8]; char size[12]; char mtime[12]; char checksum[8]; char type[1]; char link[100]; char ustar[6]; char version[2]; char owner[32]; char group[32]; char dev_major[8]; char dev_minor[8]; char prefix[155]; }; static unsigned int interpret_uid(struct ustar * file) { return ((file->ownerid[0] - '0') << 18) | ((file->ownerid[1] - '0') << 15) | ((file->ownerid[2] - '0') << 12) | ((file->ownerid[3] - '0') << 9) | ((file->ownerid[4] - '0') << 6) | ((file->ownerid[5] - '0') << 3) | ((file->ownerid[6] - '0') << 0); } static unsigned int interpret_gid(struct ustar * file) { return ((file->groupid[0] - '0') << 18) | ((file->groupid[1] - '0') << 15) | ((file->groupid[2] - '0') << 12) | ((file->groupid[3] - '0') << 9) | ((file->groupid[4] - '0') << 6) | ((file->groupid[5] - '0') << 3) | ((file->groupid[6] - '0') << 0); } static unsigned int interpret_mode(struct ustar * file) { return ((file->mode[0] - '0') << 18) | ((file->mode[1] - '0') << 15) | ((file->mode[2] - '0') << 12) | ((file->mode[3] - '0') << 9) | ((file->mode[4] - '0') << 6) | ((file->mode[5] - '0') << 3) | ((file->mode[6] - '0') << 0); } static unsigned int interpret_size(struct ustar * file) { return ((file->size[ 0] - '0') << 30) | ((file->size[ 1] - '0') << 27) | ((file->size[ 2] - '0') << 24) | ((file->size[ 3] - '0') << 21) | ((file->size[ 4] - '0') << 18) | ((file->size[ 5] - '0') << 15) | ((file->size[ 6] - '0') << 12) | ((file->size[ 7] - '0') << 9) | ((file->size[ 8] - '0') << 6) | ((file->size[ 9] - '0') << 3) | ((file->size[10] - '0') << 0); } static unsigned int round_to_512(unsigned int i) { unsigned int t = i % 512; if (!t) return i; return i + (512 - t); } static int ustar_from_offset(struct tarfs * self, unsigned int offset, struct ustar * out); static fs_node_t * file_from_ustar(struct tarfs * self, struct ustar * file, unsigned int offset); #ifndef strncat static char * strncat(char *dest, const char *src, size_t n) { char * end = dest; while (*end != '\0') { ++end; } size_t i = 0; while (*src && i < n) { *end = *src; end++; src++; i++; } *end = '\0'; return dest; } #endif static int count_slashes(char * string) { int i = 0; char * s = strstr(string, "/"); while (s) { if (*(s+1) == '\0') return i; i++; s = strstr(s+1,"/"); } return i; } static struct dirent * readdir_tar_root(fs_node_t *node, unsigned long index) { if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } index -= 2; struct tarfs * self = node->device; /* Go through each file and pick the ones are at the root */ /* Root files will have no /, so this is easy */ unsigned int offset = 0; struct ustar * file = malloc(sizeof(struct ustar)); while (offset < self->length) { int status = ustar_from_offset(self, offset, file); if (!status) { free(file); return NULL; } char filename_workspace[256]; memset(filename_workspace, 0, 256); strncat(filename_workspace, file->prefix, 155); strncat(filename_workspace, file->filename, 100); if (!count_slashes(filename_workspace)) { char * slash = strstr(filename_workspace,"/"); if (slash) *slash = '\0'; /* remove trailing slash */ if (strlen(filename_workspace)) { if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = offset; strcpy(out->d_name, filename_workspace); free(file); return out; } else { index--; } } } offset += 512; offset += round_to_512(interpret_size(file)); } free(file); return NULL; } static ssize_t read_tarfs(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { struct tarfs * self = node->device; struct ustar * file = malloc(sizeof(struct ustar)); ustar_from_offset(self, node->inode, file); size_t file_size = interpret_size(file); if ((size_t)offset > file_size) return 0; if (offset + size > file_size) { size = file_size - offset; } free(file); return read_fs(self->device, offset + node->inode + 512, size, buffer); } static struct dirent * readdir_tarfs(fs_node_t *node, unsigned long index) { if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } index -= 2; struct tarfs * self = node->device; /* Go through each file and pick the ones are at the root */ /* Root files will have no /, so this is easy */ unsigned int offset = node->inode; /* Read myself */ struct ustar * file = malloc(sizeof(struct ustar)); int status = ustar_from_offset(self, node->inode, file); char my_filename[256]; /* Figure out my own filename, with forward slash */ memset(my_filename, 0, 256); strncat(my_filename, file->prefix, 155); strncat(my_filename, file->filename, 100); while (offset < self->length) { ustar_from_offset(self, offset, file); if (!status) { free(file); return NULL; } char filename_workspace[256]; memset(filename_workspace, 0, 256); strncat(filename_workspace, file->prefix, 155); strncat(filename_workspace, file->filename, 100); if (startswith(filename_workspace, my_filename)) { if (!count_slashes(filename_workspace + strlen(my_filename))) { if (strlen(filename_workspace + strlen(my_filename))) { if (index == 0) { char * slash = strstr(filename_workspace+strlen(my_filename),"/"); if (slash) *slash = '\0'; /* remove trailing slash */ struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = offset; strcpy(out->d_name, filename_workspace+strlen(my_filename)); free(file); return out; } else { index--; } } } } offset += 512; offset += round_to_512(interpret_size(file)); } free(file); return NULL; } static fs_node_t * finddir_tarfs(fs_node_t *node, char *name) { struct tarfs * self = node->device; /* find my own filename */ struct ustar * file = malloc(sizeof(struct ustar)); ustar_from_offset(self, node->inode, file); char my_filename[256]; /* Figure out my own filename, with forward slash */ memset(my_filename, 0, 256); strncat(my_filename, file->prefix, 155); strncat(my_filename, file->filename, 100); /* Append name */ strncat(my_filename, name, strlen(name)); if (strlen(my_filename) > 255) { printf("tarfs: critical: what?"); } unsigned int offset = node->inode; while (offset < self->length) { int status = ustar_from_offset(self, offset, file); if (!status) { free(file); return NULL; } char filename_workspace[256]; memset(filename_workspace, 0, 256); strncat(filename_workspace, file->prefix, 155); strncat(filename_workspace, file->filename, 100); if (filename_workspace[strlen(filename_workspace)-1] == '/') { filename_workspace[strlen(filename_workspace)-1] = '\0'; } if (!strcmp(filename_workspace, my_filename)) { return file_from_ustar(self, file, offset); } offset += 512; offset += round_to_512(interpret_size(file)); } free(file); return NULL; } static ssize_t readlink_tarfs(fs_node_t * node, char * buf, size_t size) { struct tarfs * self = node->device; struct ustar * file = malloc(sizeof(struct ustar)); ustar_from_offset(self, node->inode, file); if (size < strlen(file->link) + 1) { //debug_print(INFO, "Requested read size was only %d, need %d.", size, strlen(file->link)+1); memcpy(buf, file->link, size-1); buf[size-1] = '\0'; free(file); return size-1; } else { //debug_print(INFO, "Reading link target is [%s]", file->link); memcpy(buf, file->link, strlen(file->link) + 1); free(file); return strlen(file->link); } } static int create_ret_rofs(fs_node_t *parent, char *name, mode_t permission) { return -EROFS; } static fs_node_t * file_from_ustar(struct tarfs * self, struct ustar * file, unsigned int offset) { fs_node_t * fs = malloc(sizeof(fs_node_t)); memset(fs, 0, sizeof(fs_node_t)); fs->device = self; fs->inode = offset; fs->impl = 0; char filename_workspace[256]; memcpy(fs->name, filename_workspace, strlen(filename_workspace)+1); fs->uid = interpret_uid(file); fs->gid = interpret_gid(file); fs->length = interpret_size(file); fs->mask = interpret_mode(file); fs->nlink = 0; /* Unsupported */ fs->flags = FS_FILE; if (file->type[0] == '5') { fs->flags = FS_DIRECTORY; fs->readdir = readdir_tarfs; fs->finddir = finddir_tarfs; fs->create = create_ret_rofs; } else if (file->type[0] == '1') { //debug_print(ERROR, "Hardlink detected"); /* go through file and find target, reassign inode to point to that */ } else if (file->type[0] == '2') { fs->flags = FS_SYMLINK; fs->readlink = readlink_tarfs; } else { fs->flags = FS_FILE; fs->read = read_tarfs; } free(file); #if 0 /* TODO times are also available from the file */ fs->atime = now(); fs->mtime = now(); fs->ctime = now(); #endif return fs; } static fs_node_t * finddir_tar_root(fs_node_t *node, char *name) { struct tarfs * self = node->device; unsigned int offset = 0; struct ustar * file = malloc(sizeof(struct ustar)); while (offset < self->length) { int status = ustar_from_offset(self, offset, file); if (!status) { free(file); return NULL; } char filename_workspace[256]; memset(filename_workspace, 0, 256); strncat(filename_workspace, file->prefix, 155); strncat(filename_workspace, file->filename, 100); if (count_slashes(filename_workspace)) { /* skip */ } else { char * slash = strstr(filename_workspace,"/"); if (slash) *slash = '\0'; if (!strcmp(filename_workspace, name)) { return file_from_ustar(self, file, offset); } } offset += 512; offset += round_to_512(interpret_size(file)); } free(file); return NULL; } static int ustar_from_offset(struct tarfs * self, unsigned int offset, struct ustar * out) { read_fs(self->device, offset, sizeof(struct ustar), (unsigned char*)out); if (out->ustar[0] != 'u' || out->ustar[1] != 's' || out->ustar[2] != 't' || out->ustar[3] != 'a' || out->ustar[4] != 'r') { return 0; } return 1; } static fs_node_t * tar_mount(const char * device, const char * mount_path) { char * arg = strdup(device); char * argv[10]; int argc = tokenize(arg, ",", argv); if (argc > 1) { //debug_print(WARNING, "tarfs driver takes no options"); printf("tarfs got unexpected mount arguments: %s\n", device); } fs_node_t * dev = kopen(argv[0], 0); free(arg); /* Shouldn't need the filename or args anymore */ if (!dev) { //debug_print(ERROR, "failed to open %s", device); printf("tarfs could not open target device\n"); return NULL; } /* Create a metadata struct for this mount */ struct tarfs * self = malloc(sizeof(struct tarfs)); self->device = dev; self->length = dev->length; fs_node_t * root = malloc(sizeof(fs_node_t)); memset(root, 0, sizeof(fs_node_t)); root->uid = 0; root->gid = 0; root->length = 0; root->mask = 0555; root->readdir = readdir_tar_root; root->finddir = finddir_tar_root; root->create = create_ret_rofs; root->flags = FS_DIRECTORY; root->device = self; return root; } int tarfs_register_init(void) { vfs_register("tar", tar_mount); return 0; } ================================================ FILE: kernel/vfs/tmpfs.c ================================================ /** * @file kernel/vfs/tmpfs.c * @brief In-memory read-write filesystem. * * Generally provides the filesystem for "migrated" live CDs, * as well as /tmp and /var. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* 4KB */ #define BLOCKSIZE 0x1000 #define TMPFS_TYPE_FILE 1 #define TMPFS_TYPE_DIR 2 #define TMPFS_TYPE_LINK 3 static volatile intptr_t tmpfs_total_blocks = 0; static fs_node_t * tmpfs_from_dir(struct tmpfs_dir * d); static struct tmpfs_file * tmpfs_file_new(char * name) { struct tmpfs_file * t = malloc(sizeof(struct tmpfs_file)); spin_init(t->lock); t->name = strdup(name); t->type = TMPFS_TYPE_FILE; t->length = 0; t->pointers = 2; t->block_count = 0; t->mask = 0; t->uid = 0; t->gid = 0; t->atime = now(); t->mtime = t->atime; t->ctime = t->atime; t->blocks = malloc(t->pointers * sizeof(char *)); for (size_t i = 0; i < t->pointers; ++i) { t->blocks[i] = 0; } return t; } static int symlink_tmpfs(fs_node_t * parent, char * target, char * name) { struct tmpfs_dir * d = (struct tmpfs_dir *)parent->inode; spin_lock(d->lock); foreach(f, d->files) { struct tmpfs_file * t = (struct tmpfs_file *)f->value; if (!strcmp(name, t->name)) { spin_unlock(d->lock); return -EEXIST; /* Already exists */ } } spin_unlock(d->lock); struct tmpfs_file * t = tmpfs_file_new(name); t->mount = parent->mount; t->type = TMPFS_TYPE_LINK; t->target = strdup(target); t->length = strlen(target); t->mask = 0777; t->uid = this_core->current_process->user; t->gid = this_core->current_process->user; spin_lock(d->lock); list_insert(d->files, t); spin_unlock(d->lock); return 0; } static ssize_t readlink_tmpfs(fs_node_t * node, char * buf, size_t size) { struct tmpfs_file * t = (struct tmpfs_file *)(node->inode); spin_lock(t->lock); if (t->type != TMPFS_TYPE_LINK) { spin_unlock(t->lock); printf("tmpfs: not a symlink?\n"); return -1; } if (size < strlen(t->target) + 1) { memcpy(buf, t->target, size-1); buf[size-1] = '\0'; spin_unlock(t->lock); return size-2; } else { size_t len = strlen(t->target); memcpy(buf, t->target, len + 1); spin_unlock(t->lock); return len; } } static struct tmpfs_dir * tmpfs_dir_new(char * name, struct tmpfs_dir * parent) { struct tmpfs_dir * d = malloc(sizeof(struct tmpfs_dir)); spin_init(d->lock); spin_init(d->nest_lock); d->mount = parent ? parent->mount : NULL; d->name = strdup(name); d->type = TMPFS_TYPE_DIR; d->mask = 0; d->uid = 0; d->gid = 0; d->atime = now(); d->mtime = d->atime; d->ctime = d->atime; d->files = list_create("tmpfs directory entries",d); return d; } static void tmpfs_file_free(struct tmpfs_file * t) { spin_lock(t->lock); if (t->type == TMPFS_TYPE_LINK) { /* free target string */ free(t->target); } for (size_t i = 0; i < t->block_count; ++i) { mmu_frame_release((uintptr_t)t->blocks[i] * 0x1000); tmpfs_total_blocks--; } spin_unlock(t->lock); } static void tmpfs_file_blocks_embiggen(struct tmpfs_file * t) { t->pointers *= 2; t->blocks = realloc(t->blocks, sizeof(char *) * t->pointers); } static char * tmpfs_file_getset_block(struct tmpfs_file * t, size_t blockid, int create) { if (create) { while (blockid >= t->pointers) { tmpfs_file_blocks_embiggen(t); } while (blockid >= t->block_count) { uintptr_t index = mmu_allocate_a_frame(); tmpfs_total_blocks++; if (create == 2) { memset((char*)mmu_map_from_physical(index << 12), 0, BLOCKSIZE); } t->blocks[t->block_count] = index; t->block_count += 1; } } else { if (blockid >= t->block_count) { printf("tmpfs: not enough blocks?\n"); return NULL; } } return (char *)mmu_map_from_physical(t->blocks[blockid] << 12); } static ssize_t read_tmpfs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct tmpfs_file * t = (struct tmpfs_file *)(node->inode); spin_lock(t->lock); t->atime = now(); uint64_t end; if ((size_t)offset + size > t->length) { end = t->length; } else { end = offset + size; } uint64_t start_block = offset / BLOCKSIZE; uint64_t end_block = end / BLOCKSIZE; uint64_t end_size = end - end_block * BLOCKSIZE; uint64_t size_to_read = end - offset; if (start_block == end_block && (size_t)offset == end) { spin_unlock(t->lock); return 0; } if (start_block == end_block) { void *buf = tmpfs_file_getset_block(t, start_block, 0); memcpy(buffer, (uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % BLOCKSIZE)), size_to_read); spin_unlock(t->lock); return size_to_read; } else { uint64_t block_offset; uint64_t blocks_read = 0; for (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) { if (block_offset == start_block) { void *buf = tmpfs_file_getset_block(t, block_offset, 0); memcpy(buffer, (uint8_t *)(((uint64_t)buf) + ((uintptr_t)offset % BLOCKSIZE)), BLOCKSIZE - (offset % BLOCKSIZE)); } else { void *buf = tmpfs_file_getset_block(t, block_offset, 0); memcpy(buffer + BLOCKSIZE * blocks_read - (offset % BLOCKSIZE), buf, BLOCKSIZE); } } if (end_size) { void *buf = tmpfs_file_getset_block(t, end_block, 0); memcpy(buffer + BLOCKSIZE * blocks_read - (offset % BLOCKSIZE), buf, end_size); } } spin_unlock(t->lock); return size_to_read; } static ssize_t write_tmpfs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct tmpfs_file * t = (struct tmpfs_file *)(node->inode); spin_lock(t->lock); t->atime = now(); t->mtime = t->atime; uint64_t end; if ((size_t)offset + size > t->length) { t->length = offset + size; } end = offset + size; uint64_t start_block = offset / BLOCKSIZE; uint64_t end_block = end / BLOCKSIZE; uint64_t end_size = end - end_block * BLOCKSIZE; uint64_t size_to_read = end - offset; if (start_block == end_block) { void *buf = tmpfs_file_getset_block(t, start_block, 1); memcpy((uint8_t *)(((uint64_t)buf) + ((uintptr_t)offset % BLOCKSIZE)), buffer, size_to_read); spin_unlock(t->lock); return size_to_read; } else { uint64_t block_offset; uint64_t blocks_read = 0; for (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) { if (block_offset == start_block) { void *buf = tmpfs_file_getset_block(t, block_offset, 1); memcpy((uint8_t *)(((uint64_t)buf) + ((uintptr_t)offset % BLOCKSIZE)), buffer, BLOCKSIZE - (offset % BLOCKSIZE)); } else { void *buf = tmpfs_file_getset_block(t, block_offset, 1); memcpy(buf, buffer + BLOCKSIZE * blocks_read - (offset % BLOCKSIZE), BLOCKSIZE); } } if (end_size) { void *buf = tmpfs_file_getset_block(t, end_block, 1); memcpy(buf, buffer + BLOCKSIZE * blocks_read - (offset % BLOCKSIZE), end_size); } } spin_unlock(t->lock); return size_to_read; } static int chmod_tmpfs(fs_node_t * node, int mode) { struct tmpfs_file * t = (struct tmpfs_file *)(node->inode); /* XXX permissions */ t->mask = mode; return 0; } static int chown_tmpfs(fs_node_t * node, int uid, int gid) { struct tmpfs_file * t = (struct tmpfs_file *)(node->inode); spin_lock(t->lock); if (uid != -1) t->uid = uid; if (gid != -1) t->gid = gid; spin_unlock(t->lock); return 0; } static int truncate_tmpfs(fs_node_t * node, size_t size) { struct tmpfs_file * t = (struct tmpfs_file *)(node->inode); spin_lock(t->lock); if (size == t->length) goto _exit_truncate; uint64_t old_end_block = (t->length / BLOCKSIZE); uint64_t old_end_size = t->length - old_end_block * BLOCKSIZE; uint64_t old_blocks = old_end_block + !!old_end_size; uint64_t new_end_block = (size / BLOCKSIZE); uint64_t new_end_size = size - new_end_block * BLOCKSIZE; uint64_t new_blocks = new_end_block + !!new_end_size; /* Is the target size bigger or smaller? */ if (size > t->length) { if (old_end_block == new_end_block) { char *buf = tmpfs_file_getset_block(t, old_end_block, 0); memset(buf + old_end_size, 0, new_end_size - old_end_size); } else { tmpfs_file_getset_block(t, new_end_block, 2); char *buf = tmpfs_file_getset_block(t, old_end_block, 0); memset(buf + old_end_size, 0, BLOCKSIZE - old_end_size); } t->length = size; goto _exit_truncate; } if (size == 0) { for (size_t i = 0; i < t->block_count; ++i) { mmu_frame_release((uintptr_t)t->blocks[i] * 0x1000); tmpfs_total_blocks--; t->blocks[i] = 0; } t->block_count = 0; t->length = 0; goto _exit_truncate; } /* Size is less than current but > 0 */ if (new_blocks < old_blocks) { for (uint64_t i = new_blocks; i < old_blocks; ++i) { mmu_frame_release((uintptr_t)t->blocks[i] * 0x1000); tmpfs_total_blocks--; t->blocks[i] = 0; } t->block_count = new_blocks; } t->length = size; _exit_truncate: t->mtime = node->atime; spin_unlock(t->lock); return 0; } static void open_tmpfs(fs_node_t * node, unsigned int flags) { struct tmpfs_file * t = (struct tmpfs_file *)(node->inode); t->atime = now(); } static fs_node_t * tmpfs_from_file(struct tmpfs_file * t) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); spin_lock(t->lock); memset(fnode, 0x00, sizeof(fs_node_t)); strcpy(fnode->name, t->name); fnode->inode = (uintptr_t)t; fnode->mask = t->mask; fnode->uid = t->uid; fnode->gid = t->gid; fnode->atime = t->atime; fnode->ctime = t->ctime; fnode->mtime = t->mtime; fnode->flags = FS_FILE; fnode->read = read_tmpfs; fnode->write = write_tmpfs; fnode->open = open_tmpfs; fnode->close = NULL; fnode->readdir = NULL; fnode->finddir = NULL; fnode->chmod = chmod_tmpfs; fnode->chown = chown_tmpfs; fnode->length = t->length; fnode->truncate = truncate_tmpfs; fnode->nlink = 1; fnode->mount = t->mount; fnode->device = t->mount; spin_unlock(t->lock); return fnode; } static fs_node_t * tmpfs_from_link(struct tmpfs_file * t) { fs_node_t * fnode = tmpfs_from_file(t); fnode->flags |= FS_SYMLINK; fnode->readlink = readlink_tmpfs; fnode->read = NULL; fnode->write = NULL; fnode->create = NULL; fnode->mkdir = NULL; fnode->readdir = NULL; fnode->finddir = NULL; return fnode; } static struct dirent * readdir_tmpfs(fs_node_t *node, uint64_t index) { struct tmpfs_dir * d = (struct tmpfs_dir *)node->inode; uint64_t i = 0; if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } index -= 2; if (index >= d->files->length) return NULL; foreach(f, d->files) { if (i == index) { struct tmpfs_file * t = (struct tmpfs_file *)f->value; struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = (uint64_t)t; strcpy(out->d_name, t->name); return out; } else { ++i; } } return NULL; } static fs_node_t * finddir_tmpfs(fs_node_t * node, char * name) { if (!name) return NULL; struct tmpfs_dir * d = (struct tmpfs_dir *)node->inode; spin_lock(d->lock); foreach(f, d->files) { struct tmpfs_file * t = (struct tmpfs_file *)f->value; if (!strcmp(name, t->name)) { fs_node_t * out = NULL; switch (t->type) { case TMPFS_TYPE_FILE: out = tmpfs_from_file(t); break; case TMPFS_TYPE_LINK: out = tmpfs_from_link(t); break; case TMPFS_TYPE_DIR: out = tmpfs_from_dir((struct tmpfs_dir *)t); break; } spin_unlock(d->lock); return out; } } spin_unlock(d->lock); return NULL; } static int try_free_dir(struct tmpfs_dir * d) { spin_lock(d->lock); if (d->files && d->files->length != 0) { spin_unlock(d->lock); return 1; } free(d->files); spin_unlock(d->lock); return 0; } static int unlink_tmpfs(fs_node_t * node, char * name) { struct tmpfs_dir * d = (struct tmpfs_dir *)node->inode; int i = -1, j = 0; spin_lock(d->lock); foreach(f, d->files) { struct tmpfs_file * t = (struct tmpfs_file *)f->value; if (!strcmp(name, t->name)) { if (t->type == TMPFS_TYPE_DIR) { if (try_free_dir((void*)t)) { spin_unlock(d->lock); return -ENOTEMPTY; } } else { tmpfs_file_free(t); } free(t); i = j; break; } j++; } if (i >= 0) { list_remove(d->files, i); } else { spin_unlock(d->lock); return -ENOENT; } spin_unlock(d->lock); return 0; } static int create_tmpfs(fs_node_t *parent, char *name, mode_t permission) { if (!name) return -EINVAL; struct tmpfs_dir * d = (struct tmpfs_dir *)parent->inode; spin_lock(d->lock); foreach(f, d->files) { struct tmpfs_file * t = (struct tmpfs_file *)f->value; if (!strcmp(name, t->name)) { spin_unlock(d->lock); return -EEXIST; /* Already exists */ } } spin_unlock(d->lock); struct tmpfs_file * t = tmpfs_file_new(name); t->mount = parent->mount; t->mask = permission; t->uid = this_core->current_process->user; t->gid = this_core->current_process->user_group; spin_lock(d->lock); list_insert(d->files, t); spin_unlock(d->lock); return 0; } static int mkdir_tmpfs(fs_node_t * parent, char * name, mode_t permission) { if (!name) return -EINVAL; if (!strlen(name)) return -EINVAL; struct tmpfs_dir * d = (struct tmpfs_dir *)parent->inode; spin_lock(d->lock); foreach(f, d->files) { struct tmpfs_file * t = (struct tmpfs_file *)f->value; if (!strcmp(name, t->name)) { spin_unlock(d->lock); return -EEXIST; /* Already exists */ } } spin_unlock(d->lock); /* Need both exec and write on the parent to create a new entry */ if (!has_permission(parent, 02) || !has_permission(parent, 01)) { return -EACCES; } struct tmpfs_dir * out = tmpfs_dir_new(name, d); out->mask = permission; out->uid = this_core->current_process->user; out->gid = this_core->current_process->user; spin_lock(d->lock); list_insert(d->files, out); spin_unlock(d->lock); return 0; } static int path_comp(const char * a, const char * b) { while (*a && *b && *a != '/' && *b != '/') { if (*a != *b) return 1; a++; b++; } if ((*a == '/' || !*a) && (*b == '/' || !*b)) return 0; return 1; } static int endswith(const char * str, char ch) { size_t len = strlen(str); if (len > 1 && str[len-1] == ch) return 1; return 0; } static char * path_dup(const char * path) { const char * n = path; while (*n && *n != '/') n++; char * out = malloc(n - path + 1); memcpy(out, path, n - path); out[n-path] = '\0'; return out; } static 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) { /* src_dir and dest_dir are definitely from us, no worries there */ int ret = 0; struct tmpfs_dir * root = (struct tmpfs_dir*)mount_root->inode; spin_lock(root->nest_lock); struct tmpfs_dir * ds = (struct tmpfs_dir *)src_dir->inode; spin_lock(ds->lock); /* First, get the source file */ struct tmpfs_file * src_file = NULL; node_t * src_node = NULL; foreach(f, ds->files) { struct tmpfs_file * t = (struct tmpfs_file *)f->value; if (!path_comp(src_name, t->name)) { src_file = t; src_node = f; break; } } if (!src_file) { ret = -ENOENT; goto _cleanup_src; } if (src_file->type != TMPFS_TYPE_DIR && endswith(src_name, '/')) { /* Source ended with trailing slashes, but was not a directory. */ ret = -ENOTDIR; goto _cleanup_src; } struct tmpfs_dir * dd = (struct tmpfs_dir *)dest_dir->inode; if (dd != ds) spin_lock(dd->lock); struct tmpfs_file * dest_file = NULL; node_t * dest_node = NULL; foreach(f, dd->files) { struct tmpfs_file * t = (struct tmpfs_file *)f->value; if (!path_comp(dest_name, t->name)) { dest_file = t; dest_node = f; break; } } if (dest_file && dest_file->type != TMPFS_TYPE_DIR && endswith(dest_name, '/')) { /* Destination ended with trailing slashes, but was not a directory. */ ret = -ENOTDIR; goto _cleanup; } if (!dest_file) { if (endswith(dest_name,'/') && src_file->type != TMPFS_TYPE_DIR) { /* Destination did not exist, ended with trailing slashes, but the source was not a directory. */ ret = -ENOTDIR; goto _cleanup; } char * old_name = src_file->name; src_file->name = path_dup(dest_name); free(old_name); list_insert(dd->files, src_file); list_delete(ds->files, src_node); } else if (src_file == dest_file) { /* Do nothing */ } else { if (dest_file->type == TMPFS_TYPE_DIR) { struct tmpfs_dir * dest = (struct tmpfs_dir*)dest_file; if (dest->files && dest->files->length) { /* Destination is not empty */ ret = -ENOTEMPTY; goto _cleanup; } if (src_file->type != TMPFS_TYPE_DIR) { /* Source is not a directory but destination is */ ret = -EISDIR; goto _cleanup; } } else if (src_file->type == TMPFS_TYPE_DIR) { /* Source is a directory, but destination is not */ ret = -ENOTDIR; goto _cleanup; } /* Rename src */ char * old_name = src_file->name; src_file->name = path_dup(dest_name); free(old_name); list_delete(ds->files, src_node); dest_node->value = src_file; /* Unlink the original destination file */ if (dest_file->type == TMPFS_TYPE_DIR) { try_free_dir((void*)dest_file); } else { tmpfs_file_free(dest_file); } } _cleanup: if (dd != ds) spin_unlock(dd->lock); _cleanup_src: spin_unlock(ds->lock); spin_unlock(root->nest_lock); return ret; } static fs_node_t * tmpfs_from_dir(struct tmpfs_dir * d) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); spin_lock(d->lock); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "tmp"); fnode->mount = d->mount; fnode->device = d->mount; fnode->mask = d->mask; fnode->uid = d->uid; fnode->gid = d->gid; fnode->inode = (uintptr_t)d; fnode->atime = d->atime; fnode->mtime = d->mtime; fnode->ctime = d->ctime; fnode->flags = FS_DIRECTORY; fnode->read = NULL; fnode->write = NULL; fnode->open = NULL; fnode->close = NULL; fnode->readdir = readdir_tmpfs; fnode->finddir = finddir_tmpfs; fnode->create = create_tmpfs; fnode->unlink = unlink_tmpfs; fnode->mkdir = mkdir_tmpfs; fnode->nlink = 1; /* should be "number of children that are directories + 1" */ fnode->symlink = symlink_tmpfs; fnode->chown = chown_tmpfs; fnode->chmod = chmod_tmpfs; fnode->rename = rename_tmpfs; spin_unlock(d->lock); return fnode; } fs_node_t * tmpfs_create(char * name) { struct tmpfs_dir * tmpfs_root = tmpfs_dir_new(name, NULL); tmpfs_root->mask = 0777; tmpfs_root->uid = 0; tmpfs_root->gid = 0; fs_node_t * out = tmpfs_from_dir(tmpfs_root); tmpfs_root->mount = out; out->mount = out; out->device = out; return out; } fs_node_t * tmpfs_mount(const char * device, const char * mount_path) { char * arg = strdup(device); char * argv[10]; int argc = tokenize(arg, ",", argv); fs_node_t * fs = tmpfs_create(argv[0]); if (argc > 1) { if (strlen(argv[1]) < 3) { printf("tmpfs: ignoring bad permission option for tmpfs\n"); } else { int mode = ((argv[1][0] - '0') << 6) | ((argv[1][1] - '0') << 3) | ((argv[1][2] - '0') << 0); fs->mask = mode; } } //free(arg); return fs; } static void tmpfs_func(fs_node_t * node) { procfs_printf(node, "UsedBlocks:\t%zd\n", tmpfs_total_blocks); } static struct procfs_entry tmpfs_entry = { 0, "tmpfs", tmpfs_func, }; void tmpfs_register_init(void) { vfs_register("tmpfs", tmpfs_mount); procfs_install(&tmpfs_entry); } ================================================ FILE: kernel/vfs/tty.c ================================================ /** * @file kernel/vfs/tty.c * @brief PTY driver. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TTY_BUFFER_SIZE 4096 #define MIN(a,b) ((a) < (b) ? (a) : (b)) extern void ptr_validate(void * ptr, const char * syscall); #define validate(o) ptr_validate(o,"ioctl") static int _pty_counter = 0; static hashmap_t * _pty_index = NULL; static fs_node_t * _pty_dir = NULL; static fs_node_t * _dev_tty = NULL; static void pty_write_in(pty_t * pty, uint8_t c) { ring_buffer_write(pty->in, 1, &c); } static void pty_write_out(pty_t * pty, uint8_t c) { ring_buffer_write(pty->out, 1, &c); } #define IN(character) pty->write_in(pty, (uint8_t)character) #define OUT(character) pty->write_out(pty, (uint8_t)character) static void dump_input_buffer(pty_t * pty) { char * c = pty->canon_buffer; while (pty->canon_buflen > 0) { IN(*c); pty->canon_buflen--; c++; } } static void clear_input_buffer(pty_t * pty) { pty->canon_buflen = 0; pty->canon_buffer[0] = '\0'; } #define output_process_slave tty_output_process_slave #define output_process tty_output_process #define input_process tty_input_process void tty_output_process_slave(pty_t * pty, uint8_t c) { if (!(pty->tios.c_oflag & OPOST)) { OUT(c); return; } if (c == '\n' && (pty->tios.c_oflag & ONLCR)) { c = '\n'; OUT(c); c = '\r'; OUT(c); return; } if (c == '\r' && (pty->tios.c_oflag & ONLRET)) { return; } if (c >= 'a' && c <= 'z' && (pty->tios.c_oflag & OLCUC)) { c = c + 'A' - 'a'; OUT(c); return; } OUT(c); } void tty_output_process(pty_t * pty, uint8_t c) { output_process_slave(pty, c); } static int is_control(int c) { return c < ' ' || c == 0x7F; } static void erase_one(pty_t * pty, int erase) { if (pty->canon_buflen > 0) { /* How many do we backspace? */ int vwidth = 1; pty->canon_buflen--; if (is_control(pty->canon_buffer[pty->canon_buflen])) { /* Erase ^@ */ vwidth = 2; } pty->canon_buffer[pty->canon_buflen] = '\0'; if (pty->tios.c_lflag & ECHO) { if (erase) { for (int i = 0; i < vwidth; ++i) { output_process(pty, '\010'); output_process(pty, ' '); output_process(pty, '\010'); } } } } } void tty_input_process(pty_t * pty, uint8_t c) { if (pty->next_is_verbatim) { pty->next_is_verbatim = 0; if (pty->canon_buflen < pty->canon_bufsize) { pty->canon_buffer[pty->canon_buflen] = c; pty->canon_buflen++; } if (pty->tios.c_lflag & ECHO) { if ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) { output_process(pty, '^'); output_process(pty, ('@'+c) % 128); } else { output_process(pty, c); } } return; } if (pty->tios.c_lflag & ISIG) { int sig = -1; if (c == pty->tios.c_cc[VINTR]) { sig = SIGINT; } else if (c == pty->tios.c_cc[VQUIT]) { sig = SIGQUIT; } else if (c == pty->tios.c_cc[VSUSP]) { sig = SIGTSTP; } /* VSUSP */ if (sig != -1) { if (pty->tios.c_lflag & ECHO) { if ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } else { output_process(pty, c); } } clear_input_buffer(pty); if (pty->fg_proc) { group_send_signal(pty->fg_proc, sig, 1); } return; } } #if 0 if (pty->tios.c_lflag & IXON ) { /* VSTOP, VSTART */ } #endif /* ISTRIP: Strip eighth bit */ if (pty->tios.c_iflag & ISTRIP) { c &= 0x7F; } /* IGNCR: Ignore carriage return. */ if ((pty->tios.c_iflag & IGNCR) && c == '\r') { return; } if ((pty->tios.c_iflag & INLCR) && c == '\n') { /* INLCR: Translate NL to CR. */ c = '\r'; } else if ((pty->tios.c_iflag & ICRNL) && c == '\r') { /* ICRNL: Convert carriage return. */ c = '\n'; } if ((pty->tios.c_iflag & IUCLC) && (c >= 'A' && c <= 'Z')) { c = c - 'A' + 'a'; } if (pty->tios.c_lflag & ICANON) { if (c == pty->tios.c_cc[VLNEXT] && (pty->tios.c_lflag & IEXTEN)) { pty->next_is_verbatim = 1; output_process(pty, '^'); output_process(pty, '\010'); return; } if (c == pty->tios.c_cc[VKILL]) { while (pty->canon_buflen > 0) { erase_one(pty, pty->tios.c_lflag & ECHOK); } if ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOK)) { if ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } else { output_process(pty, c); } } return; } if (c == pty->tios.c_cc[VERASE]) { /* Backspace */ erase_one(pty, pty->tios.c_lflag & ECHOE); if ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOE)) { if ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } else { output_process(pty, c); } } return; } if (c == pty->tios.c_cc[VWERASE] && (pty->tios.c_lflag & IEXTEN)) { while (pty->canon_buflen && pty->canon_buffer[pty->canon_buflen-1] == ' ') { erase_one(pty, pty->tios.c_lflag & ECHOE); } while (pty->canon_buflen && pty->canon_buffer[pty->canon_buflen-1] != ' ') { erase_one(pty, pty->tios.c_lflag & ECHOE); } if ((pty->tios.c_lflag & ECHO) && ! (pty->tios.c_lflag & ECHOE)) { if ((pty->tios.c_lflag & ECHOCTL) && is_control(c)) { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } else { output_process(pty, c); } } return; } if (c == pty->tios.c_cc[VEOF]) { if (pty->canon_buflen) { dump_input_buffer(pty); } else { ring_buffer_eof(pty->in); } return; } if (pty->canon_buflen < pty->canon_bufsize) { pty->canon_buffer[pty->canon_buflen] = c; pty->canon_buflen++; } if (pty->tios.c_lflag & ECHO) { if ((pty->tios.c_lflag & ECHOCTL) && is_control(c) && c != '\n') { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } else { output_process(pty, c); } } if (c == '\n' || (pty->tios.c_cc[VEOL] && c == pty->tios.c_cc[VEOL])) { if (!(pty->tios.c_lflag & ECHO) && (pty->tios.c_lflag & ECHONL)) { output_process(pty, c); } pty->canon_buffer[pty->canon_buflen-1] = c; dump_input_buffer(pty); return; } return; } else if (pty->tios.c_lflag & ECHO) { if ((pty->tios.c_lflag & ECHOCTL) && is_control(c) && c != '\n') { output_process(pty, '^'); output_process(pty, ('@' + c) % 128); } else { output_process(pty, c); } } IN(c); } static void tty_fill_name(pty_t * pty, char * out) { ((char*)out)[0] = '\0'; snprintf((char*)out, 100, "/dev/pts/%zd", pty->name); } int pty_ioctl(pty_t * pty, unsigned long request, void * argp) { switch (request) { case IOCTLDTYPE: /* * This is a special toaru-specific call to get a simple * integer that describes the kind of device this is. * It's more specific than just "character device" or "file", * but for here we just need to say we're a TTY. */ return IOCTL_DTYPE_TTY; case IOCTLTTYNAME: if (!argp) return -EINVAL; validate(argp); pty->fill_name(pty, argp); return 0; case IOCTLTTYLOGIN: /* Set the user id of the login user */ if (this_core->current_process->user != 0) return -EPERM; if (!argp) return -EINVAL; validate(argp); pty->slave->uid = *(int*)argp; pty->master->uid = *(int*)argp; return 0; case TIOCSWINSZ: if (!argp) return -EINVAL; validate(argp); memcpy(&pty->size, argp, sizeof(struct winsize)); if (pty->fg_proc) { group_send_signal(pty->fg_proc, SIGWINCH, 1); } return 0; case TIOCGWINSZ: if (!argp) return -EINVAL; validate(argp); memcpy(argp, &pty->size, sizeof(struct winsize)); return 0; case TCGETS: if (!argp) return -EINVAL; validate(argp); memcpy(argp, &pty->tios, sizeof(struct termios)); return 0; case TIOCSPGRP: if (!argp) return -EINVAL; validate(argp); pty->fg_proc = *(pid_t *)argp; return 0; case TIOCGPGRP: if (!argp) return -EINVAL; validate(argp); *(pid_t *)argp = pty->fg_proc; return 0; case TIOCSCTTY: /* If this is already the control session, quietly ignore. */ if (this_core->current_process->session == this_core->current_process->id && pty->ct_proc == this_core->current_process->session) { return 0; } /* If we aren't a session leader, we can't do this. */ if (this_core->current_process->session != this_core->current_process->id) { return -EPERM; } /* If there's already a control session, only root can steal control, and only if *argp is 1 * (on Linux, that's "if argp is 1", but we kinda messed this up by checking ioctl argp stuff * for bounds validity in the system call layer, so instead we use a pointer to 1... */ if (pty->ct_proc && (!argp || (*(int*)argp != 1) || this_core->current_process->user != 0)) { return -EPERM; } pty->ct_proc = this_core->current_process->session; return 0; case TCSETS: case TCSETSW: if (!argp) return -EINVAL; validate(argp); /* TODO wait on output for SETSW */ if (!(((struct termios *)argp)->c_lflag & ICANON) && (pty->tios.c_lflag & ICANON)) { /* Switch out of canonical mode, the dump the input buffer */ dump_input_buffer(pty); } goto tcset_common; case TCSETSF: clear_input_buffer(pty); ring_buffer_discard(pty->in); tcset_common: memcpy(&pty->tios, argp, sizeof(struct termios)); return 0; default: return -EINVAL; } } ssize_t read_pty_master(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { pty_t * pty = (pty_t *)node->device; /* Standard pipe read */ return ring_buffer_read(pty->out, size, buffer); } ssize_t write_pty_master(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { pty_t * pty = (pty_t *)node->device; size_t l = 0; for (uint8_t * c = buffer; l < size; ++c, ++l) { input_process(pty, *c); } return l; } void open_pty_master(fs_node_t * node, unsigned int flags) { return; } void close_pty_master(fs_node_t * node) { return; } static int ignoring(int sig) { if (this_core->current_process->blocked_signals & (1ULL << sig)) return 1; if (this_core->current_process->signals[sig].handler == 1) return 1; return 0; } ssize_t read_pty_slave(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { pty_t * pty = (pty_t *)node->device; /* If this process *is* part of this tty's session, but is NOT in the foreground job * and it tries to read from the TTY, then send it SIGTTIN - but only if it's not * ignoring it. If it is ignoring, make the write fail with EIO. */ if (pty->ct_proc == this_core->current_process->session && pty->fg_proc && this_core->current_process->job != pty->fg_proc) { if (ignoring(SIGTTIN)) return -EIO; group_send_signal(this_core->current_process->job, SIGTTIN, 1); return -ERESTARTSYS; } if (pty->tios.c_lflag & ICANON) { return ring_buffer_read(pty->in, size, buffer); } else { if (pty->tios.c_cc[VMIN] == 0) { return ring_buffer_read(pty->in, size, buffer); } else { ssize_t c = 0; ssize_t vmin = MIN(pty->tios.c_cc[VMIN], size); while (c < vmin) { ssize_t r = ring_buffer_read(pty->in, size - c, buffer + c); if (r < 0) return c ? c : r; c += r; } return c; } } } ssize_t write_pty_slave(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { pty_t * pty = (pty_t *)node->device; if (pty->tios.c_lflag & TOSTOP) { /* If TOSTOP is enabled and this process *is* part of this tty's session but * is NOT in the foreground job, then send the whole job SIGTTOU - but only * if we weren't ignoring SIGTTOU ourselves. If we were ignoring it, then * the write can continue without issue. */ if (pty->ct_proc == this_core->current_process->session && pty->fg_proc && this_core->current_process->job != pty->fg_proc) { if (!ignoring(SIGTTOU)) { group_send_signal(this_core->current_process->job, SIGTTOU, 1); return -ERESTARTSYS; } } } size_t l = 0; for (uint8_t * c = buffer; l < size; ++c, ++l) { output_process_slave(pty, *c); } return l; } void open_pty_slave(fs_node_t * node, unsigned int flags) { return; } void close_pty_slave(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; if (pty->name) { hashmap_remove(_pty_index, (void*)pty->name); } return; } /* * These are separate functions just in case I ever feel the need to do * things differently in the slave or master. */ int ioctl_pty_master(fs_node_t * node, unsigned long request, void * argp) { pty_t * pty = (pty_t *)node->device; return pty_ioctl(pty, request, argp); } int ioctl_pty_slave(fs_node_t * node, unsigned long request, void * argp) { pty_t * pty = (pty_t *)node->device; return pty_ioctl(pty, request, argp); } int pty_available_input(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; return ring_buffer_unread(pty->in); } int pty_available_output(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; return ring_buffer_unread(pty->out); } static int check_pty_master(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; if (ring_buffer_unread(pty->out) > 0) { return 0; } return 1; } static int check_pty_slave(fs_node_t * node) { pty_t * pty = (pty_t *)node->device; if (ring_buffer_unread(pty->in) > 0) { return 0; } return 1; } static int wait_pty_master(fs_node_t * node, void * process) { pty_t * pty = (pty_t *)node->device; ring_buffer_select_wait(pty->out, process); return 0; } static int wait_pty_slave(fs_node_t * node, void * process) { pty_t * pty = (pty_t *)node->device; ring_buffer_select_wait(pty->in, process); return 0; } fs_node_t * pty_master_create(pty_t * pty) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->name[0] = '\0'; snprintf(fnode->name, 100, "pty master"); fnode->uid = this_core->current_process->user; fnode->gid = this_core->current_process->user_group; fnode->mask = 0666; fnode->flags = FS_PIPE; fnode->read = read_pty_master; fnode->write = write_pty_master; fnode->open = open_pty_master; fnode->close = close_pty_master; fnode->selectcheck = check_pty_master; fnode->selectwait = wait_pty_master; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = ioctl_pty_master; fnode->get_size = pty_available_output; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); fnode->device = pty; return fnode; } fs_node_t * pty_slave_create(pty_t * pty) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->name[0] = '\0'; snprintf(fnode->name, 100, "pty slave"); fnode->uid = this_core->current_process->user; fnode->gid = this_core->current_process->user_group; fnode->mask = 0620; fnode->flags = FS_CHARDEVICE; fnode->read = read_pty_slave; fnode->write = write_pty_slave; fnode->open = open_pty_slave; fnode->close = close_pty_slave; fnode->selectcheck = check_pty_slave; fnode->selectwait = wait_pty_slave; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = ioctl_pty_slave; fnode->get_size = pty_available_input; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); fnode->device = pty; return fnode; } static int isatty(fs_node_t * node) { if (!node) return 0; if (!node->ioctl) return 0; return ioctl_fs(node, IOCTLDTYPE, NULL) == IOCTL_DTYPE_TTY; } static ssize_t readlink_dev_tty(fs_node_t * node, char * buf, size_t size) { pty_t * pty = NULL; for (unsigned int i = 0; i < ((this_core->current_process->fds->length < 3) ? this_core->current_process->fds->length : 3); ++i) { if (isatty(this_core->current_process->fds->entries[i])) { pty = (pty_t *)this_core->current_process->fds->entries[i]->device; break; } } char tmp[30]; size_t req; if (!pty) { snprintf(tmp, 100, "/dev/null"); } else { pty->fill_name(pty, tmp); } req = strlen(tmp) + 1; if (size < req) { memcpy(buf, tmp, size); buf[size-1] = '\0'; return size-1; } if (size > req) size = req; memcpy(buf, tmp, size); return size-1; } static fs_node_t * create_dev_tty(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "tty"); fnode->mask = 0777; fnode->uid = 0; fnode->gid = 0; fnode->flags = FS_FILE | FS_SYMLINK; fnode->readlink = readlink_dev_tty; fnode->length = 1; fnode->nlink = 1; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); return fnode; } static struct dirent * readdir_pty(fs_node_t *node, unsigned long index) { if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } index -= 2; pty_t * out_pty = NULL; list_t * values = hashmap_values(_pty_index); foreach(node, values) { if (index == 0) { out_pty = node->value; break; } index--; } list_free(values); if (out_pty) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = out_pty->name; out->d_name[0] = '\0'; snprintf(out->d_name, 100, "%zd", out_pty->name); return out; } else { return NULL; } } static fs_node_t * finddir_pty(fs_node_t * node, char * name) { if (!name) return NULL; if (strlen(name) < 1) return NULL; intptr_t c = 0; for (intptr_t i = 0; name[i]; ++i) { if (name[i] < '0' || name[i] > '9') { return NULL; } c = c * 10 + name[i] - '0'; } pty_t * _pty = hashmap_get(_pty_index, (void*)c); if (!_pty) { return NULL; } return _pty->slave; } static fs_node_t * create_pty_dir(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "pty"); fnode->mask = 0555; fnode->uid = 0; fnode->gid = 0; fnode->flags = FS_DIRECTORY; fnode->read = NULL; fnode->write = NULL; fnode->open = NULL; fnode->close = NULL; fnode->readdir = readdir_pty; fnode->finddir = finddir_pty; fnode->nlink = 1; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); return fnode; } void pty_install(void) { _pty_index = hashmap_create_int(10); _pty_dir = create_pty_dir(); _dev_tty = create_dev_tty(); vfs_mount("/dev/pts", _pty_dir, "pts", ""); vfs_mount("/dev/tty", _dev_tty, "devtty", ""); } pty_t * pty_new(struct winsize * size, int index) { if (!_pty_index) { pty_install(); } pty_t * pty = malloc(sizeof(pty_t)); pty->next_is_verbatim = 0; /* stdin linkage; characters from terminal → PTY slave */ pty->in = ring_buffer_create(TTY_BUFFER_SIZE); pty->out = ring_buffer_create(TTY_BUFFER_SIZE); /* Master endpoint - writes go to stdin, reads come from stdout */ pty->master = pty_master_create(pty); /* Slave endpoint, reads come from stdin, writes go to stdout */ pty->slave = pty_slave_create(pty); /* tty name */ pty->name = index; pty->fill_name = tty_fill_name; pty->write_in = pty_write_in; pty->write_out = pty_write_out; if (index) { hashmap_set(_pty_index, (void*)pty->name, pty); } if (size) { memcpy(&pty->size, size, sizeof(struct winsize)); } else { /* Sane defaults */ pty->size.ws_row = 25; pty->size.ws_col = 80; } /* Controlling and foreground processes are set to 0 by default */ pty->ct_proc = 0; pty->fg_proc = 0; pty->tios.c_iflag = ICRNL | BRKINT; pty->tios.c_oflag = ONLCR | OPOST; pty->tios.c_lflag = ECHO | ECHOE | ECHOK | ICANON | ISIG | IEXTEN | ECHOCTL; pty->tios.c_cflag = CREAD | CS8 | B38400; pty->tios.c_cc[VEOF] = 4; /* ^D */ pty->tios.c_cc[VEOL] = 0; /* Not set */ pty->tios.c_cc[VERASE] = 0x7f; /* ^? */ pty->tios.c_cc[VINTR] = 3; /* ^C */ pty->tios.c_cc[VKILL] = 21; /* ^U */ pty->tios.c_cc[VMIN] = 1; pty->tios.c_cc[VQUIT] = 28; /* ^\ */ pty->tios.c_cc[VSTART] = 17; /* ^Q */ pty->tios.c_cc[VSTOP] = 19; /* ^S */ pty->tios.c_cc[VSUSP] = 26; /* ^Z */ pty->tios.c_cc[VTIME] = 0; pty->tios.c_cc[VLNEXT] = 22; /* ^V */ pty->tios.c_cc[VWERASE] = 23; /* ^W */ pty->canon_buffer = malloc(TTY_BUFFER_SIZE); pty->canon_bufsize = TTY_BUFFER_SIZE-2; pty->canon_buflen = 0; return pty; } int pty_create(void *size, fs_node_t ** fs_master, fs_node_t ** fs_slave) { pty_t * pty = pty_new(size, ++_pty_counter); *fs_master = pty->master; *fs_slave = pty->slave; return 0; } ================================================ FILE: kernel/vfs/unixpipe.c ================================================ /** * @file kernel/vfs/unixpipe.c * @brief Implementation of Unix pipes. * * Provides for unidirectional communication between processes. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #define UNIX_PIPE_BUFFER 4096 struct unix_pipe { fs_node_t * read_end; fs_node_t * write_end; volatile int read_closed; volatile int write_closed; ring_buffer_t * buffer; }; static void close_complete(struct unix_pipe * self) { ring_buffer_destroy(self->buffer); } static ssize_t read_unixpipe(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { struct unix_pipe * self = node->device; if (self->write_closed && !ring_buffer_unread(self->buffer)) { return 0; } return ring_buffer_read(self->buffer, size, buffer); } static ssize_t write_unixpipe(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { struct unix_pipe * self = node->device; if (self->read_closed) { send_signal(this_core->current_process->id, SIGPIPE, 1); return -EPIPE; } return ring_buffer_write(self->buffer, size, buffer); } static void close_read_pipe(fs_node_t * node) { struct unix_pipe * self = node->device; spin_lock(self->buffer->lock); self->read_closed = 1; if (!self->write_closed) { ring_buffer_interrupt(self->buffer); } spin_unlock(self->buffer->lock); } static void close_write_pipe(fs_node_t * node) { struct unix_pipe * self = node->device; spin_lock(self->buffer->lock); self->write_closed = 1; if (!self->read_closed) { ring_buffer_interrupt(self->buffer); if (!ring_buffer_unread(self->buffer)) { ring_buffer_alert_waiters(self->buffer); } } spin_unlock(self->buffer->lock); } static int check_pipe(fs_node_t * node) { struct unix_pipe * self = node->device; if (ring_buffer_unread(self->buffer) > 0) { return 0; } if (self->write_closed) return 0; return 1; } static int wait_pipe(fs_node_t * node, void * process) { struct unix_pipe * self = node->device; ring_buffer_select_wait(self->buffer, process); return 0; } int make_unix_pipe(fs_node_t ** pipes) { size_t size = UNIX_PIPE_BUFFER; pipes[0] = malloc(sizeof(fs_node_t)); pipes[1] = malloc(sizeof(fs_node_t)); memset(pipes[0], 0, sizeof(fs_node_t)); memset(pipes[1], 0, sizeof(fs_node_t)); snprintf(pipes[0]->name, 100, "[pipe:read]"); snprintf(pipes[1]->name, 100, "[pipe:write]"); pipes[0]->mask = 0666; pipes[1]->mask = 0666; pipes[0]->flags = FS_PIPE; pipes[1]->flags = FS_PIPE; pipes[0]->read = read_unixpipe; pipes[1]->write = write_unixpipe; pipes[0]->close = close_read_pipe; pipes[1]->close = close_write_pipe; /* Read end can wait */ pipes[0]->selectcheck = check_pipe; pipes[0]->selectwait = wait_pipe; struct unix_pipe * internals = malloc(sizeof(struct unix_pipe)); internals->read_end = pipes[0]; internals->write_end = pipes[1]; internals->read_closed = 0; internals->write_closed = 0; internals->buffer = ring_buffer_create(size); pipes[0]->device = internals; pipes[1]->device = internals; return 0; } ================================================ FILE: kernel/vfs/vfs.c ================================================ /** * @file kernel/vfs/vfs.c * @brief Virtual file system. * * Provides the high-level generic operations for the VFS. * * @warning Here be dragons * * This VFS implementation comes from toaru32. It has a lot of weird * quirks and doesn't quite work like a typical Unix VFS would. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2021 K. Lange * Copyright (C) 2014 Lioncash * Copyright (C) 2012 Tianyi Wang */ #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_SYMLINK_DEPTH 8 #define MAX_SYMLINK_SIZE 4096 tree_t * fs_tree = NULL; /* File system mountpoint tree */ fs_node_t * fs_root = NULL; /* Pointer to the root mount fs_node (must be some form of filesystem, even ramdisk) */ hashmap_t * fs_types = NULL; #define MIN(l,r) ((l) < (r) ? (l) : (r)) #define MAX(l,r) ((l) > (r) ? (l) : (r)) #define debug_print(x, ...) do { if (0) {printf("vfs.c [%s] ", #x); printf(__VA_ARGS__); printf("\n"); } } while (0) static int cb_printf(void * user, char c) { fs_node_t * f = user; write_fs(f, 0, 1, (uint8_t*)&c); return 0; } /** * @brief Write printf output to a simple file node. * * The file node, f, must be a simple character device that * allows repeated writes of a single byte without an incrementing * offset, such as a serial port or TTY. */ int fprintf(fs_node_t * f, const char * fmt, ...) { va_list args; va_start(args, fmt); int out = xvasprintf(cb_printf, f, fmt, args); va_end(args); return out; } int has_permission(fs_node_t * node, int permission_bit) { if (!node) return 0; if (this_core->current_process->user == 0 && permission_bit != 01) { /* even root needs exec to exec */ return 1; } uint64_t permissions = node->mask; uint8_t my_permissions = (permissions) & 07; uint8_t user_perm = (permissions >> 6) & 07; uint8_t group_perm = (permissions >> 3) & 07; if (this_core->current_process->user == node->uid) my_permissions |= user_perm; if (this_core->current_process->user_group == node->gid) my_permissions |= group_perm; else if (this_core->current_process->supplementary_group_count) { for (int i = 0; i < this_core->current_process->supplementary_group_count; ++i) { if (this_core->current_process->supplementary_group_list[i] == node->gid) { my_permissions |= group_perm; } } } return (permission_bit & my_permissions); } static struct dirent * readdir_mapper(fs_node_t *node, unsigned long index) { tree_node_t * d = (tree_node_t *)node->device; if (!d) return NULL; if (index == 0) { struct dirent * dir = malloc(sizeof(struct dirent)); strcpy(dir->d_name, "."); dir->d_ino = 0; return dir; } else if (index == 1) { struct dirent * dir = malloc(sizeof(struct dirent)); strcpy(dir->d_name, ".."); dir->d_ino = 1; return dir; } index -= 2; unsigned long i = 0; foreach(child, d->children) { if (i == index) { /* Recursively print the children */ tree_node_t * tchild = (tree_node_t *)child->value; struct vfs_entry * n = (struct vfs_entry *)tchild->value; struct dirent * dir = malloc(sizeof(struct dirent)); size_t len = strlen(n->name) + 1; memcpy(&dir->d_name, n->name, MIN(256, len)); dir->d_ino = i; return dir; } ++i; } return NULL; } static fs_node_t * vfs_mapper(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->mask = 0555; fnode->flags = FS_DIRECTORY; fnode->readdir = readdir_mapper; fnode->ctime = now(); fnode->mtime = now(); fnode->atime = now(); return fnode; } /** * @brief Check if a read from this file would block. */ int selectcheck_fs(fs_node_t * node) { if (!node) return -ENOENT; if (node->selectcheck) { return node->selectcheck(node); } return -EINVAL; } /** * @brief Inform a node that it should alert the current_process. */ int selectwait_fs(fs_node_t * node, void * process) { if (!node) return -ENOENT; if (node->selectwait) { return node->selectwait(node, process); } return -EINVAL; } /** * @brief Read a file system node based on its underlying type. * * @param node Node to read * @param offset Offset into the node data to read from * @param size How much data to read (in bytes) * @param buffer A buffer to copy of the read data into * @returns Bytes read */ ssize_t read_fs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { if (!node) return -ENOENT; if (node->read) { return node->read(node, offset, size, buffer); } else { if (node->flags & FS_DIRECTORY) return -EISDIR; return -EINVAL; } } /** * @brief Write a file system node based on its underlying type. * * @param node Node to write to * @param offset Offset into the node data to write to * @param size How much data to write (in bytes) * @param buffer A buffer to copy from * @returns Bytes written */ ssize_t write_fs(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { if (!node) return -ENOENT; if (node->write) { return node->write(node, offset, size, buffer); } else { if (node->flags & FS_DIRECTORY) return -EISDIR; return -EROFS; } } /** * @brief set the size of a file to 9 * * @param node File to resize */ int truncate_fs(fs_node_t * node, size_t size) { if (!node) return -ENOENT; if (node->truncate) { return node->truncate(node, size); } return -EINVAL; } //volatile uint8_t tmp_refcount_lock = 0; static spin_lock_t tmp_refcount_lock = { 0 }; void vfs_lock(fs_node_t * node) { spin_lock(tmp_refcount_lock); node->refcount = -1; spin_unlock(tmp_refcount_lock); } /** * @brief Open a file system node. * * @param node Node to open * @param flags Same as open, specifies read/write/append/truncate */ void open_fs(fs_node_t *node, unsigned int flags) { if (!node) return; if (node->refcount >= 0) { spin_lock(tmp_refcount_lock); node->refcount++; spin_unlock(tmp_refcount_lock); } if (node->open) { node->open(node, flags); } } /** * @brief Close a file system node * * @param node Node to close */ void close_fs(fs_node_t *node) { //assert(node != fs_root && "Attempted to close the filesystem root. kablooey"); if (!node) { debug_print(WARNING, "Double close? This isn't an fs_node."); return; } if (node->refcount == -1) return; spin_lock(tmp_refcount_lock); node->refcount--; if (node->refcount == 0) { debug_print(NOTICE, "Node refcount [%s] is now 0: %ld", node->name, node->refcount); if (node->close) { node->close(node); } free(node); } spin_unlock(tmp_refcount_lock); } /** * @brief Change permissions for a file system node. * * @param node Node to change permissions for * @param mode New mode bits */ int chmod_fs(fs_node_t *node, mode_t mode) { if (node->chmod) { return node->chmod(node, mode); } return 0; } /** * @brief Change ownership for a file system node. */ int chown_fs(fs_node_t *node, uid_t uid, gid_t gid) { if (node->chown) { return node->chown(node, uid, gid); } return 0; } /** * @brief Read a directory for the requested index * * @param node Directory to read * @param index Offset to look for * @returns A dirent object. */ struct dirent *readdir_fs(fs_node_t *node, unsigned long index) { if (!node) return NULL; if ((node->flags & FS_DIRECTORY) && node->readdir) { return node->readdir(node, index); } else { return NULL; } } /** * @brief Find the requested file in the directory and return an fs_node for it * * @param node Directory to search * @param name File to look for * @returns An fs_node that the caller can free */ fs_node_t *finddir_fs(fs_node_t *node, char *name) { if (!node) return NULL; if ((node->flags & FS_DIRECTORY) && node->finddir) { return node->finddir(node, name); } else { debug_print(WARNING, "Node passed to finddir_fs isn't a directory!"); debug_print(WARNING, "node = %p, name = %s", (void*)node, name); return NULL; } } /** * @brief Control Device * * @param node Device node to control * @param request Device-specific request code * @param argp Depends on `request` * @returns Depends on `request` */ int ioctl_fs(fs_node_t *node, unsigned long request, void * argp) { if (!node) return -ENOENT; if (node->ioctl) { return node->ioctl(node, request, argp); } else { return -EINVAL; } } fs_node_t * file_get_parent(const char * path) { char * parent_path = malloc(strlen(path) + 5); snprintf(parent_path, strlen(path) + 4, "%s/..", path); fs_node_t * parent = kopen(parent_path, 0); free(parent_path); return parent; } static const char * fs_basename(const char * path) { const char * f_path = path + strlen(path) - 1; while (f_path > path && *f_path == '/') { /* Trailing slashes */ f_path--; } while (f_path > path) { if (*f_path == '/') { f_path += 1; break; } f_path--; } while (*f_path == '/') { f_path++; } return f_path; } int rename_file_fs(const char * src, const char * dest) { if (!*src || !*dest) return -ENOENT; fs_node_t * src_parent = file_get_parent(src); if (!src_parent) return -ENOENT; fs_node_t * dest_parent = file_get_parent(dest); if (!dest_parent) { close_fs(src_parent); return -ENOENT; } int out = 0; if (!src_parent->mount) { out = -EROFS; goto _nope; } if (src_parent->mount != dest_parent->mount) { out = -EXDEV; goto _nope; } if (!src_parent->mount->rename) { out = -ENOTSUP; goto _nope; } if (!has_permission(src_parent, 02) || !has_permission(src_parent, 01)) { out = -EACCES; goto _nope; } if (!has_permission(dest_parent, 02) || !has_permission(dest_parent, 01)) { out = -EACCES; goto _nope; } /* Get basename of each path component */ const char * src_name = fs_basename(src); const char * dest_name = fs_basename(dest); if (!*src_name || !*dest_name) return -EINVAL; if (*src_name == '/' || *dest_name == '/') return -EINVAL; out = src_parent->mount->rename(src_parent->mount, src_parent, src_name, dest_parent, dest_name); _nope: close_fs(dest_parent); close_fs(src_parent); return out; } /* * XXX: The following two function should be replaced with * one function to create children of directory nodes. * There is no fundamental difference between a directory * and a file, thus, the use of flag sets should suffice */ int create_file_fs(char *name, mode_t permission) { fs_node_t * parent; char *cwd = (char *)(this_core->current_process->wd_name); char *path = canonicalize_path(cwd, name); char * parent_path = malloc(strlen(path) + 5); snprintf(parent_path, strlen(path) + 4, "%s/..", path); char * f_path = path + strlen(path) - 1; while (f_path > path) { if (*f_path == '/') { f_path += 1; break; } f_path--; } while (*f_path == '/') { f_path++; } debug_print(NOTICE, "creating file %s within %s (hope these strings are good)", f_path, parent_path); parent = kopen(parent_path, 0); free(parent_path); if (!parent) { debug_print(WARNING, "failed to open parent"); free(path); return -ENOENT; } /* Need both exec and write on the parent to create a new entry */ if (!has_permission(parent, 02) || !has_permission(parent, 01)) { free(path); close_fs(parent); return -EACCES; } int ret = 0; if (parent->create) { ret = parent->create(parent, f_path, permission); } else { ret = -EINVAL; } free(path); free(parent); return ret; } int unlink_fs(char * name) { fs_node_t * parent; char *cwd = (char *)(this_core->current_process->wd_name); char *path = canonicalize_path(cwd, name); char * parent_path = malloc(strlen(path) + 5); snprintf(parent_path, strlen(path) + 4, "%s/..", path); char * f_path = path + strlen(path) - 1; while (f_path > path) { if (*f_path == '/') { f_path += 1; break; } f_path--; } while (*f_path == '/') { f_path++; } debug_print(WARNING, "unlinking file %s within %s (hope these strings are good)", f_path, parent_path); parent = kopen(parent_path, 0); free(parent_path); if (!parent) { free(path); return -ENOENT; } if (!has_permission(parent, 02) || !has_permission(parent, 01)) { free(path); close_fs(parent); return -EACCES; } int ret = 0; if (parent->unlink) { ret = parent->unlink(parent, f_path); } else { ret = -EINVAL; } free(path); close_fs(parent); return ret; } int mkdir_fs(char *name, mode_t permission) { fs_node_t * parent; char *cwd = (char *)(this_core->current_process->wd_name); char *path = canonicalize_path(cwd, name); if (!name || !strlen(name)) { return -EINVAL; } char * parent_path = malloc(strlen(path) + 5); snprintf(parent_path, strlen(path) + 4, "%s/..", path); char * f_path = path + strlen(path) - 1; while (f_path > path) { if (*f_path == '/') { f_path += 1; break; } f_path--; } while (*f_path == '/') { f_path++; } debug_print(WARNING, "creating directory %s within %s (hope these strings are good)", f_path, parent_path); parent = kopen(parent_path, 0); free(parent_path); if (!parent) { free(path); return -ENOENT; } if (!f_path || !strlen(f_path)) { /* Odd edge case with / */ return -EEXIST; } /* Permission check was moved into methods for reasons. */ int ret = 0; if (parent->mkdir) { ret = parent->mkdir(parent, f_path, permission); } else { ret = -EROFS; } free(path); close_fs(parent); return ret; } fs_node_t *clone_fs(fs_node_t *source) { if (!source) return NULL; if (source->refcount >= 0) { spin_lock(tmp_refcount_lock); source->refcount++; spin_unlock(tmp_refcount_lock); } return source; } int symlink_fs(char * target, char * name) { fs_node_t * parent; char *cwd = (char *)(this_core->current_process->wd_name); char *path = canonicalize_path(cwd, name); char * parent_path = malloc(strlen(path) + 5); snprintf(parent_path, strlen(path) + 4, "%s/..", path); char * f_path = path + strlen(path) - 1; while (f_path > path) { if (*f_path == '/') { f_path += 1; break; } f_path--; } debug_print(NOTICE, "creating symlink %s within %s", f_path, parent_path); parent = kopen(parent_path, 0); free(parent_path); if (!parent) { free(path); return -ENOENT; } /* Need both exec and write on the parent to create a new entry */ if (!has_permission(parent, 02) || !has_permission(parent, 01)) { free(path); close_fs(parent); return -EACCES; } int ret = 0; if (parent->symlink) { ret = parent->symlink(parent, target, f_path); } else { ret = -EINVAL; } free(path); close_fs(parent); return ret; } ssize_t readlink_fs(fs_node_t *node, char * buf, size_t size) { if (!node) return -ENOENT; if (node->readlink) { return node->readlink(node, buf, size); } else { return -EINVAL; } } /** * @brief Canonicalize a path. * * @param cwd Current working directory * @param input Path to append or canonicalize on * @returns An absolute path string */ char *canonicalize_path(const char *cwd, const char *input) { /* This is a stack-based canonicalizer; we use a list as a stack */ list_t *out = list_create("vfs canonicalize_path working memory",input); /* * If we have a relative path, we need to canonicalize * the working directory and insert it into the stack. */ if (strlen(input) && input[0] != PATH_SEPARATOR) { /* Make a copy of the working directory */ char *path = malloc((strlen(cwd) + 1) * sizeof(char)); memcpy(path, cwd, strlen(cwd) + 1); /* Setup tokenizer */ char *pch; char *save; pch = strtok_r(path,PATH_SEPARATOR_STRING,&save); /* Start tokenizing */ while (pch != NULL) { /* Make copies of the path elements */ char *s = malloc(sizeof(char) * (strlen(pch) + 1)); memcpy(s, pch, strlen(pch) + 1); /* And push them */ list_insert(out, s); pch = strtok_r(NULL,PATH_SEPARATOR_STRING,&save); } free(path); } /* Similarly, we need to push the elements from the new path */ char *path = malloc((strlen(input) + 1) * sizeof(char)); memcpy(path, input, strlen(input) + 1); /* Initialize the tokenizer... */ char *pch; char *save; pch = strtok_r(path,PATH_SEPARATOR_STRING,&save); /* * Tokenize the path, this time, taking care to properly * handle .. and . to represent up (stack pop) and current * (do nothing) */ while (pch != NULL) { if (!strcmp(pch,PATH_UP)) { /* * Path = .. * Pop the stack to move up a directory */ node_t * n = list_pop(out); if (n) { free(n->value); free(n); } } else if (!strcmp(pch,PATH_DOT)) { /* * Path = . * Do nothing */ } else { /* * Regular path, push it * XXX: Path elements should be checked for existence! */ char * s = malloc(sizeof(char) * (strlen(pch) + 1)); memcpy(s, pch, strlen(pch) + 1); list_insert(out, s); } pch = strtok_r(NULL, PATH_SEPARATOR_STRING, &save); } free(path); /* Calculate the size of the path string */ size_t size = 0; foreach(item, out) { /* Helpful use of our foreach macro. */ size += strlen(item->value) + 1; } /* join() the list */ char *output = malloc(sizeof(char) * (size + 1)); char *output_offset = output; if (size == 0) { /* * If the path is empty, we take this to mean the root * thus we synthesize a path of "/" to return. */ output = realloc(output, sizeof(char) * 2); output[0] = PATH_SEPARATOR; output[1] = '\0'; } else { /* Otherwise, append each element together */ foreach(item, out) { output_offset[0] = PATH_SEPARATOR; output_offset++; memcpy(output_offset, item->value, strlen(item->value) + 1); output_offset += strlen(item->value); } } /* Clean up the various things we used to get here */ list_destroy(out); list_free(out); free(out); /* And return a working, absolute path */ return output; } void vfs_install(void) { /* Initialize the mountpoint tree */ fs_tree = tree_create(); struct vfs_entry * root = malloc(sizeof(struct vfs_entry)); root->name = strdup("[root]"); root->file = NULL; /* Nothing mounted as root */ root->fs_type = NULL; root->device = NULL; tree_set_root(fs_tree, root); fs_types = hashmap_create(5); } int vfs_register(const char * name, vfs_mount_callback callback) { if (hashmap_get(fs_types, name)) return 1; hashmap_set(fs_types, name, (void *)(uintptr_t)callback); return 0; } int vfs_mount_type(const char * type, const char * arg, const char * mountpoint) { vfs_mount_callback t = (vfs_mount_callback)(uintptr_t)hashmap_get(fs_types, type); if (!t) { debug_print(WARNING, "Unknown filesystem type: %s", type); return -ENODEV; } fs_node_t * n = t(arg, mountpoint); /* Quick hack to let partition mappers not return a node to mount at 'mountpoint'... */ if ((uintptr_t)n == (uintptr_t)1) return 0; if (!n) return -EINVAL; tree_node_t * node = vfs_mount(mountpoint, n, type, arg); if (!node) return -EINVAL; debug_print(NOTICE, "Mounted %s[%s] to %s: %p", type, arg, mountpoint, (void*)n); debug_print_vfs_tree(); return 0; } static spin_lock_t tmp_vfs_lock = { 0 }; /** * @brief Mount a file system to the specified path. * * Mounts a file system node to a given base path. * For example, if we have an EXT2 filesystem with a root node * of ext2_root and we want to mount it to /, we would run * vfs_mount("/", ext2_root); - or, if we have a procfs node, * we could mount that to /dev/procfs. Individual files can also * be mounted. * * Paths here must be absolute. */ void * vfs_mount(const char * path, fs_node_t * local_root, const char * type, const char * options) { if (!fs_tree) { debug_print(ERROR, "VFS hasn't been initialized, you can't mount things yet!"); return NULL; } if (!path || path[0] != '/') { debug_print(ERROR, "Path must be absolute for mountpoint."); return NULL; } spin_lock(tmp_vfs_lock); local_root->refcount = -1; tree_node_t * ret_val = NULL; char * p = strdup(path); char * i = p; int path_len = strlen(p); /* Chop the path up */ while (i < p + path_len) { if (*i == PATH_SEPARATOR) { *i = '\0'; } i++; } /* Clean up */ p[path_len] = '\0'; i = p + 1; /* Root */ tree_node_t * root_node = fs_tree->root; if (*i == '\0') { /* Special case, we're trying to set the root node */ struct vfs_entry * root = (struct vfs_entry *)root_node->value; if (root->file) { debug_print(WARNING, "Path %s already mounted, unmount before trying to mount something else.", path); } root->file = local_root; root->device = strdup(options); root->fs_type = strdup(type); /* We also keep a legacy shortcut around for that */ fs_root = local_root; ret_val = root_node; } else { tree_node_t * node = root_node; char * at = i; while (1) { if (at >= p + path_len) { break; } int found = 0; debug_print(NOTICE, "Searching for %s", at); foreach(child, node->children) { tree_node_t * tchild = (tree_node_t *)child->value; struct vfs_entry * ent = (struct vfs_entry *)tchild->value; if (!strcmp(ent->name, at)) { found = 1; node = tchild; ret_val = node; break; } } if (!found) { debug_print(NOTICE, "Did not find %s, making it.", at); struct vfs_entry * ent = malloc(sizeof(struct vfs_entry)); ent->name = strdup(at); ent->file = NULL; ent->device = NULL; ent->fs_type = NULL; node = tree_node_insert_child(fs_tree, node, ent); } at = at + strlen(at) + 1; } struct vfs_entry * ent = (struct vfs_entry *)node->value; if (ent->file) { debug_print(WARNING, "Path %s already mounted, unmount before trying to mount something else.", path); } ent->file = local_root; ent->device = strdup(options); ent->fs_type = strdup(type); ret_val = node; } free(p); spin_unlock(tmp_vfs_lock); return ret_val; } void map_vfs_directory(const char * c) { fs_node_t * f = vfs_mapper(); tree_node_t * e = vfs_mount((char*)c, f, "vfs_mapper", ""); if (!strcmp(c, "/")) { f->device = fs_tree->root; } else { f->device = e; } } void debug_print_vfs_tree_node(tree_node_t * node, size_t height) { /* End recursion on a blank entry */ if (!node) return; char * tmp = malloc(512); memset(tmp, 0, 512); char * c = tmp; /* Indent output */ for (uint32_t i = 0; i < height; ++i) { c += snprintf(c, 3, " "); } /* Get the current process */ struct vfs_entry * fnode = (struct vfs_entry *)node->value; /* Print the process name */ if (fnode->file) { c += snprintf(c, 100, "%s → %s %p (%s, %s)", fnode->name, fnode->device, (void*)fnode->file, fnode->fs_type, fnode->file->name); } else { c += snprintf(c, 100, "%s → (empty)", fnode->name); } /* Linefeed */ debug_print(NOTICE, "%s", tmp); free(tmp); foreach(child, node->children) { /* Recursively print the children */ debug_print_vfs_tree_node(child->value, height + 1); } } void debug_print_vfs_tree(void) { debug_print_vfs_tree_node(fs_tree->root, 0); } /** * get_mount_point * */ fs_node_t *get_mount_point(char * path, unsigned int path_depth, char **outpath, unsigned int * outdepth) { size_t depth; for (depth = 0; depth <= path_depth; ++depth) { path += strlen(path) + 1; } /* Last available node */ fs_node_t * last = fs_root; tree_node_t * node = fs_tree->root; char * at = *outpath; int _depth = 1; int _tree_depth = 0; while (1) { if (at >= path) { break; } int found = 0; debug_print(INFO, "Searching for %s", at); foreach(child, node->children) { tree_node_t * tchild = (tree_node_t *)child->value; struct vfs_entry * ent = (struct vfs_entry *)tchild->value; if (!strcmp(ent->name, at)) { found = 1; node = tchild; at = at + strlen(at) + 1; if (ent->file) { _tree_depth = _depth; last = ent->file; *outpath = at; } break; } } if (!found) { break; } _depth++; } *outdepth = _tree_depth; if (last) { fs_node_t * last_clone = malloc(sizeof(fs_node_t)); memcpy(last_clone, last, sizeof(fs_node_t)); last_clone->refcount = 0; return last_clone; } return last; } fs_node_t *kopen_recur(const char *filename, uint64_t flags, uint64_t symlink_depth, char *relative_to) { /* Simple sanity checks that we actually have a file system */ if (!filename) { return NULL; } /* Canonicalize the (potentially relative) path... */ char *path = canonicalize_path(relative_to, filename); /* And store the length once to save recalculations */ size_t path_len = strlen(path); /* If strlen(path) == 1, then path = "/"; return root */ if (path_len == 1) { /* Clone the root file system node */ fs_node_t *root_clone = malloc(sizeof(fs_node_t)); memcpy(root_clone, fs_root, sizeof(fs_node_t)); root_clone->refcount = 0; /* Free the path */ free(path); open_fs(root_clone, flags); /* And return the clone */ return root_clone; } /* Otherwise, we need to break the path up and start searching */ char *path_offset = path; uint64_t path_depth = 0; while (path_offset < path + path_len) { /* Find each PATH_SEPARATOR */ if (*path_offset == PATH_SEPARATOR) { *path_offset = '\0'; path_depth++; } path_offset++; } /* Clean up */ path[path_len] = '\0'; path_offset = path + 1; /* * At this point, the path is tokenized and path_offset points * to the first token (directory) and path_depth is the number * of directories in the path */ /* * Dig through the (real) tree to find the file */ unsigned int depth = 0; /* Find the mountpoint for this file */ fs_node_t *node_ptr = get_mount_point(path, path_depth, &path_offset, &depth); debug_print(INFO, "path_offset: %s", path_offset); debug_print(INFO, "depth: %d", depth); if (!node_ptr) return NULL; do { /* * This test is a little complicated, but we basically always resolve symlinks in the * of a path (like /home/symlink/file) even if O_NOFOLLOW and O_PATH are set. If we are * on the leaf of the path then we will look at those flags and act accordingly */ if ((node_ptr->flags & FS_SYMLINK) && !((flags & O_NOFOLLOW) && (flags & O_PATH) && depth == path_depth)) { /* This ensures we don't return a path when NOFOLLOW is requested but PATH * isn't passed. */ debug_print(NOTICE, "resolving symlink at %s", node_ptr->name); if ((flags & O_NOFOLLOW) && depth == path_depth - 1) { /* TODO(gerow): should probably be setting errno from this */ debug_print(NOTICE, "Refusing to follow final entry for open with O_NOFOLLOW for %s.", node_ptr->name); free((void *)path); free(node_ptr); return NULL; } if (symlink_depth >= MAX_SYMLINK_DEPTH) { /* TODO(gerow): should probably be setting errno from this */ debug_print(WARNING, "Reached max symlink depth on %s.", node_ptr->name); free((void *)path); free(node_ptr); return NULL; } /* * This may actually be big enough that we wouldn't want to allocate it on * the stack, especially considering this function is called recursively */ char symlink_buf[MAX_SYMLINK_SIZE]; int len = readlink_fs(node_ptr, symlink_buf, sizeof(symlink_buf)); if (len < 0) { /* TODO(gerow): should probably be setting errno from this */ debug_print(WARNING, "Got error %d from symlink for %s.", len, node_ptr->name); free((void *)path); free(node_ptr); return NULL; } if (symlink_buf[len] != '\0') { /* TODO(gerow): should probably be setting errno from this */ debug_print(WARNING, "readlink for %s doesn't end in a null pointer. That's weird...", node_ptr->name); free((void *)path); free(node_ptr); return NULL; } fs_node_t * old_node_ptr = node_ptr; /* Rebuild our path up to this point. This is hella hacky. */ char * relpath = malloc(path_len + 1); char * ptr = relpath; memcpy(relpath, path, path_len + 1); for (unsigned int i = 0; depth && i < depth-1; i++) { while(*ptr != '\0') { ptr++; } *ptr = PATH_SEPARATOR; } node_ptr = kopen_recur(symlink_buf, 0, symlink_depth + 1, relpath); free(relpath); free(old_node_ptr); if (!node_ptr) { /* Dangling symlink? */ debug_print(WARNING, "Failed to open symlink path %s. Perhaps it's a dangling symlink?", symlink_buf); free((void *)path); return NULL; } } if (path_offset >= path+path_len) { free(path); open_fs(node_ptr, flags); return node_ptr; } if (depth == path_depth) { /* We found the file and are done, open the node */ open_fs(node_ptr, flags); free((void *)path); return node_ptr; } /* We are still searching... */ if (!has_permission(node_ptr, 01)) { /* * TODO: kopen_recur has no way to pass along a failure reason? * This will appear as 'ENOENT' instead of 'EACCESS', should fix that... */ free(node_ptr); free((void*)path); return NULL; } debug_print(INFO, "... Searching for %s", path_offset); fs_node_t * node_next = finddir_fs(node_ptr, path_offset); free(node_ptr); /* Always a clone or an unopened thing */ node_ptr = node_next; /* Search the active directory for the requested directory */ if (!node_ptr) { /* We failed to find the requested directory */ free((void *)path); return NULL; } path_offset += strlen(path_offset) + 1; ++depth; } while (depth < path_depth + 1); debug_print(INFO, "- Not found."); /* We failed to find the requested file, but our loop terminated. */ free((void *)path); return NULL; } /** * @brief Open a file by name. * * Explore the file system tree to find the appropriate node for * for a given path. The path can be relative to the working directory * and will be canonicalized by the kernel. * * @param filename Filename to open * @param flags Flag bits for read/write mode. * @returns A file system node element that the caller can free. */ fs_node_t *kopen(const char *filename, unsigned int flags) { debug_print(NOTICE, "kopen(%s)", filename); return kopen_recur(filename, flags, 0, (char *)(this_core->current_process->wd_name)); } ================================================ FILE: kernel/vfs/zero.c ================================================ /** * @file kernel/vfs/zero.c * @brief /dev/null and /dev/zero provider. * * @copyright * 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-2018 K. Lange */ #include #include #include #include #include static ssize_t read_null(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { return 0; } static ssize_t write_null(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { return size; } static void open_null(fs_node_t * node, unsigned int flags) { return; } static void close_null(fs_node_t * node) { return; } static ssize_t read_zero(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { memset(buffer, 0x00, size); return size; } static ssize_t write_zero(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { return size; } static void open_zero(fs_node_t * node, unsigned int flags) { return; } static void close_zero(fs_node_t * node) { return; } static fs_node_t * null_device_create(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "null"); fnode->uid = 0; fnode->gid = 0; fnode->mask = 0666; fnode->flags = FS_CHARDEVICE; fnode->read = read_null; fnode->write = write_null; fnode->open = open_null; fnode->close = close_null; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = NULL; return fnode; } static fs_node_t * zero_device_create(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; strcpy(fnode->name, "zero"); fnode->uid = 0; fnode->gid = 0; fnode->mask = 0666; fnode->flags = FS_CHARDEVICE; fnode->read = read_zero; fnode->write = write_zero; fnode->open = open_zero; fnode->close = close_zero; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = NULL; return fnode; } void zero_initialize(void) { vfs_mount("/dev/null", null_device_create(), "null", ""); vfs_mount("/dev/zero", zero_device_create(), "zero", ""); } ================================================ FILE: kernel/video/lfbvideo.c ================================================ /** * @file kernel/video/lfbvideo.c * @brief Shared linear framebuffer drivers for qemu/bochs/vbox, vmware, * and platforms that can modeset in the bootloader. * * Detects a small set of video devices that can be configured with simple * port writes and provides a runtime modesetting API for them. For other * devices, provides framebuffer mapping and resolution querying for modes * that have been preconfigured by the bootloader. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* FIXME: Not sure what to do with this; ifdef around it? */ #include static int PREFERRED_W = 1440; static int PREFERRED_H = 900; #define PREFERRED_VY 4096 #define PREFERRED_B 32 /* Exported to other modules */ uint16_t lfb_resolution_x = 0; uint16_t lfb_resolution_y = 0; uint16_t lfb_resolution_b = 0; uint32_t lfb_resolution_s = 0; uint8_t * lfb_vid_memory = (uint8_t *)0xE0000000; size_t lfb_memsize = 0xFF0000; const char * lfb_driver_name = NULL; int lfb_use_write_combining = 0; uintptr_t lfb_qemu_mmio = 0; fs_node_t * lfb_device = NULL; static int lfb_init(const char * c); /* Where to send display size change signals */ static pid_t display_change_recipient = 0; /* Driver-specific modesetting function */ void (*lfb_resolution_impl)(uint16_t,uint16_t) = NULL; /* Called by ioctl on /dev/fb0 */ void lfb_set_resolution(uint16_t x, uint16_t y) { if (lfb_resolution_impl) { lfb_resolution_impl(x,y); if (display_change_recipient) { send_signal(display_change_recipient, SIGWINEVENT, 1); } } } extern void ptr_validate(void * ptr, const char * syscall); #define validate(o) ptr_validate(o,"ioctl") /** * Framebuffer control ioctls. * Used by the compositor to get display sizes and by the * resolution changer to initiate modesetting. */ static int ioctl_vid(fs_node_t * node, unsigned long request, void * argp) { switch (request) { case IO_VID_WIDTH: /* Get framebuffer width */ validate(argp); *((size_t *)argp) = lfb_resolution_x; return 0; case IO_VID_HEIGHT: /* Get framebuffer height */ validate(argp); *((size_t *)argp) = lfb_resolution_y; return 0; case IO_VID_DEPTH: /* Get framebuffer bit depth */ validate(argp); *((size_t *)argp) = lfb_resolution_b; return 0; case IO_VID_STRIDE: /* Get framebuffer scanline stride */ validate(argp); *((size_t *)argp) = lfb_resolution_s; return 0; case IO_VID_ADDR: /* Map framebuffer into userspace process */ validate(argp); { uintptr_t lfb_user_offset; if (*(uintptr_t*)argp == 0) { /* Pick an address and map it */ lfb_user_offset = USER_DEVICE_MAP; } else { validate((void*)(*(uintptr_t*)argp)); lfb_user_offset = *(uintptr_t*)argp; } for (uintptr_t i = 0; i < lfb_memsize; i += 0x1000) { union PML * page = mmu_get_page(lfb_user_offset + i, MMU_GET_MAKE); mmu_frame_map_address(page,MMU_FLAG_WRITABLE | (lfb_use_write_combining ? MMU_FLAG_WC : 0),((uintptr_t)(lfb_vid_memory) & 0xFFFFFFFF) + i); } *((uintptr_t *)argp) = lfb_user_offset; } return 0; case IO_VID_SIGNAL: /* ioctl to register for a signal (vid device change? idk) on display change */ display_change_recipient = this_core->current_process->id; return 0; case IO_VID_SET: /* Initiate mode setting */ validate(argp); lfb_set_resolution(((struct vid_size *)argp)->width, ((struct vid_size *)argp)->height); return 0; case IO_VID_DRIVER: validate(argp); memcpy(argp, lfb_driver_name, strlen(lfb_driver_name)); return 0; case IO_VID_REINIT: if (this_core->current_process->user != 0) { return -EPERM; } validate(argp); return lfb_init(argp); default: return -EINVAL; } return -EINVAL; } /* Framebuffer device file initializer */ static fs_node_t * lfb_video_device_create(void /* TODO */) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); snprintf(fnode->name, 100, "fb0"); /* TODO */ fnode->length = 0; fnode->flags = FS_BLOCKDEVICE; /* Framebuffers are block devices */ fnode->mask = 0660; /* Only accessible to root user/group */ fnode->ioctl = ioctl_vid; /* control function defined above */ return fnode; } static void framebuffer_func(fs_node_t * node) { if (lfb_driver_name) { procfs_printf(node, "Driver:\t%s\n" "XRes:\t%d\n" "YRes:\t%d\n" "BitsPerPixel:\t%d\n" "Stride:\t%d\n" "Address:\t%p\n", lfb_driver_name, lfb_resolution_x, lfb_resolution_y, lfb_resolution_b, lfb_resolution_s, lfb_vid_memory); } else { procfs_printf(node, "Driver:\tnone\n"); } } static struct procfs_entry framebuffer_entry = { 0, "framebuffer", framebuffer_func, }; /* Install framebuffer device */ static void finalize_graphics(const char * driver) { lfb_driver_name = driver; lfb_device = lfb_video_device_create(); lfb_device->length = lfb_resolution_s * lfb_resolution_y; /* Size is framebuffer size in bytes */ char info[100]; snprintf(info, 99, "%s,%ux%u", driver, lfb_resolution_x, lfb_resolution_y); vfs_mount("/dev/fb0", lfb_device, "lfb", info); procfs_install(&framebuffer_entry); } /* QEMU support {{{ */ static void qemu_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) { uintptr_t * output = extra; if ((v == 0x1234 && d == 0x1111) || (v == 0x10de && d == 0x0a20)) { #ifndef __x86_64__ /* we have to configure this thing ourselves */ uintptr_t t = 0x10000008; uintptr_t m = 0x11000000; pci_write_field(device, PCI_BAR0, 4, t); /* video memory? */ pci_write_field(device, PCI_BAR2, 4, m); /* MMIO? */ pci_write_field(device, PCI_COMMAND, 2, 4|2|1); #else uintptr_t t = pci_read_field(device, PCI_BAR0, 4); uintptr_t m = pci_read_field(device, PCI_BAR2, 4); #endif if (m == 0) { /* Shoot. */ return; } if (t > 0) { output[0] = (uintptr_t)mmu_map_from_physical(t & 0xFFFFFFF0); output[1] = (uintptr_t)mmu_map_from_physical(m & 0xFFFFFFF0); /* Figure out size */ pci_write_field(device, PCI_BAR0, 4, 0xFFFFFFFF); uint32_t s = pci_read_field(device, PCI_BAR0, 4); s = ~(s & -15) + 1; output[2] = s; pci_write_field(device, PCI_BAR0, 4, (uint32_t)t); } } } #define QEMU_MMIO_ID 0x00 #define QEMU_MMIO_FBWIDTH 0x02 #define QEMU_MMIO_FBHEIGHT 0x04 #define QEMU_MMIO_BPP 0x06 #define QEMU_MMIO_ENABLED 0x08 #define QEMU_MMIO_VIRTX 0x0c #define QEMU_MMIO_VIRTY 0x0e static void qemu_mmio_out(int off, uint16_t val) { *(volatile uint16_t*)(lfb_qemu_mmio + 0x500 + off) = val; } static uint16_t qemu_mmio_in(int off) { return *(volatile uint16_t*)(lfb_qemu_mmio + 0x500 + off); } static void qemu_set_resolution(uint16_t x, uint16_t y) { qemu_mmio_out(QEMU_MMIO_ENABLED, 0); qemu_mmio_out(QEMU_MMIO_FBWIDTH, x); qemu_mmio_out(QEMU_MMIO_FBHEIGHT, y); qemu_mmio_out(QEMU_MMIO_BPP, PREFERRED_B); qemu_mmio_out(QEMU_MMIO_VIRTX, x); qemu_mmio_out(QEMU_MMIO_VIRTY, y); qemu_mmio_out(QEMU_MMIO_ENABLED, 0x41); /* 01h: enabled, 40h: lfb */ /* unblank vga; this should only be necessary on secondary displays */ *(volatile uint8_t*)(lfb_qemu_mmio + 0x400) = 0x20; lfb_resolution_x = qemu_mmio_in(QEMU_MMIO_FBWIDTH); lfb_resolution_y = qemu_mmio_in(QEMU_MMIO_FBHEIGHT); lfb_resolution_b = qemu_mmio_in(QEMU_MMIO_BPP); lfb_resolution_s = qemu_mmio_in(QEMU_MMIO_VIRTX) * (lfb_resolution_b / 8); } static void graphics_install_bochs(uint16_t resolution_x, uint16_t resolution_y); static void graphics_install_qemu(uint16_t resolution_x, uint16_t resolution_y) { uintptr_t vals[3] = {0,0,0}; pci_scan(qemu_scan_pci, -1, vals); if (!vals[0]) { /* Try port-IO interface */ graphics_install_bochs(resolution_x, resolution_y); return; } lfb_vid_memory = (uint8_t*)vals[0]; lfb_qemu_mmio = vals[1]; lfb_memsize = vals[2]; uint16_t i = qemu_mmio_in(QEMU_MMIO_ID); if (i < 0xB0C0 || i > 0xB0C6) return; /* Unsupported qemu device. */ qemu_mmio_out(QEMU_MMIO_ID, 0xB0C4); /* We speak ver. 4 */ qemu_set_resolution(resolution_x, resolution_y); resolution_x = lfb_resolution_x; /* may have changed */ lfb_resolution_impl = &qemu_set_resolution; if (!lfb_vid_memory) { printf("failed to locate video memory\n"); return; } finalize_graphics("qemu"); } /* VirtualBox implements the portio-based interface, but not the MMIO one. */ static void bochs_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) { if ((v == 0x80EE && d == 0xBEEF) || (v == 0x1234 && d == 0x1111)) { uintptr_t t = pci_read_field(device, PCI_BAR0, 4); if (t > 0) { *((uint8_t **)extra) = mmu_map_from_physical(t & 0xFFFFFFF0); } } } static void bochs_set_resolution(uint16_t x, uint16_t y) { outports(0x1CE, 0x04); outports(0x1CF, 0x00); outports(0x1CE, 0x01); outports(0x1CF, x); outports(0x1CE, 0x02); outports(0x1CF, y); outports(0x1CE, 0x03); outports(0x1CF, PREFERRED_B); outports(0x1CE, 0x07); outports(0x1CF, PREFERRED_VY); outports(0x1CE, 0x04); outports(0x1CF, 0x41); outports(0x1CE, 0x01); x = inports(0x1CF); lfb_resolution_x = x; lfb_resolution_s = x * 4; lfb_resolution_y = y; lfb_resolution_b = 32; } static void graphics_install_bochs(uint16_t resolution_x, uint16_t resolution_y) { outports(0x1CE, 0x00); uint16_t i = inports(0x1CF); if (i < 0xB0C0 || i > 0xB0C6) { return; } outports(0x1CF, 0xB0C4); i = inports(0x1CF); bochs_set_resolution(resolution_x, resolution_y); resolution_x = lfb_resolution_x; /* may have changed */ pci_scan(bochs_scan_pci, -1, &lfb_vid_memory); lfb_resolution_impl = &bochs_set_resolution; if (!lfb_vid_memory) { printf("failed to locate video memory\n"); return; } outports(0x1CE, 0x0a); i = inports(0x1CF); if (i > 1) { lfb_memsize = (uint32_t)i * 64 * 1024; } else { lfb_memsize = inportl(0x1CF); } finalize_graphics("bochs"); } extern void arch_framebuffer_initialize(void); static void graphics_install_preset(uint16_t w, uint16_t h) { /* Make sure memsize is actually big enough */ size_t minsize = lfb_resolution_s * lfb_resolution_y * 4; if (lfb_memsize < minsize) lfb_memsize = minsize; finalize_graphics("preset"); } #define SVGA_IO_BASE (vmware_io) #define SVGA_IO_MUL 1 #define SVGA_INDEX_PORT 0 #define SVGA_VALUE_PORT 1 #define SVGA_REG_ID 0 #define SVGA_REG_ENABLE 1 #define SVGA_REG_WIDTH 2 #define SVGA_REG_HEIGHT 3 #define SVGA_REG_BITS_PER_PIXEL 7 #define SVGA_REG_BYTES_PER_LINE 12 #define SVGA_REG_FB_START 13 static uint32_t vmware_io = 0; static void vmware_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) { if ((v == 0x15ad && d == 0x0405)) { uintptr_t t = pci_read_field(device, PCI_BAR0, 4); if (t > 0) { *((uint8_t **)extra) = (uint8_t *)(t & 0xFFFFFFF0); } } } static void vmware_write(int reg, int value) { outportl(SVGA_IO_MUL * SVGA_INDEX_PORT + SVGA_IO_BASE, reg); outportl(SVGA_IO_MUL * SVGA_VALUE_PORT + SVGA_IO_BASE, value); } static uint32_t vmware_read(int reg) { outportl(SVGA_IO_MUL * SVGA_INDEX_PORT + SVGA_IO_BASE, reg); return inportl(SVGA_IO_MUL * SVGA_VALUE_PORT + SVGA_IO_BASE); } static void vmware_set_resolution(uint16_t w, uint16_t h) { vmware_write(SVGA_REG_ENABLE, 0); vmware_write(SVGA_REG_ID, 0); vmware_write(SVGA_REG_WIDTH, w); vmware_write(SVGA_REG_HEIGHT, h); vmware_write(SVGA_REG_BITS_PER_PIXEL, 32); vmware_write(SVGA_REG_ENABLE, 1); uint32_t bpl = vmware_read(SVGA_REG_BYTES_PER_LINE); lfb_resolution_x = w; lfb_resolution_s = bpl; lfb_resolution_y = h; lfb_resolution_b = 32; lfb_memsize = vmware_read(15); } static void graphics_install_vmware(uint16_t w, uint16_t h) { pci_scan(vmware_scan_pci, -1, &vmware_io); if (!vmware_io) { printf("vmware video, but no device found?\n"); return; } else { printf("vmware io base: %p\n", (void*)(uintptr_t)vmware_io); } vmware_set_resolution(w,h); lfb_resolution_impl = &vmware_set_resolution; uintptr_t fb_addr = vmware_read(SVGA_REG_FB_START); printf("vmware fb address: %p\n", (void*)fb_addr); lfb_memsize = vmware_read(15); printf("vmware fb size: 0x%lx\n", lfb_memsize); lfb_vid_memory = mmu_map_from_physical(fb_addr); finalize_graphics("vmware"); } struct disp_mode { int16_t x; int16_t y; int set; }; static void auto_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) { struct disp_mode * mode = extra; if (mode->set) return; if ((v == 0x1234 && d == 0x1111) || (v == 0x10de && d == 0x0a20)) { mode->set = 1; graphics_install_qemu(mode->x, mode->y); } else if (v == 0x80EE && d == 0xBEEF) { mode->set = 1; graphics_install_bochs(mode->x, mode->y); } else if ((v == 0x15ad && d == 0x0405)) { mode->set = 1; graphics_install_vmware(mode->x, mode->y); } } static fs_node_t * vga_text_device = NULL; static int ioctl_vga(fs_node_t * node, unsigned long request, void * argp) { switch (request) { case IO_VID_WIDTH: /* Get framebuffer width */ validate(argp); *((size_t *)argp) = 80; return 0; case IO_VID_HEIGHT: /* Get framebuffer height */ validate(argp); *((size_t *)argp) = 25; return 0; case IO_VID_ADDR: /* Map framebuffer into userspace process */ validate(argp); { uintptr_t vga_user_offset; if (*(uintptr_t*)argp == 0) { vga_user_offset = USER_DEVICE_MAP; } else { validate((void*)(*(uintptr_t*)argp)); vga_user_offset = *(uintptr_t*)argp; } for (uintptr_t i = 0; i < 0x1000; i += 0x1000) { union PML * page = mmu_get_page(vga_user_offset + i, MMU_GET_MAKE); mmu_frame_map_address(page,MMU_FLAG_WRITABLE/*|MMU_FLAG_WC*/,(uintptr_t)(0xB8000 + i)); } *((uintptr_t *)argp) = vga_user_offset; } return 0; default: return -EINVAL; } } static void vga_text_init(void) { vga_text_device = calloc(sizeof(fs_node_t), 1); snprintf(vga_text_device->name, 100, "vga0"); vga_text_device->length = 0; vga_text_device->flags = FS_BLOCKDEVICE; vga_text_device->mask = 0660; vga_text_device->ioctl = ioctl_vga; vfs_mount("/dev/vga0", vga_text_device, "vgatext", ""); } static int lfb_init(const char * c) { char * arg = strdup(c); char * argv[10]; int argc = tokenize(arg, ",", argv); if (!strcmp(argv[0],"text")) { /* VGA text mode? TODO: We should try to detect this, * or limit it to things that are likely to have it... */ vga_text_init(); free(arg); return 0; } uint16_t x, y; /* Extract framebuffer information from multiboot */ arch_framebuffer_initialize(); x = lfb_resolution_x; y = lfb_resolution_y; if (argc >= 3) { x = atoi(argv[1]); y = atoi(argv[2]); } else if (!lfb_resolution_x) { x = PREFERRED_W; y = PREFERRED_H; } if (args_present("lfbwc")) { lfb_use_write_combining = 1; } int ret_val = 0; if (!strcmp(argv[0], "auto")) { /* Attempt autodetection */ struct disp_mode mode = {x,y,0}; pci_scan(auto_scan_pci, -1, &mode); if (!mode.set) { graphics_install_preset(x,y); } } else if (!strcmp(argv[0], "qemu")) { /* BGA with MMIO */ graphics_install_qemu(x,y); } else if (!strcmp(argv[0], "bochs")) { /* BGA with no MMIO */ graphics_install_bochs(x,y); } else if (!strcmp(argv[0],"vmware")) { /* VMware SVGA */ graphics_install_vmware(x,y); } else if (!strcmp(argv[0],"preset")) { /* Set by bootloader (UEFI) */ graphics_install_preset(x,y); } else { ret_val = 1; } free(arg); return ret_val; } int framebuffer_initialize(void) { lfb_init(args_present("vid") ? args_value("vid") : "auto"); return 0; } ================================================ FILE: lib/README.md ================================================ # ToaruOS System Libraries These 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. ## `toaru_auth` Provides 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`...). ## `toaru_button` Renderer for button widgets. Not really a widget library at the moment. ## `toaru_confreader` Implements a basic INI parser for use with configuration files. ## `toaru_decorations` Client-side decoration library for the compositor. Supports pluggable decoration themes through additional libraries, which are named as `libtoaru_decor-...`. ## `toaru_graphics` General-purpose 2D drawing and pixel-pushing library. Provides sprite blitting, rotation, scaling, etc. ## `toaru_hashmap` Generic hashmap implementation. Also used by the kernel. ## `toaru_iconcache` Convenience library for loading icons at specific sizes. ## `toaru_inflate` Decompression library for DEFLATE payloads. ## `toaru_jpeg` Minimal, incomplete JPEG decoder. Mostly used for providing wallpapers. Doesn't support most JPEG features. ## `toaru_kbd` Keyboard scancode parser. ## `toaru_list` Generic expandable linked list implementation. ## `toaru_markup` XML-like syntax parser. ## `toaru_menu` Menu widget library. Used for the "Applications" menu, context menus, etc. ## `toaru_pex` Userspace library for using the ToaruOS "packetfs" subsystem, which provides packet-based IPC. ## `toaru_png` Decoder for Portable Network Graphics images. ## `toaru_rline` Rich line editor for terminal applications, with support for tab completion and syntax highlighting. ## `toaru_termemu` Terminal ANSI escape processor. ## `toaru_text` TrueType font parser and text renderer. ## `toaru_tree` Generic tree implementation. Also used by the kernel. ## `toaru_yutani` Compositor client library, used to build GUI applications. ================================================ FILE: lib/auth.c ================================================ /** * @brief Authentication routines. * * @copyright * 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 */ #include #include #include #include #include #ifndef fgetpwent extern struct passwd *fgetpwent(FILE *stream); #endif extern int setgroups(int size, const gid_t list[]); #define MASTER_PASSWD "/etc/master.passwd" int toaru_auth_check_pass(char * user, char * pass) { /* XXX DO something useful */ /* Open up /etc/master.passwd */ FILE * master = fopen(MASTER_PASSWD, "r"); struct passwd * p; while ((p = fgetpwent(master))) { if (!strcmp(p->pw_name, user) && !strcmp(p->pw_passwd, pass)) { fclose(master); return p->pw_uid; } } fclose(master); return -1; } void toaru_auth_set_vars(void) { int uid = getuid(); struct passwd * p = getpwuid(uid); if (!p) { char tmp[10]; sprintf(tmp, "%d", uid); setenv("USER", strdup(tmp), 1); setenv("HOME", "/", 1); setenv("SHELL", "/bin/sh", 1); } else { setenv("USER", strdup(p->pw_name), 1); setenv("HOME", strdup(p->pw_dir), 1); setenv("SHELL", strdup(p->pw_shell), 1); setenv("WM_THEME", strdup(p->pw_comment), 1); } endpwent(); setenv("PATH", "/usr/bin:/bin", 0); chdir(getenv("HOME")); } void toaru_auth_set_groups(uid_t uid) { /* Get the username for this uid */ struct passwd * pwd = getpwuid(uid); /* No username? No group memberships! */ if (!pwd) goto no_groups; /* Open the group file */ FILE * groupList = fopen("/etc/group","r"); /* No groups? No membership. */ if (!groupList) goto no_groups; /* Scan through lines of groups. */ #define LINE_LEN 2048 char * pw_blob = malloc(LINE_LEN); int groupCount = 0; gid_t myGroups[32] = {0}; while (!feof(groupList)) { memset(pw_blob, 0x00, LINE_LEN); fgets(pw_blob, LINE_LEN, groupList); if (pw_blob[strlen(pw_blob)-1] == '\n') { pw_blob[strlen(pw_blob)-1] = '\0'; /* erase newline */ } /* Tokenize */ char * memberlist = NULL; char *p, *last; gid_t groupNumber = -1; int i = 0; for ((p = strtok_r(pw_blob, ":", &last)); p; (p = strtok_r(NULL, ":", &last)), i++) { if (i == 2) { groupNumber = atoi(p); } else if (i == 3) { memberlist = p; break; } } if (groupNumber == -1) continue; if (!memberlist) continue; for ((p = strtok_r(memberlist, ",", &last)); p; (p = strtok_r(NULL, ",", &last))) { if (!strcmp(p, pwd->pw_name)) { if (groupCount < 32) { myGroups[groupCount] = groupNumber; groupCount++; } } } } setgroups(groupCount, myGroups); free(pw_blob); fclose(groupList); return; no_groups: setgroups(0, NULL); } void toaru_set_credentials(uid_t uid) { toaru_auth_set_groups(uid); setgid(uid); setuid(uid); toaru_auth_set_vars(); } ================================================ FILE: lib/button.c ================================================ /** * @brief Button "widget" * * Really just a function to render a button... * * @copyright * 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 */ #include #include #include #include static struct TT_Font * _tt_font_thin = NULL; void ttk_button_draw(gfx_context_t * ctx, struct TTKButton * button) { if (button->width == 0) { return; } int hilight = button->hilight & 0xFF; int disabled = button->hilight & 0x100; /* Dark edge */ if (hilight < 3) { struct gradient_definition edge = {button->height, button->y, rgb(166,166,166), rgb(136,136,136)}; draw_rounded_rectangle_pattern(ctx, button->x, button->y, button->width, button->height, 4, gfx_vertical_gradient_pattern, &edge); } /* Sheen */ if (hilight < 2) { draw_rounded_rectangle(ctx, button->x + 1, button->y + 1, button->width - 2, button->height - 2, 3, rgb(238,238,238)); /* Button face - this should normally be a gradient */ if (hilight == 1) { struct gradient_definition face = {button->height-3, button->y + 2, rgb(240,240,240), rgb(230,230,230)}; draw_rounded_rectangle_pattern(ctx, button->x + 2, button->y + 2, button->width - 4, button->height - 3, 2, gfx_vertical_gradient_pattern, &face); } else { struct gradient_definition face = {button->height-3, button->y + 2, rgb(219,219,219), rgb(204,204,204)}; draw_rounded_rectangle_pattern(ctx, button->x + 2, button->y + 2, button->width - 4, button->height - 3, 2, gfx_vertical_gradient_pattern, &face); } } else if (hilight == 2) { struct gradient_definition face = {button->height-2, button->y + 1, rgb(180,180,180), rgb(160,160,160)}; draw_rounded_rectangle_pattern(ctx, button->x + 1, button->y + 1, button->width - 2, button->height - 2, 3, gfx_vertical_gradient_pattern, &face); } if (button->title[0] != '\033') { if (!_tt_font_thin) { _tt_font_thin = tt_font_from_shm("sans-serif"); } tt_set_size(_tt_font_thin, 13); int label_width = tt_string_width(_tt_font_thin, button->title); int centered = (button->width - label_width) / 2; int centered_y = (button->height - 16) / 2; tt_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)); } else { sprite_t * icon = icon_get_16(button->title+1); int centered = button->x + (button->width - icon->width) / 2 + (hilight == 2); int centered_y = button->y + (button->height - icon->height) / 2 + (hilight == 2); if (disabled) { draw_sprite_alpha(ctx, icon, centered, centered_y, 0.5); } else { draw_sprite(ctx, icon, centered, centered_y); } } } ================================================ FILE: lib/confreader.c ================================================ /** * @brief Configuration File Reader * * Reads an implementation of the INI "standard". Note that INI * isn't actually a standard. We support the following: * - ; comments * - foo=bar keyword assignment * - [sections] * * @copyright * 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 */ #include #include #include #define TRACE_APP_NAME "confreader" #define TRACE(...) //#include static void free_hashmap(void * h) { hashmap_free(h); free(h); } static int write_section(FILE * f, hashmap_t * section) { list_t * keys = hashmap_keys(section); foreach(node, keys) { char * key = (char*)node->value; char * value = hashmap_get(section, key); fprintf(f, "%s=%s\n", key, value); } list_free(keys); free(keys); return 0; } int confreader_write(confreader_t * config, const char * file) { FILE * f = fopen(file, "w"); if (!f) return 1; hashmap_t * base = hashmap_get(config->sections, ""); if (base) { write_section(f, base); } list_t * sections = hashmap_keys(config->sections); foreach(node, sections) { char * section = (char*)node->value; if (strcmp(section,"")) { hashmap_t * data = hashmap_get(config->sections, section); fprintf(f, "[%s]\n", section); write_section(f, data); } } return 0; } confreader_t * confreader_create_empty(void) { confreader_t * out = malloc(sizeof(confreader_t)); out->sections = hashmap_create(10); return out; } confreader_t * confreader_load(const char * file) { FILE * f = fopen(file, "r"); if (!f) return NULL; confreader_t * out = confreader_create_empty(); hashmap_t * current_section = hashmap_create(10); current_section->hash_val_free = free_hashmap; hashmap_set(out->sections, "", current_section); if (!f) { /* File does not exist, no configuration values, but continue normally. */ return out; } char tmp[1024]; char tmp2[1024]; while (!feof(f)) { int c = fgetc(f); tmp[0] = '\0'; tmp2[0] = '\0'; if (c == ';') { TRACE("Comment"); while (!feof(f) && fgetc(f) != '\n'); TRACE("Done"); } else if (c == '\n' || c == EOF) { TRACE("blank line or EOF: %d", c); continue; } else if (c == '[') { TRACE("section"); char * foo = tmp; int i; while ((i = fgetc(f)) >= 0) { if (i == ']') break; *foo = i; foo++; *foo = '\0'; } while (!feof(f) && fgetc(f) != '\n'); current_section = hashmap_create(10); TRACE("adding section %s", tmp); hashmap_set(out->sections, tmp, current_section); TRACE("section is over"); } else { TRACE("value"); char * foo = tmp; *foo = c; foo++; int i; while ((i = fgetc(f)) >= 0) { if (i == '=') break; *foo = i; foo++; *foo = '\0'; } if (i != '=') { TRACE("no equals sign"); while (!feof(f) && fgetc(f) != '\n'); continue; } TRACE("="); foo = tmp2; while ((i = fgetc(f)) >= 0) { if (i == '\n') break; *foo = i; foo++; *foo = '\0'; } TRACE("setting value %s to %s", tmp, tmp2); hashmap_set(current_section, tmp, strdup(tmp2)); } } fclose(f); TRACE("done reading"); return out; } void confreader_free(confreader_t * conf) { free_hashmap(conf->sections); free(conf); } char * confreader_get(confreader_t * ctx, char * section, char * value) { if (!ctx) return NULL; hashmap_t * s = hashmap_get(ctx->sections, section); if (!s) { TRACE("section doesn't exist: %s", section); return NULL; } return hashmap_get(s, value); } char * confreader_getd(confreader_t * ctx, char * section, char * value, char * def) { char * result = confreader_get(ctx, section, value); return result ? result : def; } int confreader_int(confreader_t * ctx, char * section, char * value) { char * result = confreader_get(ctx, section, value); if (!result) return 0; return atoi(result); } int confreader_intd(confreader_t * ctx, char * section, char * value, int def) { char * result = confreader_get(ctx, section, value); if (!result) return def; return atoi(result); } ================================================ FILE: lib/decor-fancy.c ================================================ /** * @file lib/decor-fancy.c * @brief "Fancy" decoration theme; the default. * * This is based on an old GTK theme I used to use back in ~2010. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #define TTK_FANCY_PATH "/usr/share/ttk/fancy/" #define TITLEBAR_HEIGHT 33 #define BASE_SIZE 10 #define TOTAL_SCALE 1 #define OUTER_SIZE 6 /* Color for the extra border lines drawn in tiled mode. */ #define BORDER_COLOR rgb(62,62,62) /* Button and title colors */ #define ACTIVE_COLOR rgb(226,226,226) #define INACTIVE_COLOR rgb(147,147,147) static int u_height = TITLEBAR_HEIGHT * TOTAL_SCALE; static int ul_width = BASE_SIZE * TOTAL_SCALE; static int ur_width = BASE_SIZE * TOTAL_SCALE; static int ml_width = BASE_SIZE * TOTAL_SCALE; static int mr_width = BASE_SIZE * TOTAL_SCALE; static int l_height = BASE_SIZE * TOTAL_SCALE; static int ll_width = BASE_SIZE * TOTAL_SCALE; static int lr_width = BASE_SIZE * TOTAL_SCALE; static struct TT_Font * _tt_font = NULL; #define BUTTON_CLOSE 0 #define BUTTON_MAXIMIZE 1 #define BUTTON_MINIMIZE 2 #define BUTTON_UNMAXIMIZE 3 #define ACTIVE 4 #define INACTIVE 13 static sprite_t * sprites[22]; #define TEXT_OFFSET ((window->decorator_flags & DECOR_FLAG_TILED) ? 5 : 10) #define BUTTON_OFFSET ((window->decorator_flags & DECOR_FLAG_TILED) ? 5 : 0) /** * Replaces an old graphics API function from the * very early days of ToaruOS... */ static void init_sprite(int id, char * path) { sprites[id] = malloc(sizeof(sprite_t)); load_sprite(sprites[id], path); } /** * Make a new sprite by cropping down @p from. */ static sprite_t * sprite_crop(sprite_t * from, int x, int y, int w, int h) { sprite_t * dest = create_sprite(w,h,ALPHA_EMBEDDED); gfx_context_t * sctx = init_graphics_sprite(dest); draw_fill(sctx, rgba(0,0,0,0)); draw_sprite(sctx, from, -x, -y); free(sctx); return dest; } /** * Chop up a spritesheet into edge/corner pieces. */ static void create_borders_from_spritesheet(int spriteIndex, const char * path) { sprite_t tmp; load_sprite(&tmp, path); int um_width = 1; /* These need to always be 1... tmp.width - ul_width - ur_width; */ int m_height = 1; /* These need to always be 1... tmp.height - u_height - l_height; */ int lm_width = 1; /* These need to always be 1... tmp.width - ll_width - lr_width; */ int c = ul_width; int r = tmp.width - ur_width; int m = u_height; int l = tmp.height - l_height; sprites[spriteIndex + 0] = sprite_crop(&tmp, 0, 0, ul_width, u_height); sprites[spriteIndex + 1] = sprite_crop(&tmp, c, 0, um_width, u_height); sprites[spriteIndex + 2] = sprite_crop(&tmp, r, 0, ur_width, u_height); sprites[spriteIndex + 3] = sprite_crop(&tmp, 0, m, ml_width, m_height); sprites[spriteIndex + 4] = sprite_crop(&tmp, r, m, mr_width, m_height); sprites[spriteIndex + 5] = sprite_crop(&tmp, 0, l, ll_width, l_height); sprites[spriteIndex + 6] = sprite_crop(&tmp, c, l, lm_width, l_height); sprites[spriteIndex + 7] = sprite_crop(&tmp, r, l, lr_width, l_height); free(tmp.bitmap); } static int get_bounds_fancy(yutani_window_t * window, struct decor_bounds * bounds) { if (window == NULL || !(window->decorator_flags & DECOR_FLAG_TILED)) { bounds->top_height = TITLEBAR_HEIGHT * TOTAL_SCALE; bounds->bottom_height = OUTER_SIZE * TOTAL_SCALE; bounds->left_width = OUTER_SIZE * TOTAL_SCALE; bounds->right_width = OUTER_SIZE * TOTAL_SCALE; } else { /* Any "exposed" edge gets an extra pixel. */ bounds->top_height = 27 * TOTAL_SCALE + !(window->decorator_flags & DECOR_FLAG_TILE_UP); bounds->bottom_height = !(window->decorator_flags & DECOR_FLAG_TILE_DOWN); bounds->left_width = !(window->decorator_flags & DECOR_FLAG_TILE_LEFT); bounds->right_width = !(window->decorator_flags & DECOR_FLAG_TILE_RIGHT); } bounds->width = bounds->left_width + bounds->right_width; bounds->height = bounds->top_height + bounds->bottom_height; return 0; } #define BUTTON_PAD 5 static void render_decorations_fancy(yutani_window_t * window, gfx_context_t * ctx, char * title, int decors_active) { int width = window->width; int height = window->height; struct decor_bounds bounds; get_bounds_fancy(window, &bounds); for (int j = 0; j < (int)bounds.top_height; ++j) { for (int i = 0; i < width; ++i) { GFX(ctx,i,j) = 0; } } decors_active = (decors_active == DECOR_INACTIVE) ? INACTIVE : ACTIVE; if ((window->decorator_flags & DECOR_FLAG_TILED)) { for (int i = 0; i < width; ++i) { draw_sprite(ctx, sprites[decors_active + 1], i, -6 * TOTAL_SCALE + !(window->decorator_flags & DECOR_FLAG_TILE_UP)); } uint32_t clear_color = BORDER_COLOR; if (!(window->decorator_flags & DECOR_FLAG_TILE_DOWN)) { /* Draw bottom line */ for (int i = 0; i < (int)window->width; ++i) { GFX(ctx,i,window->height-1) = clear_color; } } if (!(window->decorator_flags & DECOR_FLAG_TILE_LEFT)) { /* Draw left line */ for (int i = 0; i < (int)window->height; ++i) { GFX(ctx,0,i) = clear_color; } } if (!(window->decorator_flags & DECOR_FLAG_TILE_RIGHT)) { /* Draw right line */ for (int i = 0; i < (int)window->height; ++i) { GFX(ctx,window->width-1,i) = clear_color; } } } else { uint32_t clear_color = 0x000000; for (int j = (int)bounds.top_height; j < height - (int)bounds.bottom_height; ++j) { for (int i = 0; i < (int)bounds.left_width; ++i) { GFX(ctx,i,j) = clear_color; } for (int i = width - (int)bounds.right_width; i < width; ++i) { GFX(ctx,i,j) = clear_color; } } for (int j = height - (int)bounds.bottom_height; j < height; ++j) { for (int i = 0; i < width; ++i) { GFX(ctx,i,j) = clear_color; } } draw_sprite(ctx, sprites[decors_active + 0], 0, 0); for (int i = 0; i < width - (ul_width + ur_width); ++i) { draw_sprite(ctx, sprites[decors_active + 1], i + ul_width, 0); } draw_sprite(ctx, sprites[decors_active + 2], width - ur_width, 0); for (int i = 0; i < height - (u_height + l_height); ++i) { draw_sprite(ctx, sprites[decors_active + 3], 0, i + u_height); draw_sprite(ctx, sprites[decors_active + 4], width - mr_width, i + u_height); } draw_sprite(ctx, sprites[decors_active + 5], 0, height - l_height); for (int i = 0; i < width - (ll_width + lr_width); ++i) { draw_sprite(ctx, sprites[decors_active + 6], i + ll_width, height - l_height); } draw_sprite(ctx, sprites[decors_active + 7], width - lr_width, height - l_height); } #define EXTRA_SPACE 120 uint32_t title_color = (decors_active == ACTIVE) ? ACTIVE_COLOR : INACTIVE_COLOR; int buttons_width = (!(window->decorator_flags & DECOR_FLAG_NO_MAXIMIZE)) ? 72 : 28; int usable_width = width - bounds.width - (2 * buttons_width + 10) * TOTAL_SCALE; tt_set_size(_tt_font, 12 * TOTAL_SCALE); int title_width = tt_string_width(_tt_font, title); if (title_width > usable_width) { usable_width += buttons_width * TOTAL_SCALE; if (usable_width > 0) { char * tmp_title = tt_ellipsify(title, 12 * TOTAL_SCALE, _tt_font, usable_width, &title_width); int title_offset = bounds.left_width + 10 * TOTAL_SCALE; tt_draw_string(ctx, _tt_font, title_offset, (TEXT_OFFSET + 14) * TOTAL_SCALE, tmp_title, title_color); free(tmp_title); } } else { int title_offset = buttons_width * TOTAL_SCALE + bounds.left_width + 10 * TOTAL_SCALE + (usable_width / 2) - (title_width / 2); tt_draw_string(ctx, _tt_font, title_offset, (TEXT_OFFSET + 14) * TOTAL_SCALE, title, title_color); } uint32_t h_color = rgb(100,100,100); uint32_t i_color = (decor_hover_window == window && decor_hover_button) ? ACTIVE_COLOR : title_color; if (width + (BUTTON_OFFSET - 28) * TOTAL_SCALE > bounds.left_width) { if (decor_hover_window == window && decor_hover_button == DECOR_CLOSE) { draw_rounded_rectangle(ctx, width + (BUTTON_OFFSET - 28 - BUTTON_PAD) * TOTAL_SCALE, (16 - BUTTON_OFFSET - BUTTON_PAD) * TOTAL_SCALE, 8 + BUTTON_PAD * 2, 8 + BUTTON_PAD * 2, 4, h_color); } draw_sprite_alpha_paint(ctx, sprites[BUTTON_CLOSE], width + (BUTTON_OFFSET - 28) * TOTAL_SCALE, (16 - BUTTON_OFFSET) * TOTAL_SCALE, 1.0, i_color); if (width + (BUTTON_OFFSET - 50) * TOTAL_SCALE > bounds.left_width) { if (!(window->decorator_flags & DECOR_FLAG_NO_MAXIMIZE)) { if (decor_hover_window == window && decor_hover_button == DECOR_MAXIMIZE) { draw_rounded_rectangle(ctx, width + (BUTTON_OFFSET - 50 - BUTTON_PAD) * TOTAL_SCALE, (16 - BUTTON_OFFSET - BUTTON_PAD) * TOTAL_SCALE, 8 + BUTTON_PAD * 2, 8 + BUTTON_PAD * 2, 4, h_color); } draw_sprite_alpha_paint(ctx, sprites[(window->decorator_flags & DECOR_FLAG_TILED) ? BUTTON_UNMAXIMIZE : BUTTON_MAXIMIZE], width + (BUTTON_OFFSET - 50) * TOTAL_SCALE, (16 - BUTTON_OFFSET) * TOTAL_SCALE, 1.0, i_color); if (width + (BUTTON_OFFSET - 72) * TOTAL_SCALE > bounds.left_width) { if (decor_hover_window == window && decor_hover_button == DECOR_MINIMIZE) { draw_rounded_rectangle(ctx, width + (BUTTON_OFFSET - 72 - BUTTON_PAD) * TOTAL_SCALE, (16 - BUTTON_OFFSET - BUTTON_PAD) * TOTAL_SCALE, 8 + BUTTON_PAD * 2, 8 + BUTTON_PAD * 2, 4, h_color); } draw_sprite_alpha_paint(ctx, sprites[BUTTON_MINIMIZE], width + (BUTTON_OFFSET - 72) * TOTAL_SCALE, (16 - BUTTON_OFFSET) * TOTAL_SCALE, 1.0, i_color); } } } } } static int check_button_press_fancy(yutani_window_t * window, int x, int y) { if (y >= (16 - BUTTON_OFFSET - BUTTON_PAD) * TOTAL_SCALE && y <= (16 - BUTTON_OFFSET + 8 + BUTTON_PAD) * TOTAL_SCALE ) { if (x >= (int)window->width + (BUTTON_OFFSET - 28 - BUTTON_PAD) * TOTAL_SCALE && x <= (int)window->width + (BUTTON_OFFSET - 28 + 8 + BUTTON_PAD) * TOTAL_SCALE) { return DECOR_CLOSE; } if (!(window->decorator_flags & DECOR_FLAG_NO_MAXIMIZE)) { if (x >= (int)window->width + (BUTTON_OFFSET - 50 - BUTTON_PAD) * TOTAL_SCALE && x <= (int)window->width + (BUTTON_OFFSET - 50 + 8 + BUTTON_PAD) * TOTAL_SCALE) { return DECOR_MAXIMIZE; } if (x >= (int)window->width + (BUTTON_OFFSET - 72 - BUTTON_PAD) * TOTAL_SCALE && x <= (int)window->width + (BUTTON_OFFSET - 72 + 8 + BUTTON_PAD) * TOTAL_SCALE) { return DECOR_MINIMIZE; } } if (x >= (int)window->width + (BUTTON_OFFSET - 72 - BUTTON_PAD) * TOTAL_SCALE && x <= (int)window->width + (BUTTON_OFFSET - 28 + 8 + BUTTON_PAD) * TOTAL_SCALE) { return DECOR_OTHER; } } return 0; } void decor_init() { init_sprite(BUTTON_CLOSE, TTK_FANCY_PATH "button-close.png"); init_sprite(BUTTON_MAXIMIZE, TTK_FANCY_PATH "button-maximize.png"); init_sprite(BUTTON_MINIMIZE, TTK_FANCY_PATH "button-minimize.png"); init_sprite(BUTTON_UNMAXIMIZE, TTK_FANCY_PATH "button-unmaximize.png"); create_borders_from_spritesheet(ACTIVE, TTK_FANCY_PATH "borders-active.png"); create_borders_from_spritesheet(INACTIVE, TTK_FANCY_PATH "borders-inactive.png"); decor_render_decorations = render_decorations_fancy; decor_check_button_press = check_button_press_fancy; decor_get_bounds = get_bounds_fancy; _tt_font = tt_font_from_shm("sans-serif.bold"); } ================================================ FILE: lib/decorations.c ================================================ /** * @brief Client-side Window Decoration library * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2018 K. Lange */ #include #include #include #include #include #include #include #include #define TEXT_OFFSET_X 10 #define TEXT_OFFSET_Y 3 #define BORDERCOLOR rgb(59,59,59) #define BORDERCOLOR_INACTIVE rgb(30,30,30) #define TEXTCOLOR rgb(230,230,230) #define TEXTCOLOR_INACTIVE rgb(140,140,140) void (*decor_render_decorations)(yutani_window_t *, gfx_context_t *, char *, int) = NULL; int (*decor_check_button_press)(yutani_window_t *, int x, int y) = NULL; int (*decor_get_bounds)(yutani_window_t *, struct decor_bounds *) = NULL; static void (*callback_close)(yutani_window_t *) = NULL; static void (*callback_resize)(yutani_window_t *) = NULL; static void (*callback_maximize)(yutani_window_t *) = NULL; static int close_enough(struct yutani_msg_window_mouse_event * me) { return (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2.0) + pow(me->new_y - me->old_y, 2.0)) < 10.0); } static struct TT_Font * tt_font = NULL; static void render_decorations_simple(yutani_window_t * window, gfx_context_t * ctx, char * title, int decors_active) { uint32_t color = BORDERCOLOR; if (decors_active == DECOR_INACTIVE) { color = BORDERCOLOR_INACTIVE; } for (int i = 0; i < (int)window->height; ++i) { GFX(ctx, 0, i) = color; GFX(ctx, window->width - 1, i) = color; } for (int i = 1; i < (int)24; ++i) { for (int j = 1; j < (int)window->width - 1; ++j) { GFX(ctx, j, i) = color; } } tt_set_size(tt_font, 12); uint32_t textcolor = (decors_active == DECOR_INACTIVE) ? TEXTCOLOR_INACTIVE : TEXTCOLOR; tt_draw_string(ctx, tt_font, TEXT_OFFSET_X, TEXT_OFFSET_Y + 12, title, textcolor); tt_draw_string(ctx, tt_font, window->width - 20, TEXT_OFFSET_Y + 12, "x", textcolor); for (uint32_t i = 0; i < window->width; ++i) { GFX(ctx, i, 0) = color; GFX(ctx, i, 24 - 1) = color; GFX(ctx, i, window->height - 1) = color; } } static int check_button_press_simple(yutani_window_t * window, int x, int y) { if (x >= (int)window->width - 20 && x <= (int)window->width - 2 && y >= 2) { return DECOR_CLOSE; } return 0; } static int get_bounds_simple(yutani_window_t * window, struct decor_bounds * bounds) { /* Does not change with window state */ bounds->top_height = 24; bounds->bottom_height = 1; bounds->left_width = 1; bounds->right_width = 1; bounds->width = bounds->left_width + bounds->right_width; bounds->height = bounds->top_height + bounds->bottom_height; return 0; } static void initialize_simple() { decor_render_decorations = render_decorations_simple; decor_check_button_press = check_button_press_simple; decor_get_bounds = get_bounds_simple; tt_font = tt_font_from_shm("sans-serif"); } void render_decorations(yutani_window_t * window, gfx_context_t * ctx, char * title) { if (!window) return; window->decorator_flags |= DECOR_FLAG_DECORATED; if (window->focused || !hashmap_is_empty(menu_get_windows_hash())) { decor_render_decorations(window, ctx, title, DECOR_ACTIVE); } else { decor_render_decorations(window, ctx, title, DECOR_INACTIVE); } } void render_decorations_inactive(yutani_window_t * window, gfx_context_t * ctx, char * title) { if (!window) return; window->decorator_flags |= DECOR_FLAG_DECORATED; decor_render_decorations(window, ctx, title, DECOR_INACTIVE); } static void _decor_maximize(yutani_t * yctx, yutani_window_t * window) { if (callback_maximize) { callback_maximize(window); } else { yutani_special_request(yctx, window, YUTANI_SPECIAL_REQUEST_MAXIMIZE); } } static void _decor_minimize(yutani_t * yctx, yutani_window_t * window) { yutani_special_request(yctx, window, YUTANI_SPECIAL_REQUEST_MINIMIZE); } static yutani_window_t * _decor_menu_owner_window = NULL; static struct MenuList * _decor_menu = NULL; static void _decor_start_move(struct MenuEntry * self) { if (!_decor_menu_owner_window) return; yutani_focus_window(_decor_menu_owner_window->ctx, _decor_menu_owner_window->wid); yutani_window_drag_start(_decor_menu_owner_window->ctx, _decor_menu_owner_window); } static void _decor_start_maximize(struct MenuEntry * self) { if (!_decor_menu_owner_window) return; _decor_maximize(_decor_menu_owner_window->ctx, _decor_menu_owner_window); yutani_focus_window(_decor_menu_owner_window->ctx, _decor_menu_owner_window->wid); } static void _decor_start_minimize(struct MenuEntry * self) { if (!_decor_menu_owner_window) return; _decor_minimize(_decor_menu_owner_window->ctx, _decor_menu_owner_window); } static void _decor_close(struct MenuEntry * self) { if (!_decor_menu_owner_window) return; yutani_special_request(_decor_menu_owner_window->ctx, _decor_menu_owner_window, YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE); } yutani_window_t * decor_show_default_menu(yutani_window_t * window, int x, int y) { if (_decor_menu->window) return NULL; _decor_menu_owner_window = window; menu_show_at(_decor_menu, window, x - window->x, y - window->y); return _decor_menu->window; } void init_decorations() { char * tmp = getenv("WM_THEME"); char * theme = tmp ? strdup(tmp) : NULL; _decor_menu = menu_create(); menu_insert(_decor_menu, menu_create_normal(NULL, NULL, "Maximize", _decor_start_maximize)); menu_insert(_decor_menu, menu_create_normal(NULL, NULL, "Minimize", _decor_start_minimize)); menu_insert(_decor_menu, menu_create_normal(NULL, NULL, "Move", _decor_start_move)); menu_insert(_decor_menu, menu_create_separator()); menu_insert(_decor_menu, menu_create_normal(NULL, NULL, "Close", _decor_close)); if (!theme || !strcmp(theme, "simple")) { initialize_simple(); } else { char * options = strchr(theme,','); if (options) { *options = '\0'; options++; } char lib_name[100]; sprintf(lib_name, "libtoaru_decor-%s.so", theme); void * theme_lib = dlopen(lib_name, 0); if (!theme_lib) { goto _theme_error; } void (*theme_init)(char *) = dlsym(theme_lib, "decor_init"); if (!theme_init) { goto _theme_error; } theme_init(options); return; _theme_error: fprintf(stderr, "decorations: could not load theme `%s`: %s\n", theme, dlerror()); initialize_simple(); } } void decor_set_close_callback(void (*callback)(yutani_window_t *)) { callback_close = callback; } void decor_set_resize_callback(void (*callback)(yutani_window_t *)) { callback_resize = callback; } void decor_set_maximize_callback(void (*callback)(yutani_window_t *)) { callback_maximize = callback; } static int within_decors(yutani_window_t * window, int x, int y) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); if ((x <= (int)bounds.left_width || x >= (int)window->width - (int)bounds.right_width) && (x > 0 && x < (int)window->width)) return 1; if ((y <= (int)bounds.top_height || y >= (int)window->height - (int)bounds.bottom_height) && (y > 0 && y < (int)window->height)) return 1; return 0; } #define LEFT_SIDE (me->new_x <= (int)bounds.left_width) #define RIGHT_SIDE (me->new_x >= (int)window->width - (int)bounds.right_width) #define TOP_SIDE (me->new_y <= (int)bounds.top_height) #define BOTTOM_SIDE (me->new_y >= (int)window->height - (int)bounds.bottom_height) static yutani_scale_direction_t check_resize_direction(struct yutani_msg_window_mouse_event * me, yutani_window_t * window) { struct decor_bounds bounds; decor_get_bounds(window, &bounds); yutani_scale_direction_t resize_direction = SCALE_NONE; if (LEFT_SIDE && !TOP_SIDE && !BOTTOM_SIDE) { resize_direction = SCALE_LEFT; } else if (RIGHT_SIDE && !TOP_SIDE && !BOTTOM_SIDE) { resize_direction = SCALE_RIGHT; } else if (BOTTOM_SIDE && !LEFT_SIDE && !RIGHT_SIDE) { resize_direction = SCALE_DOWN; } else if (BOTTOM_SIDE && LEFT_SIDE) { resize_direction = SCALE_DOWN_LEFT; } else if (BOTTOM_SIDE && RIGHT_SIDE) { resize_direction = SCALE_DOWN_RIGHT; } else if (TOP_SIDE && LEFT_SIDE) { resize_direction = SCALE_UP_LEFT; } else if (TOP_SIDE && RIGHT_SIDE) { resize_direction = SCALE_UP_RIGHT; } else if (TOP_SIDE && (me->new_y < 5)) { resize_direction = SCALE_UP; } return resize_direction; } static yutani_scale_direction_t old_resize_direction = SCALE_NONE; int decor_hover_button = 0; yutani_window_t * decor_hover_window = NULL; int decor_handle_event(yutani_t * yctx, yutani_msg_t * m) { if (m) { switch (m->type) { case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; yutani_window_t * window = hashmap_get(yctx->windows, (void*)(uintptr_t)me->wid); struct decor_bounds bounds; decor_get_bounds(window, &bounds); if (!window) return 0; if (!(window->decorator_flags & DECOR_FLAG_DECORATED)) return 0; if (me->command == YUTANI_MOUSE_EVENT_LEAVE && decor_hover_window == window) { decor_hover_window = NULL; decor_hover_button = 0; yutani_internal_refocus(yctx, window); return DECOR_REDRAW; } if (within_decors(window, me->new_x, me->new_y)) { int button = decor_check_button_press(window, me->new_x, me->new_y); if (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { if (!button || button == DECOR_OTHER) { /* Resize edges */ yutani_scale_direction_t resize_direction = check_resize_direction(me, window); if (resize_direction != SCALE_NONE) { yutani_window_resize_start(yctx, window, resize_direction); } if (me->new_y < (int)bounds.top_height && resize_direction == SCALE_NONE) { yutani_window_drag_start(yctx, window); } return DECOR_OTHER; } } if (!button && (me->buttons & YUTANI_MOUSE_BUTTON_RIGHT)) { return DECOR_RIGHT; } if (me->command == YUTANI_MOUSE_EVENT_MOVE) { if (!button) { /* Resize edges */ yutani_scale_direction_t resize_direction = check_resize_direction(me, window); if (resize_direction != old_resize_direction) { if (resize_direction == SCALE_NONE) { yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET); } else { switch (resize_direction) { case SCALE_UP: case SCALE_DOWN: yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_VERTICAL); break; case SCALE_LEFT: case SCALE_RIGHT: yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_HORIZONTAL); break; case SCALE_DOWN_RIGHT: case SCALE_UP_LEFT: yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_UP_DOWN); break; case SCALE_DOWN_LEFT: case SCALE_UP_RIGHT: yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESIZE_DOWN_UP); break; case SCALE_AUTO: case SCALE_NONE: break; } } old_resize_direction = resize_direction; } } else if (old_resize_direction != SCALE_NONE) { yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET); old_resize_direction = SCALE_NONE; } } if (me->command == YUTANI_MOUSE_EVENT_CLICK || close_enough(me)) { /* Determine if we clicked on a button */ switch (button) { case DECOR_CLOSE: if (callback_close) callback_close(window); break; case DECOR_RESIZE: if (callback_resize) callback_resize(window); break; case DECOR_MAXIMIZE: _decor_maximize(yctx, window); break; case DECOR_MINIMIZE: _decor_minimize(yctx, window); break; default: break; } decor_hover_window = NULL; decor_hover_button = 0; yutani_internal_refocus(yctx, window); return button; } if (button != decor_hover_button || window != decor_hover_window) { decor_hover_button = button; decor_hover_window = window; yutani_internal_refocus(yctx, window); return DECOR_REDRAW; } } else { if (old_resize_direction != SCALE_NONE) { yutani_window_show_mouse(yctx, window, YUTANI_CURSOR_TYPE_RESET); old_resize_direction = SCALE_NONE; } if (decor_hover_window == window) { decor_hover_button = 0; decor_hover_window = NULL; yutani_internal_refocus(yctx, window); return DECOR_REDRAW; } } } break; } } return 0; } ================================================ FILE: lib/graphics.c ================================================ /** * @brief Generic Graphics library for ToaruOS * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2021 K. Lange */ #include #include #include #include #include #include #include #if !defined(NO_SSE) && defined(__x86_64__) #include #include #endif #include #include static inline int32_t min(int32_t a, int32_t b) { return (a < b) ? a : b; } static inline int32_t max(int32_t a, int32_t b) { return (a > b) ? a : b; } static inline uint16_t min16(uint16_t a, uint16_t b) { return (a < b) ? a : b; } static inline uint16_t max16(uint16_t a, uint16_t b) { return (a > b) ? a : b; } #define fmax(a,b) ((a) > (b) ? (a) : (b)) #define fmin(a,b) ((a) < (b) ? (a) : (b)) static inline int _is_in_clip(gfx_context_t * ctx, int32_t y) { if (!ctx->clips) return 1; if (y < 0 || y >= ctx->clips_size) return 1; return ctx->clips[y]; } void gfx_add_clip(gfx_context_t * ctx, int32_t x, int32_t y, int32_t w, int32_t h) { (void)x; (void)w; // TODO Horizontal clipping if (!ctx->clips) { ctx->clips = malloc(ctx->height); memset(ctx->clips, 0, ctx->height); ctx->clips_size = ctx->height; } for (int i = max(y,0); i < min(y+h,ctx->clips_size); ++i) { ctx->clips[i] = 1; } } void gfx_clear_clip(gfx_context_t * ctx) { if (ctx->clips) { memset(ctx->clips, 0, ctx->clips_size); } } void gfx_no_clip(gfx_context_t * ctx) { void * tmp = ctx->clips; if (!tmp) return; ctx->clips = NULL; free(tmp); } /* Pointer to graphics memory */ void flip(gfx_context_t * ctx) { if (ctx->clips) { for (size_t i = 0; i < ctx->height; ++i) { if (_is_in_clip(ctx,i)) { memcpy(&ctx->buffer[i*GFX_S(ctx)], &ctx->backbuffer[i*GFX_S(ctx)], 4 * ctx->width); } } } else { memcpy(ctx->buffer, ctx->backbuffer, ctx->size); } } void gfx_flip_24bit(gfx_context_t * ctx) { for (size_t y = 0; y < ctx->height; ++y) { if (_is_in_clip(ctx,y)) { for (size_t x = 0; x < ctx->width; ++x) { ((uint8_t*)ctx->buffer)[y * ctx->_true_stride + x * 3] = ((uint8_t*)ctx->backbuffer)[y * ctx->stride + x * 4]; ((uint8_t*)ctx->buffer)[y * ctx->_true_stride + x * 3+1] = ((uint8_t*)ctx->backbuffer)[y * ctx->stride + x * 4+1]; ((uint8_t*)ctx->buffer)[y * ctx->_true_stride + x * 3+2] = ((uint8_t*)ctx->backbuffer)[y * ctx->stride + x * 4+2]; } } } } void clearbuffer(gfx_context_t * ctx) { memset(ctx->backbuffer, 0, ctx->size); } /* Deprecated */ static int framebuffer_fd = 0; gfx_context_t * init_graphics_fullscreen() { gfx_context_t * out = malloc(sizeof(gfx_context_t)); out->clips = NULL; out->buffer = NULL; if (!framebuffer_fd) { framebuffer_fd = open("/dev/fb0", 0, 0); } if (framebuffer_fd < 0) { /* oh shit */ free(out); return NULL; } ioctl(framebuffer_fd, IO_VID_WIDTH, &out->width); ioctl(framebuffer_fd, IO_VID_HEIGHT, &out->height); ioctl(framebuffer_fd, IO_VID_DEPTH, &out->depth); ioctl(framebuffer_fd, IO_VID_STRIDE, &out->stride); ioctl(framebuffer_fd, IO_VID_ADDR, &out->buffer); ioctl(framebuffer_fd, IO_VID_SIGNAL, NULL); out->size = GFX_H(out) * GFX_S(out); if (out->depth == 24) { out->depth = 32; out->_true_stride = out->stride; out->stride = 4 * GFX_W(out); out->size = 0; } out->backbuffer = out->buffer; return out; } uint32_t framebuffer_stride(void) { uint32_t stride; ioctl(framebuffer_fd, IO_VID_STRIDE, &stride); return stride; } gfx_context_t * init_graphics_fullscreen_double_buffer() { gfx_context_t * out = init_graphics_fullscreen(); if (!out) return NULL; out->backbuffer = malloc(GFX_S(out) * GFX_H(out)); return out; } gfx_context_t * init_graphics_subregion(gfx_context_t * base, int x, int y, int width, int height) { gfx_context_t * out = malloc(sizeof(gfx_context_t)); out->clips = NULL; out->depth = 32; out->width = width; out->height = height; out->stride = base->stride; out->backbuffer = base->backbuffer + (base->stride * y) + x * 4; out->buffer = base->buffer + (base->stride * y) + x * 4; if (base->clips) { for (int _y = 0; _y < height; ++_y) { if (_is_in_clip(base, y + _y)) { gfx_add_clip(out,0,_y,width,1); } } } out->size = 0; /* don't allow flip or clear operations */ return out; } void reinit_graphics_fullscreen(gfx_context_t * out) { ioctl(framebuffer_fd, IO_VID_WIDTH, &out->width); ioctl(framebuffer_fd, IO_VID_HEIGHT, &out->height); ioctl(framebuffer_fd, IO_VID_DEPTH, &out->depth); ioctl(framebuffer_fd, IO_VID_STRIDE, &out->stride); out->size = GFX_H(out) * GFX_S(out); if (out->clips && out->clips_size != out->height) { free(out->clips); out->clips = NULL; out->clips_size = 0; } if (out->buffer != out->backbuffer) { ioctl(framebuffer_fd, IO_VID_ADDR, &out->buffer); out->backbuffer = realloc(out->backbuffer, GFX_S(out) * GFX_H(out)); } else { ioctl(framebuffer_fd, IO_VID_ADDR, &out->buffer); out->backbuffer = out->buffer; } } gfx_context_t * init_graphics_sprite(sprite_t * sprite) { gfx_context_t * out = malloc(sizeof(gfx_context_t)); out->clips = NULL; out->width = sprite->width; out->stride = sprite->width * sizeof(uint32_t); out->height = sprite->height; out->depth = 32; out->size = GFX_H(out) * GFX_W(out) * GFX_B(out); out->buffer = (char *)sprite->bitmap; out->backbuffer = out->buffer; return out; } sprite_t * create_sprite(size_t width, size_t height, int alpha) { sprite_t * out = malloc(sizeof(sprite_t)); /* uint16_t width; uint16_t height; uint32_t * bitmap; uint32_t * masks; uint32_t blank; uint8_t alpha; */ out->width = width; out->height = height; out->bitmap = malloc(sizeof(uint32_t) * out->width * out->height); out->masks = NULL; out->blank = 0x00000000; out->alpha = alpha; return out; } void sprite_free(sprite_t * sprite) { if (sprite->masks) { free(sprite->masks); } free(sprite->bitmap); free(sprite); } inline uint32_t rgb(uint8_t r, uint8_t g, uint8_t b) { return 0xFF000000 | (r << 16) | (g << 8) | (b); } inline uint32_t rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return (a << 24U) | (r << 16) | (g << 8) | (b); } uint32_t alpha_blend(uint32_t bottom, uint32_t top, uint32_t mask) { uint8_t a = _RED(mask); uint8_t red = (_RED(bottom) * (255 - a) + _RED(top) * a) / 255; uint8_t gre = (_GRE(bottom) * (255 - a) + _GRE(top) * a) / 255; uint8_t blu = (_BLU(bottom) * (255 - a) + _BLU(top) * a) / 255; uint8_t alp = (int)a + (int)_ALP(bottom) > 255 ? 255 : a + _ALP(bottom); return rgba(red,gre,blu, alp); } inline uint32_t alpha_blend_rgba(uint32_t bottom, uint32_t top) { if (_ALP(bottom) == 0) return top; if (_ALP(top) == 255) return top; if (_ALP(top) == 0) return bottom; uint8_t a = _ALP(top); uint16_t t = 0xFF ^ a; uint8_t d_r = _RED(top) + (((uint32_t)(_RED(bottom) * t + 0x80) * 0x101) >> 16UL); uint8_t d_g = _GRE(top) + (((uint32_t)(_GRE(bottom) * t + 0x80) * 0x101) >> 16UL); uint8_t d_b = _BLU(top) + (((uint32_t)(_BLU(bottom) * t + 0x80) * 0x101) >> 16UL); uint8_t d_a = _ALP(top) + (((uint32_t)(_ALP(bottom) * t + 0x80) * 0x101) >> 16UL); return rgba(d_r, d_g, d_b, d_a); } uint32_t premultiply(uint32_t color) { uint16_t a = _ALP(color); uint16_t r = _RED(color); uint16_t g = _GRE(color); uint16_t b = _BLU(color); r = r * a / 255; g = g * a / 255; b = b * a / 255; return rgba(r,g,b,a); } static inline int clamp(int a, int l, int h) { return a < l ? l : (a > h ? h : a); } static void _box_blur_horizontal(gfx_context_t * _src, int radius) { int w = _src->width; int h = _src->height; int half_radius = radius / 2; uint32_t * out_color = calloc(sizeof(uint32_t), w); for (int y = 0; y < h; y++) { int hits = 0; int r = 0; int g = 0; int b = 0; int a = 0; for (int x = -half_radius; x < w; x++) { int old_p = x - half_radius - 1; if (old_p >= 0) { uint32_t col = GFX(_src, clamp(old_p,0,w-1), y); if (col) { r -= _RED(col); g -= _GRE(col); b -= _BLU(col); a -= _ALP(col); } hits--; } int newPixel = x + half_radius; if (newPixel < w) { uint32_t col = GFX(_src, clamp(newPixel,0,w-1), y); if (col != 0) { r += _RED(col); g += _GRE(col); b += _BLU(col); a += _ALP(col); } hits++; } if (x >= 0 && x < w) { out_color[x] = rgba(r / hits, g / hits, b / hits, a / hits); } } if (!_is_in_clip(_src, y)) continue; for (int x = 0; x < w; x++) { GFX(_src,x,y) = out_color[x]; } } free(out_color); } static void _box_blur_vertical(gfx_context_t * _src, int radius) { int w = _src->width; int h = _src->height; int half_radius = radius / 2; uint32_t * out_color = calloc(sizeof(uint32_t), h); for (int x = 0; x < w; x++) { int hits = 0; int r = 0; int g = 0; int b = 0; int a = 0; for (int y = -half_radius; y < h; y++) { int old_p = y - half_radius - 1; if (old_p >= 0) { uint32_t col = GFX(_src,x,clamp(old_p,0,h-1)); if (col != 0) { r -= _RED(col); g -= _GRE(col); b -= _BLU(col); a -= _ALP(col); } hits--; } int newPixel = y + half_radius; if (newPixel < h) { uint32_t col = GFX(_src,x,clamp(newPixel,0,h-1)); if (col != 0) { r += _RED(col); g += _GRE(col); b += _BLU(col); a += _ALP(col); } hits++; } if (y >= 0 && y < h) { out_color[y] = rgba(r / hits, g / hits, b / hits, a / hits); } } for (int y = 0; y < h; y++) { if (!_is_in_clip(_src, y)) continue; GFX(_src,x,y) = out_color[y]; } } free(out_color); } void blur_context_box(gfx_context_t * _src, int radius) { _box_blur_horizontal(_src,radius); _box_blur_vertical(_src,radius); } void blur_from_into(gfx_context_t * _src, gfx_context_t * _dest, int radius) { draw_fill(_dest, rgb(255,0,0)); } static int (*load_sprite_jpg)(sprite_t *, const char *) = NULL; static int (*load_sprite_png)(sprite_t *, const char *) = NULL; static void _load_format_libraries() { void * _lib_jpeg = dlopen("libtoaru_jpeg.so", 0); if (_lib_jpeg) load_sprite_jpg = dlsym(_lib_jpeg, "load_sprite_jpg"); void * _lib_png = dlopen("libtoaru_png.so", 0); if (_lib_png) load_sprite_png = dlsym(_lib_png, "load_sprite_png"); } static const char * extension_from_filename(const char * filename) { const char * ext = strrchr(filename, '.'); if (ext && *ext == '.') return ext + 1; return ""; } int load_sprite(sprite_t * sprite, const char * filename) { static int librariesLoaded = 0; if (!librariesLoaded) { _load_format_libraries(); librariesLoaded = 1; } const char * ext = extension_from_filename(filename); if (!strcmp(ext,"png") || !strcmp(ext,"sdf")) return load_sprite_png ? load_sprite_png(sprite, filename) : 1; if (!strcmp(ext,"jpg") || !strcmp(ext,"jpeg")) return load_sprite_jpg ? load_sprite_jpg(sprite, filename) : 1; /* Fall back to bitmap */ return load_sprite_bmp(sprite, filename); } int load_sprite_bmp(sprite_t * sprite, const char * filename) { /* Open the requested binary */ FILE * image = fopen(filename, "r"); if (!image) return 1; long image_size= 0; fseek(image, 0, SEEK_END); image_size = ftell(image); fseek(image, 0, SEEK_SET); if (image_size < 16) { fclose(image); return 1; } /* Alright, we have the length */ char * bufferb = malloc(image_size); fread(bufferb, image_size, 1, image); if (bufferb[0] == 'B' && bufferb[1] == 'M') { /* Bitmaps */ uint16_t x = 0; /* -> 212 */ uint16_t y = 0; /* -> 68 */ /* Get the width / height of the image */ signed int *bufferi = (signed int *)((uintptr_t)bufferb + 2); uint32_t width = bufferi[4]; uint32_t height = bufferi[5]; uint16_t bpp = bufferi[6] / 0x10000; uint32_t row_width = (bpp * width + 31) / 32 * 4; /* Skip right to the important part */ size_t i = bufferi[2]; sprite->width = width; sprite->height = height; sprite->bitmap = malloc(sizeof(uint32_t) * width * height); sprite->masks = NULL; int alpha_after = ((unsigned char *)&bufferi[13])[2] == 0xFF; #define _BMP_A 0x1000000 #define _BMP_R 0x1 #define _BMP_G 0x100 #define _BMP_B 0x10000 if (bpp == 32) { sprite->alpha = ALPHA_EMBEDDED; } for (y = 0; y < height; ++y) { for (x = 0; x < width; ++x) { if (i > (size_t)image_size) goto _cleanup_sprite; /* Extract the color */ uint32_t color; if (bpp == 24) { color = (bufferb[i + 3 * x] & 0xFF) + (bufferb[i+1 + 3 * x] & 0xFF) * 0x100 + (bufferb[i+2 + 3 * x] & 0xFF) * 0x10000 + 0xFF000000; } else if (bpp == 32 && alpha_after == 0) { color = (bufferb[i + 4 * x] & 0xFF) * _BMP_A + (bufferb[i+1 + 4 * x] & 0xFF) * _BMP_R + (bufferb[i+2 + 4 * x] & 0xFF) * _BMP_G + (bufferb[i+3 + 4 * x] & 0xFF) * _BMP_B; color = premultiply(color); } else if (bpp == 32 && alpha_after == 1) { color = (bufferb[i + 4 * x] & 0xFF) * _BMP_R + (bufferb[i+1 + 4 * x] & 0xFF) * _BMP_G + (bufferb[i+2 + 4 * x] & 0xFF) * _BMP_B + (bufferb[i+3 + 4 * x] & 0xFF) * _BMP_A; color = premultiply(color); } else { color = rgb(bufferb[i + x],bufferb[i + x],bufferb[i + x]); /* Unsupported */ } /* Set our point */ sprite->bitmap[(height - y - 1) * width + x] = color; } i += row_width; } } else { /* Assume targa; limited support */ struct Header { uint8_t id_length; uint8_t color_map_type; uint8_t image_type; uint16_t color_map_first_entry; uint16_t color_map_length; uint8_t color_map_entry_size; uint16_t x_origin; uint16_t y_origin; uint16_t width; uint16_t height; uint8_t depth; uint8_t descriptor; } __attribute__((packed)); struct Header * header = (struct Header *)bufferb; if (header->id_length || header->color_map_type || (header->image_type != 2)) { /* Possibly valid but unsupported Targa file */ fclose(image); free(bufferb); return 1; } sprite->width = header->width; sprite->height = header->height; size_t size = sizeof(uint32_t) * sprite->width * sprite->height; if (size > image_size - sizeof(struct Header)) { /* sus */ fclose(image); free(bufferb); return 1; } sprite->bitmap = malloc(size); sprite->masks = NULL; uint16_t x = 0; uint16_t y = 0; int i = sizeof(struct Header); if (header->depth == 24) { for (y = 0; y < sprite->height; ++y) { for (x = 0; x < sprite->width; ++x) { uint32_t color = rgb( bufferb[i+2 + 3 * x], bufferb[i+1 + 3 * x], bufferb[i + 3 * x]); sprite->bitmap[(sprite->height - y - 1) * sprite->width + x] = color; } i += sprite->width * 3; } } else if (header->depth == 32) { for (y = 0; y < sprite->height; ++y) { for (x = 0; x < sprite->width; ++x) { uint32_t color = rgba( bufferb[i+2 + 4 * x], bufferb[i+1 + 4 * x], bufferb[i + 4 * x], bufferb[i+3 + 4 * x]); sprite->bitmap[(sprite->height - y - 1) * sprite->width + x] = color; } i += sprite->width * 4; } } } _cleanup_sprite: fclose(image); free(bufferb); return 0; } #if !defined(NO_SSE) && defined(__x86_64__) static __m128i mask00ff; static __m128i mask0080; static __m128i mask0101; __attribute__((constructor)) static void _masks(void) { mask00ff = _mm_set1_epi16(0x00FF); mask0080 = _mm_set1_epi16(0x0080); mask0101 = _mm_set1_epi16(0x0101); } __attribute__((__force_align_arg_pointer__)) #endif void draw_sprite(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y) { int32_t _left = max(x, 0); int32_t _top = max(y, 0); int32_t _right = min(x + sprite->width, ctx->width - 1); int32_t _bottom = min(y + sprite->height, ctx->height - 1); if (sprite->alpha == ALPHA_EMBEDDED) { /* Alpha embedded is the most important step. */ for (uint16_t _y = 0; _y < sprite->height; ++_y) { if (y + _y < _top) continue; if (y + _y > _bottom) break; if (!_is_in_clip(ctx, y + _y)) continue; #if defined(NO_SSE) || !defined(__x86_64__) for (uint16_t _x = 0; _x < sprite->width; ++_x) { if (x + _x < _left || x + _x > _right || y + _y < _top || y + _y > _bottom) continue; GFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), SPRITE(sprite, _x, _y)); } #else uint16_t _x = (x < _left) ? _left - x : 0; /* Ensure alignment */ for (; _x < sprite->width && x + _x <= _right; ++_x) { if (!((uintptr_t)&GFX(ctx, x + _x, y + _y) & 15)) break; GFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), SPRITE(sprite, _x, _y)); } for (; _x < sprite->width - 3 && x + _x + 3 <= _right; _x += 4) { __m128i d = _mm_load_si128((void *)&GFX(ctx, x + _x, y + _y)); __m128i s = _mm_loadu_si128((void *)&SPRITE(sprite, _x, _y)); __m128i d_l, d_h; __m128i s_l, s_h; // unpack destination d_l = _mm_unpacklo_epi8(d, _mm_setzero_si128()); d_h = _mm_unpackhi_epi8(d, _mm_setzero_si128()); // unpack source s_l = _mm_unpacklo_epi8(s, _mm_setzero_si128()); s_h = _mm_unpackhi_epi8(s, _mm_setzero_si128()); __m128i a_l, a_h; __m128i t_l, t_h; // extract source alpha RGBA → AAAA a_l = _mm_shufflehi_epi16(_mm_shufflelo_epi16(s_l, _MM_SHUFFLE(3,3,3,3)), _MM_SHUFFLE(3,3,3,3)); a_h = _mm_shufflehi_epi16(_mm_shufflelo_epi16(s_h, _MM_SHUFFLE(3,3,3,3)), _MM_SHUFFLE(3,3,3,3)); // negate source alpha t_l = _mm_xor_si128(a_l, mask00ff); t_h = _mm_xor_si128(a_h, mask00ff); // apply source alpha to destination d_l = _mm_mulhi_epu16(_mm_adds_epu16(_mm_mullo_epi16(d_l,t_l),mask0080),mask0101); d_h = _mm_mulhi_epu16(_mm_adds_epu16(_mm_mullo_epi16(d_h,t_h),mask0080),mask0101); // combine source and destination d_l = _mm_adds_epu8(s_l,d_l); d_h = _mm_adds_epu8(s_h,d_h); // pack low + high and write back to memory _mm_storeu_si128((void*)&GFX(ctx, x + _x, y + _y), _mm_packus_epi16(d_l,d_h)); } for (; _x < sprite->width && x + _x <= _right; ++_x) { GFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), SPRITE(sprite, _x, _y)); } #endif } } else if (sprite->alpha == ALPHA_OPAQUE) { for (uint16_t _y = 0; _y < sprite->height; ++_y) { if (y + _y < _top) continue; if (y + _y > _bottom) break; if (!_is_in_clip(ctx, y + _y)) continue; for (uint16_t _x = (x < _left) ? _left - x : 0; _x < sprite->width && x + _x <= _right; ++_x) { GFX(ctx, x + _x, y + _y) = SPRITE(sprite, _x, _y) | 0xFF000000; } } } } void draw_line(gfx_context_t * ctx, int32_t x0, int32_t x1, int32_t y0, int32_t y1, uint32_t color) { int deltax = abs(x1 - x0); int deltay = abs(y1 - y0); int sx = (x0 < x1) ? 1 : -1; int sy = (y0 < y1) ? 1 : -1; int error = deltax - deltay; while (1) { if (x0 >= 0 && y0 >= 0 && x0 < ctx->width && y0 < ctx->height) { GFX(ctx, x0, y0) = color; } if (x0 == x1 && y0 == y1) break; int e2 = 2 * error; if (e2 > -deltay) { error -= deltay; x0 += sx; } if (e2 < deltax) { error += deltax; y0 += sy; } } } 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) { int deltax = abs(x1 - x0); int deltay = abs(y1 - y0); int sx = (x0 < x1) ? 1 : -1; int sy = (y0 < y1) ? 1 : -1; int error = deltax - deltay; while (1) { for (char j = -thickness; j <= thickness; ++j) { for (char i = -thickness; i <= thickness; ++i) { if (x0 + i >= 0 && x0 + i < ctx->width && y0 + j >= 0 && y0 + j < ctx->height) { GFX(ctx, x0 + i, y0 + j) = color; } } } if (x0 == x1 && y0 == y1) break; int e2 = 2 * error; if (e2 > -deltay) { error -= deltay; x0 += sx; } if (e2 < deltax) { error += deltax; y0 += sy; } } } void draw_fill(gfx_context_t * ctx, uint32_t color) { for (uint16_t y = 0; y < ctx->height; ++y) { for (uint16_t x = 0; x < ctx->width; ++x) { GFX(ctx, x, y) = color; } } } static inline int out_of_bounds(const sprite_t * tex, int x, int y) { return x < 0 || y < 0 || x >= tex->width || y >= tex->height; } /** * @brief Use bilinear interpolation to get a blended color at the point u,v */ #if 1 static inline uint32_t linear_interp(uint32_t left, uint32_t right, uint16_t pr) { uint16_t pl = 0xFF ^ pr; uint8_t d_r = (((uint32_t)(_RED(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_RED(left) * pl + 0x80) * 0x101) >> 16UL); uint8_t d_g = (((uint32_t)(_GRE(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_GRE(left) * pl + 0x80) * 0x101) >> 16UL); uint8_t d_b = (((uint32_t)(_BLU(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_BLU(left) * pl + 0x80) * 0x101) >> 16UL); uint8_t d_a = (((uint32_t)(_ALP(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_ALP(left) * pl + 0x80) * 0x101) >> 16UL); return rgba(d_r, d_g, d_b, d_a); } __attribute__((hot)) static inline uint32_t gfx_bilinear_interpolation(const sprite_t * tex, double u, double v) { int x = (int)(u + 2.0) - 2; int y = (int)(v + 2.0) - 2; uint32_t ul = out_of_bounds(tex,x,y) ? 0 : SPRITE(tex,x,y); uint32_t ur = out_of_bounds(tex,x+1,y) ? 0 : SPRITE(tex,x+1,y); uint32_t ll = out_of_bounds(tex,x,y+1) ? 0 : SPRITE(tex,x,y+1); uint32_t lr = out_of_bounds(tex,x+1,y+1) ? 0 : SPRITE(tex,x+1,y+1); if ((ul | ur | ll | lr) == 0) return 0; uint8_t u_ratio = (u - x) * 0xFF; uint8_t v_ratio = (v - y) * 0xFF; uint32_t top = linear_interp(ul,ur,u_ratio); uint32_t bot = linear_interp(ll,lr,u_ratio); return linear_interp(top,bot,v_ratio); } #else static uint32_t gfx_bilinear_interpolation(const sprite_t * tex, double u, double v) { return out_of_bounds(tex,u,v) ? 0 : SPRITE(tex,(unsigned int)u,(unsigned int)v); } #endif static inline void apply_alpha_vector(uint32_t * pixels, size_t width, uint8_t alpha) { size_t i = 0; #if !defined(NO_SSE) && defined(__x86_64__) __m128i alp = _mm_set_epi16(alpha,alpha,alpha,alpha,alpha,alpha,alpha,alpha); while (i + 3 < width) { __m128i p = _mm_load_si128((void*)&pixels[i]); __m128i d_l, d_h; d_l = _mm_mulhi_epu16(_mm_adds_epu16(_mm_mullo_epi16(_mm_unpacklo_epi8(p, _mm_setzero_si128()),alp),mask0080),mask0101); d_h = _mm_mulhi_epu16(_mm_adds_epu16(_mm_mullo_epi16(_mm_unpackhi_epi8(p, _mm_setzero_si128()),alp),mask0080),mask0101); _mm_storeu_si128((void*)&pixels[i], _mm_packus_epi16(d_l,d_h)); i += 4; } #endif while (i < width) { uint8_t r = _RED(pixels[i]); uint8_t g = _GRE(pixels[i]); uint8_t b = _BLU(pixels[i]); uint8_t a = _ALP(pixels[i]); r = (((uint16_t)r * alpha + 0x80) * 0x101) >> 16UL; g = (((uint16_t)g * alpha + 0x80) * 0x101) >> 16UL; b = (((uint16_t)b * alpha + 0x80) * 0x101) >> 16UL; a = (((uint16_t)a * alpha + 0x80) * 0x101) >> 16UL; pixels[i] = rgba(r,g,b,a); i++; } } void draw_sprite_alpha(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float alpha) { int32_t _left = max(x, 0); int32_t _top = max(y, 0); int32_t _right = min(x + sprite->width, ctx->width); int32_t _bottom = min(y + sprite->height, ctx->height); sprite_t * scanline = create_sprite(_right - _left, 1, ALPHA_EMBEDDED); uint8_t alp = alpha * 255; for (uint16_t _y = 0; _y < sprite->height; ++_y) { if (y + _y < _top) continue; if (y + _y >= _bottom) break; if (!_is_in_clip(ctx, y + _y)) continue; for (uint16_t _x = (x < _left) ? _left - x : 0; _x < sprite->width && x + _x < _right; ++_x) { SPRITE(scanline,_x + x - _left,0) = SPRITE(sprite, _x, _y); } apply_alpha_vector(scanline->bitmap, scanline->width, alp); draw_sprite(ctx,scanline,_left,y + _y); } sprite_free(scanline); } void draw_sprite_alpha_paint(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float alpha, uint32_t c) { int32_t _left = max(x, 0); int32_t _top = max(y, 0); int32_t _right = min(x + sprite->width, ctx->width); int32_t _bottom = min(y + sprite->height, ctx->height); for (uint16_t _y = 0; _y < sprite->height; ++_y) { if (y + _y < _top) continue; if (y + _y >= _bottom) break; if (!_is_in_clip(ctx, y + _y)) continue; for (uint16_t _x = (x < _left) ? _left - x : 0; _x < sprite->width && x + _x < _right; ++_x) { /* Get the alpha from the sprite at this pixel */ float n_alpha = alpha * ((float)_ALP(SPRITE(sprite, _x, _y)) / 255.0); uint32_t f_color = premultiply((c & 0xFFFFFF) | ((uint32_t)(255 * n_alpha) << 24)); f_color = (f_color & 0xFFFFFF) | ((uint32_t)(n_alpha * _ALP(c)) << 24); GFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), f_color); } } } static void apply_matrix(double x, double y, gfx_matrix_t matrix, double *out_x, double *out_y) { *out_x = matrix[0][0] * x + matrix[0][1] * y + matrix[0][2]; *out_y = matrix[1][0] * x + matrix[1][1] * y + matrix[1][2]; } void gfx_apply_matrix(double x, double y, gfx_matrix_t matrix, double *out_x, double *out_y) { apply_matrix(x,y,matrix,out_x,out_y); } static void multiply_matrix(gfx_matrix_t x, gfx_matrix_t y) { double a = x[0][0]; double b = x[0][1]; double c = x[0][2]; double d = x[1][0]; double e = x[1][1]; double f = x[1][2]; double g = y[0][0]; double h = y[0][1]; double i = y[0][2]; double j = y[1][0]; double k = y[1][1]; double l = y[1][2]; x[0][0] = a * g + b * j; x[0][1] = a * h + b * k; x[0][2] = a * i + b * l + c; x[1][0] = d * g + e * j; x[1][1] = d * h + e * k; x[1][2] = d * i + e * l + f; } void gfx_matrix_identity(gfx_matrix_t matrix) { matrix[0][0] = 1; matrix[0][1] = 0; matrix[0][2] = 0; matrix[1][0] = 0; matrix[1][1] = 1; matrix[1][2] = 0; } void gfx_matrix_scale(gfx_matrix_t matrix, double x, double y) { multiply_matrix(matrix, (gfx_matrix_t){ {x, 0.0, 0.0}, {0.0, y, 0.0}, }); } void gfx_matrix_shear(gfx_matrix_t matrix, double x, double y) { multiply_matrix(matrix, (gfx_matrix_t){ {1.0, x, 0.0}, {y, 1.0, 0.0}, }); } void gfx_matrix_rotate(gfx_matrix_t matrix, double r) { multiply_matrix(matrix, (gfx_matrix_t){ { cos(r), -sin(r), 0.0}, { sin(r), cos(r), 0.0}, }); } void gfx_matrix_translate(gfx_matrix_t matrix, double x, double y) { multiply_matrix(matrix, (gfx_matrix_t){ { 1.0, 0.0, x }, { 0.0, 1.0, y }, }); } static double matrix_det(gfx_matrix_t matrix) { double a = matrix[0][0]; double b = matrix[0][1]; double d = matrix[1][0]; double e = matrix[1][1]; return a * e - b * d; } int gfx_matrix_invert(gfx_matrix_t m, gfx_matrix_t inverse) { double det = matrix_det(m); if (det == 0.0) return 1; double a = m[0][0]; double b = m[0][1]; double c = m[1][0]; double d = m[1][1]; double tx = m[0][2]; double ty = m[1][2]; inverse[0][0] = d * (1.0 / det); inverse[0][1] = -b * (1.0 / det); inverse[1][0] = -c * (1.0 / det); inverse[1][1] = a * (1.0 / det); inverse[0][2] = (b * ty - d * tx) / det; inverse[1][2] = (c * tx - a * ty) / det; return 0; } /** * @brief Draw a sprite into a context, applying a transformation matrix. * * Uses the affine transformaton matrix @p matrix to draw @p sprite into @p ctx. */ void draw_sprite_transform(gfx_context_t * ctx, const sprite_t * sprite, gfx_matrix_t matrix, float alpha) { double inverse[2][3]; /* Calculate the inverse matrix for use in calculating sprite * coordinate from screen coordinate. */ gfx_matrix_invert(matrix, inverse); /* Use primary matrix to obtain corners of the transformed * sprite in screen coordinates. */ double ul_x, ul_y; double ll_x, ll_y; double ur_x, ur_y; double lr_x, lr_y; apply_matrix(0, 0, matrix, &ul_x, &ul_y); apply_matrix(0, sprite->height, matrix, &ll_x, &ll_y); apply_matrix(sprite->width, 0, matrix, &ur_x, &ur_y); apply_matrix(sprite->width, sprite->height, matrix, &lr_x, &lr_y); /* Use the corners to calculate bounds within the target context. */ int32_t _left = clamp(fmin(fmin(ul_x, ll_x), fmin(ur_x, lr_x)), 0, ctx->width); int32_t _top = clamp(fmin(fmin(ul_y, ll_y), fmin(ur_y, lr_y)), 0, ctx->height); int32_t _right = clamp(fmax(fmax(ul_x+2, ll_x+2), fmax(ur_x+2, lr_x+2)), 0, ctx->width); int32_t _bottom = clamp(fmax(fmax(ul_y+2, ll_y+2), fmax(ur_y+2, lr_y+2)), 0, ctx->height); sprite_t * scanline = create_sprite(_right - _left, 1, ALPHA_EMBEDDED); uint8_t alp = alpha * 255; double filter_x, filter_y, filter_dxx, filter_dxy, filter_dyx, filter_dyy; gfx_apply_matrix(_left, _top, inverse, &filter_x, &filter_y); gfx_apply_matrix(_left+1, _top, inverse, &filter_dxx, &filter_dxy); filter_dxx -= filter_x; filter_dxy -= filter_y; gfx_apply_matrix(_left, _top+1, inverse, &filter_dyx, &filter_dyy); filter_dyx -= filter_x; filter_dyy -= filter_y; for (int32_t _y = _top; _y < _bottom; ++_y) { float u = filter_x; float v = filter_y; filter_x += filter_dyx; filter_y += filter_dyy; if (!_is_in_clip(ctx, _y)) continue; for (int32_t _x = _left; _x < _right; ++_x) { SPRITE(scanline,_x - _left,0) = gfx_bilinear_interpolation(sprite, u, v); u += filter_dxx; v += filter_dxy; } apply_alpha_vector(scanline->bitmap, scanline->width, alp); draw_sprite(ctx,scanline,_left,_y); } sprite_free(scanline); } 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) { double inverse[2][3]; /* Calculate the inverse matrix for use in calculating sprite * coordinate from screen coordinate. */ gfx_matrix_invert(matrix, inverse); /* Use primary matrix to obtain corners of the transformed * sprite in screen coordinates. */ double ul_x, ul_y; double ll_x, ll_y; double ur_x, ur_y; double lr_x, lr_y; apply_matrix(0, 0, matrix, &ul_x, &ul_y); apply_matrix(0, sprite->height, matrix, &ll_x, &ll_y); apply_matrix(sprite->width, 0, matrix, &ur_x, &ur_y); apply_matrix(sprite->width, sprite->height, matrix, &lr_x, &lr_y); /* Use the corners to calculate bounds within the target context. */ int32_t _left = clamp(fmin(fmin(ul_x, ll_x), fmin(ur_x, lr_x)), 0, ctx->width); int32_t _top = clamp(fmin(fmin(ul_y, ll_y), fmin(ur_y, lr_y)), 0, ctx->height); int32_t _right = clamp(fmax(fmax(ul_x+2, ll_x+2), fmax(ur_x+2, lr_x+2)), 0, ctx->width); int32_t _bottom = clamp(fmax(fmax(ul_y+2, ll_y+2), fmax(ur_y+2, lr_y+2)), 0, ctx->height); blur_ctx->clips_size = ctx->clips_size; blur_ctx->clips = ctx->clips; blur_ctx->backbuffer = ctx->backbuffer; gfx_context_t * f = init_graphics_subregion(blur_ctx, _left, _top, _right - _left, _bottom - _top); flip(f); f->backbuffer = f->buffer; blur_context_box(f, 10); free(f); blur_ctx->backbuffer = blur_ctx->buffer; blur_ctx->clips_size = 0; blur_ctx->clips = NULL; sprite_t * scanline = create_sprite(_right - _left, 1, ALPHA_EMBEDDED); sprite_t * blurline = create_sprite(_right - _left, 1, ALPHA_EMBEDDED); uint8_t alp = alpha * 255; double filter_x, filter_y, filter_dxx, filter_dxy, filter_dyx, filter_dyy; gfx_apply_matrix(_left, _top, inverse, &filter_x, &filter_y); gfx_apply_matrix(_left+1, _top, inverse, &filter_dxx, &filter_dxy); filter_dxx -= filter_x; filter_dxy -= filter_y; gfx_apply_matrix(_left, _top+1, inverse, &filter_dyx, &filter_dyy); filter_dyx -= filter_x; filter_dyy -= filter_y; for (int32_t _y = _top; _y < _bottom; ++_y) { float u = filter_x; float v = filter_y; filter_x += filter_dyx; filter_y += filter_dyy; if (!_is_in_clip(ctx, _y)) continue; for (int32_t _x = _left; _x < _right; ++_x) { SPRITE(scanline,_x - _left,0) = gfx_bilinear_interpolation(sprite, u, v); SPRITE(blurline,_x - _left,0) = (_ALP(SPRITE(scanline,_x - _left,0)) > threshold) ? GFX(blur_ctx,_x,_y) : 0; u += filter_dxx; v += filter_dxy; } apply_alpha_vector(blurline->bitmap, blurline->width, alp); apply_alpha_vector(scanline->bitmap, scanline->width, alp); draw_sprite(ctx,blurline,_left,_y); draw_sprite(ctx,scanline,_left,_y); } sprite_free(scanline); sprite_free(blurline); } void draw_sprite_rotate(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, float rotation, float alpha) { gfx_matrix_t m; gfx_matrix_identity(m); gfx_matrix_translate(m, x + sprite->width / 2, y + sprite->height / 2); gfx_matrix_rotate(m, rotation); gfx_matrix_translate(m, -sprite->width / 2, -sprite->height / 2); draw_sprite_transform(ctx,sprite,m,alpha); } void draw_sprite_scaled(gfx_context_t * ctx, const sprite_t * sprite, int32_t x, int32_t y, uint16_t width, uint16_t height) { gfx_matrix_t m; gfx_matrix_identity(m); gfx_matrix_translate(m, x, y); gfx_matrix_scale(m, (double)width / (double)sprite->width, (double)height / (double)sprite->height); draw_sprite_transform(ctx,sprite,m,1.0); } 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) { gfx_matrix_t m; gfx_matrix_identity(m); gfx_matrix_translate(m, x, y); gfx_matrix_scale(m, (double)width / (double)sprite->width, (double)height / (double)sprite->height); draw_sprite_transform(ctx,sprite,m,alpha); } uint32_t interp_colors(uint32_t bottom, uint32_t top, uint8_t interp) { uint8_t red = (_RED(bottom) * (255 - interp) + _RED(top) * interp) / 255; uint8_t gre = (_GRE(bottom) * (255 - interp) + _GRE(top) * interp) / 255; uint8_t blu = (_BLU(bottom) * (255 - interp) + _BLU(top) * interp) / 255; uint8_t alp = (_ALP(bottom) * (255 - interp) + _ALP(top) * interp) / 255; return rgba(red,gre,blu, alp); } void draw_rectangle(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, uint32_t color) { int32_t _left = max(x, 0); int32_t _top = max(y, 0); int32_t _right = min(x + width, ctx->width - 1); int32_t _bottom = min(y + height, ctx->height - 1); for (uint16_t _y = 0; _y < height; ++_y) { if (!_is_in_clip(ctx, y + _y)) continue; for (uint16_t _x = 0; _x < width; ++_x) { if (x + _x < _left || x + _x > _right || y + _y < _top || y + _y > _bottom) continue; GFX(ctx, x + _x, y + _y) = alpha_blend_rgba(GFX(ctx, x + _x, y + _y), color); } } } void draw_rectangle_solid(gfx_context_t * ctx, int32_t x, int32_t y, uint16_t width, uint16_t height, uint32_t color) { int32_t _left = max(x, 0); int32_t _top = max(y, 0); int32_t _right = min(x + width, ctx->width - 1); int32_t _bottom = min(y + height, ctx->height - 1); for (uint16_t _y = 0; _y < height; ++_y) { if (!_is_in_clip(ctx, y + _y)) continue; for (uint16_t _x = 0; _x < width; ++_x) { if (x + _x < _left || x + _x > _right || y + _y < _top || y + _y > _bottom) continue; GFX(ctx, x + _x, y + _y) = color; } } } uint32_t gfx_vertical_gradient_pattern(int32_t x, int32_t y, double alpha, void * extra) { struct gradient_definition * gradient = extra; int base_r = _RED(gradient->top), base_g = _GRE(gradient->top), base_b = _BLU(gradient->top); int last_r = _RED(gradient->bottom), last_g = _GRE(gradient->bottom), last_b = _BLU(gradient->bottom); double gradpoint = (double)(y - (gradient->y)) / (double)gradient->height; if (alpha > 1.0) alpha = 1.0; if (alpha < 0.0) alpha = 0.0; return premultiply(rgba( base_r * (1.0 - gradpoint) + last_r * (gradpoint), base_g * (1.0 - gradpoint) + last_g * (gradpoint), base_b * (1.0 - gradpoint) + last_b * (gradpoint), alpha * 255)); } float gfx_point_distance(const struct gfx_point * a, const struct gfx_point * b) { return sqrt((a->x - b->x) * (a->x - b->x) + (a->y - b->y) * (a->y - b->y)); } 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) { /* Draw a rounded rectangle */ if (radius > width / 2) { radius = width / 2; } if (radius > height / 2) { radius = height / 2; } for (int row = y; row < y + height; row++){ if (row < 0) continue; if (row >= ctx->height) break; for (int col = x; col < x + width; col++) { if (col < 0) continue; if (col >= ctx->width) break; if ((col < x + radius || col > x + width - radius - 1) && (row < y + radius || row > y + height - radius - 1)) { continue; } GFX(ctx, col, row) = alpha_blend_rgba(GFX(ctx, col, row), pattern(col,row,1.0,extra)); } } struct gfx_point origin = {0.0,0.0}; for (int py = 0; py < radius + 1; ++py) { for (int px = 0; px < radius + 1; ++px) { struct gfx_point this = {px,py}; float dist = gfx_point_distance(&origin,&this); if (dist > (double)radius) continue; float alpha = 1.0; if (dist > (double)(radius-1)) { alpha = 1.0 - (dist - (double)(radius-1)); } int _x = clamp(x + width - radius + px, 0, ctx->width-1); int _y = clamp(y + height - radius + py, 0, ctx->height-1); int _z = clamp(y + radius - py - 1, 0, ctx->height-1); GFX(ctx, _x, _y) = alpha_blend_rgba(GFX(ctx, _x, _y), pattern(_x,_y,alpha,extra)); GFX(ctx, _x, _z) = alpha_blend_rgba(GFX(ctx, _x, _z), pattern(_x,_z,alpha,extra)); _x = clamp(x + radius - px - 1, 0, ctx->width-1); GFX(ctx, _x, _y) = alpha_blend_rgba(GFX(ctx, _x, _y), pattern(_x,_y,alpha,extra)); GFX(ctx, _x, _z) = alpha_blend_rgba(GFX(ctx, _x, _z), pattern(_x,_z,alpha,extra)); } } } uint32_t gfx_fill_pattern(int32_t x, int32_t y, double alpha, void * extra) { if (alpha > 1.0) alpha = 1.0; if (alpha < 0.0) alpha = 0.0; uint32_t c = *(uint32_t*)extra; return premultiply(rgba(_RED(c),_GRE(c),_BLU(c),(int)((double)_ALP(c) * alpha))); } 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) { draw_rounded_rectangle_pattern(ctx,x,y,width,height,radius,gfx_fill_pattern,&color); } float gfx_point_distance_squared(const struct gfx_point * a, const struct gfx_point * b) { return (a->x - b->x) * (a->x - b->x) + (a->y - b->y) * (a->y - b->y); } float gfx_point_dot(const struct gfx_point * a, const struct gfx_point * b) { return (a->x * b->x) + (a->y * b->y); } struct gfx_point gfx_point_sub(const struct gfx_point * a, const struct gfx_point * b) { struct gfx_point p = {a->x - b->x, a->y - b->y}; return p; } struct gfx_point gfx_point_add(const struct gfx_point * a, const struct gfx_point * b) { struct gfx_point p = {a->x + b->x, a->y + b->y}; return p; } float gfx_line_distance(const struct gfx_point * p, const struct gfx_point * v, const struct gfx_point * w) { float lengthlength = gfx_point_distance_squared(v,w); if (lengthlength == 0.0) return gfx_point_distance(p, v); /* point */ struct gfx_point p_v = gfx_point_sub(p,v); struct gfx_point w_v = gfx_point_sub(w,v); float tmp = gfx_point_dot(&p_v,&w_v) / lengthlength; tmp = fmin(1.0,tmp); float t = fmax(0.0, tmp); w_v.x *= t; w_v.y *= t; struct gfx_point v_t = gfx_point_add(v, &w_v); return gfx_point_distance(p, &v_t); } /** * This is slow, but it works... * * Maybe acceptable for baked UI elements? */ void draw_line_aa_points(gfx_context_t * ctx, struct gfx_point *v, struct gfx_point *w, uint32_t color, float thickness) { /* Calculate viable bounds */ int x_0 = max(min(v->x - thickness - 1, w->x - thickness - 1), 0); int x_1 = min(max(v->x + thickness + 1, w->x + thickness + 1), ctx->width); int y_0 = max(min(v->y - thickness - 1, w->y - thickness - 1), 0); int y_1 = min(max(v->y + thickness + 1, w->y + thickness + 1), ctx->height); for (int y = y_0; y < y_1; ++y) { for (int x = x_0; x < x_1; ++x) { struct gfx_point p = {x,y}; float d = gfx_line_distance(&p,v,w); if (d < thickness + 0.5) { if (d < thickness - 0.5) { GFX(ctx,x,y) = alpha_blend_rgba(GFX(ctx,x,y), color); } else { float alpha = 1.0 - (d - thickness + 0.5); GFX(ctx,x,y) = alpha_blend_rgba(GFX(ctx,x,y), premultiply(rgba(_RED(color),_GRE(color),_BLU(color),(int)((double)_ALP(color) * alpha)))); } } } } } 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) { struct gfx_point v = {(float)x_1, (float)y_1}; struct gfx_point w = {(float)x_2, (float)y_2}; draw_line_aa_points(ctx,&v,&w,color,thickness); } ================================================ FILE: lib/hashmap.c ================================================ /** * @brief Generic hashmap implementation. * * @copyright * 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 */ #include #include unsigned int hashmap_string_hash(const void * _key) { unsigned int hash = 0; char * key = (char *)_key; int c; /* This is the so-called "sdbm" hash. It comes from a piece of * public domain code from a clone of ndbm. */ while ((c = *key++)) { hash = c + (hash << 6) + (hash << 16) - hash; } return hash; } int hashmap_string_comp(const void * a, const void * b) { return !strcmp(a,b); } void * hashmap_string_dupe(const void * key) { return strdup(key); } unsigned int hashmap_int_hash(const void * key) { return (uintptr_t)key; } int hashmap_int_comp(const void * a, const void * b) { return (uintptr_t)a == (uintptr_t)b; } void * hashmap_int_dupe(const void * key) { return (void*)key; } static void hashmap_int_free(void * ptr) { (void)ptr; return; } hashmap_t * hashmap_create(int size) { hashmap_t * map = malloc(sizeof(hashmap_t)); map->hash_func = &hashmap_string_hash; map->hash_comp = &hashmap_string_comp; map->hash_key_dup = &hashmap_string_dupe; map->hash_key_free = &free; map->hash_val_free = &free; map->size = size; map->entries = malloc(sizeof(hashmap_entry_t *) * size); memset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size); return map; } hashmap_t * hashmap_create_int(int size) { hashmap_t * map = malloc(sizeof(hashmap_t)); map->hash_func = &hashmap_int_hash; map->hash_comp = &hashmap_int_comp; map->hash_key_dup = &hashmap_int_dupe; map->hash_key_free = &hashmap_int_free; map->hash_val_free = &free; map->size = size; map->entries = malloc(sizeof(hashmap_entry_t *) * size); memset(map->entries, 0x00, sizeof(hashmap_entry_t *) * size); return map; } void * hashmap_set(hashmap_t * map, const void * key, void * value) { unsigned int hash = map->hash_func(key) % map->size; hashmap_entry_t * x = map->entries[hash]; if (!x) { hashmap_entry_t * e = malloc(sizeof(hashmap_entry_t)); e->key = map->hash_key_dup(key); e->value = value; e->next = NULL; map->entries[hash] = e; return NULL; } else { hashmap_entry_t * p = NULL; do { if (map->hash_comp(x->key, key)) { void * out = x->value; x->value = value; return out; } else { p = x; x = x->next; } } while (x); hashmap_entry_t * e = malloc(sizeof(hashmap_entry_t)); e->key = map->hash_key_dup(key); e->value = value; e->next = NULL; p->next = e; return NULL; } } void * hashmap_get(hashmap_t * map, const void * key) { unsigned int hash = map->hash_func(key) % map->size; hashmap_entry_t * x = map->entries[hash]; if (!x) { return NULL; } else { do { if (map->hash_comp(x->key, key)) { return x->value; } x = x->next; } while (x); return NULL; } } void * hashmap_remove(hashmap_t * map, const void * key) { unsigned int hash = map->hash_func(key) % map->size; hashmap_entry_t * x = map->entries[hash]; if (!x) { return NULL; } else { if (map->hash_comp(x->key, key)) { void * out = x->value; map->entries[hash] = x->next; map->hash_key_free(x->key); map->hash_val_free(x); return out; } else { hashmap_entry_t * p = x; x = x->next; do { if (map->hash_comp(x->key, key)) { void * out = x->value; p->next = x->next; map->hash_key_free(x->key); map->hash_val_free(x); return out; } p = x; x = x->next; } while (x); } return NULL; } } int hashmap_has(hashmap_t * map, const void * key) { unsigned int hash = map->hash_func(key) % map->size; hashmap_entry_t * x = map->entries[hash]; if (!x) { return 0; } else { do { if (map->hash_comp(x->key, key)) { return 1; } x = x->next; } while (x); return 0; } } list_t * hashmap_keys(hashmap_t * map) { list_t * l = list_create(); for (unsigned int i = 0; i < map->size; ++i) { hashmap_entry_t * x = map->entries[i]; while (x) { list_insert(l, x->key); x = x->next; } } return l; } list_t * hashmap_values(hashmap_t * map) { list_t * l = list_create(); for (unsigned int i = 0; i < map->size; ++i) { hashmap_entry_t * x = map->entries[i]; while (x) { list_insert(l, x->value); x = x->next; } } return l; } void hashmap_free(hashmap_t * map) { for (unsigned int i = 0; i < map->size; ++i) { hashmap_entry_t * x = map->entries[i], * p; while (x) { p = x; x = x->next; map->hash_key_free(p->key); map->hash_val_free(p); } } free(map->entries); } int hashmap_is_empty(hashmap_t * map) { for (unsigned int i = 0; i < map->size; ++i) { if (map->entries[i]) return 0; } return 1; } ================================================ FILE: lib/icon_cache.c ================================================ /** * @brief icon_cache - caches icons * * Used be a few different applications. * Probably needs scaling? * * @copyright * 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 */ #include #include #include #include #include #include static hashmap_t * icon_cache_16; static hashmap_t * icon_cache_48; static char * icon_directories_16[] = { "/usr/share/icons/16", "/usr/share/icons/24", "/usr/share/icons/48", "/usr/share/icons", "/usr/share/icons/external", NULL }; static char * icon_directories_48[] = { "/usr/share/icons/48", "/usr/share/icons/24", "/usr/share/icons/16", "/usr/share/icons", "/usr/share/icons/external", NULL }; static char * prefixes[] = { "png", "bmp", NULL }; __attribute__((constructor)) static void _init_caches(void) { icon_cache_16 = hashmap_create(10); { /* Generic fallback icon */ sprite_t * app_icon = malloc(sizeof(sprite_t)); load_sprite(app_icon, "/usr/share/icons/16/applications-generic.png"); hashmap_set(icon_cache_16, "generic", app_icon); } icon_cache_48 = hashmap_create(10); { /* Generic fallback icon */ sprite_t * app_icon = malloc(sizeof(sprite_t)); load_sprite(app_icon, "/usr/share/icons/48/applications-generic.png"); hashmap_set(icon_cache_48, "generic", app_icon); } } static sprite_t * icon_get_int(const char * name, hashmap_t * icon_cache, char ** icon_directories) { if (!strcmp(name,"")) { /* If a window doesn't have an icon set, return the generic icon */ return hashmap_get(icon_cache, "generic"); } /* Check the icon cache */ sprite_t * icon = hashmap_get(icon_cache, (void*)name); if (!icon) { /* We don't have an icon cached for this identifier, try search */ int i = 0; char path[100]; while (icon_directories[i]) { /* Check each path... */ char ** prefix = prefixes; while (*prefix) { sprintf(path, "%s/%s.%s", icon_directories[i], name, *prefix); if (access(path, R_OK) == 0) { /* And if we find one, cache it */ icon = malloc(sizeof(sprite_t)); load_sprite(icon, path); hashmap_set(icon_cache, (void*)name, icon); return icon; } prefix++; } i++; } /* If we've exhausted our search paths, just return the generic icon */ icon = hashmap_get(icon_cache, "generic"); hashmap_set(icon_cache, (void*)name, icon); } /* We have an icon, return it */ return icon; } sprite_t * icon_get_16(const char * name) { return icon_get_int(name, icon_cache_16, icon_directories_16); } sprite_t * icon_get_48(const char * name) { return icon_get_int(name, icon_cache_48, icon_directories_48); } ================================================ FILE: lib/inflate.c ================================================ /** * @brief libtoaru_inflate: Methods for decompressing DEFLATE and gzip payloads. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2020 K. Lange */ #include #include #ifndef _BOOT_LOADER #include #endif /** * Decoded Huffman table */ struct huff { uint16_t counts[16]; /* Number of symbols of each length */ uint16_t symbols[288]; /* Ordered symbols */ }; /** * 32K ringbuffer for backwards lookup */ struct huff_ring { size_t pointer; uint8_t data[32768]; }; /** * Fixed Huffman code tables, generated later. */ struct huff fixed_lengths; struct huff fixed_dists; /** * Read a little-endian short from the input. */ static uint16_t read_16le(struct inflate_context * ctx) { uint16_t a, b; a = ctx->get_input(ctx); b = ctx->get_input(ctx); return (a << 0) | (b << 8); } /** * Read a single bit from the source. * Fills the byte buffer with one byte when it runs out. */ static uint8_t read_bit(struct inflate_context * ctx) { /* When we run out of bits... */ if (ctx->buffer_size == 0) { /* Refill from the next input byte */ ctx->bit_buffer = ctx->get_input(ctx); /* And restore bit buffer size to 8 bits */ ctx->buffer_size = 8; } /* Get the next available bit */ int out = ctx->bit_buffer & 1; /* Shift the bit buffer forward */ ctx->bit_buffer >>= 1; /* There is now one less bit available */ ctx->buffer_size--; return out; } /** * Read multible bits, in bit order, from the source. */ static uint32_t read_bits(struct inflate_context * ctx, unsigned int count) { uint32_t out = 0; for (unsigned int bit = 0; bit < count; bit++) { /* Read one bit at a time, from least to most significant */ out |= (read_bit(ctx) << bit); } return out; } /** * Build a Huffman table from an array of lengths. */ static void build_huffman(uint8_t * lengths, size_t size, struct huff * out) { uint16_t offsets[16]; unsigned int count = 0; /* Zero symbol counts */ for (unsigned int i = 0; i < 16; ++i) out->counts[i] = 0; /* Count symbols */ for (unsigned int i = 0; i < size; ++i) out->counts[lengths[i]]++; /* Special case... */ out->counts[0] = 0; /* Figure out offsets */ for (unsigned int i = 0; i < 16; ++i) { offsets[i] = count; count += out->counts[i]; } /* Build symbol ordering */ for (unsigned int i = 0; i < size; ++i) { if (lengths[i]) out->symbols[offsets[lengths[i]]++] = i; } } /** * Build the fixed Huffman tables */ static void build_fixed(void) { /* From 3.2.6: * Lit Value Bits Codes * --------- ---- ----- * 0 - 143 8 00110000 through * 10111111 * 144 - 255 9 110010000 through * 111111111 * 256 - 279 7 0000000 through * 0010111 * 280 - 287 8 11000000 through * 11000111 */ uint8_t lengths[288]; for (int i = 0; i < 144; ++i) lengths[i] = 8; for (int i = 144; i < 256; ++i) lengths[i] = 9; for (int i = 256; i < 280; ++i) lengths[i] = 7; for (int i = 280; i < 288; ++i) lengths[i] = 8; build_huffman(lengths, 288, &fixed_lengths); /* Continued from 3.2.6: * Distance codes 0-31 are represented by (fixed-length) 5-bit * codes, with possible additional bits as shown in the table * shown in Paragraph 3.2.5, above. Note that distance codes 30- * 31 will never actually occur in the compressed data. */ for (int i = 0; i < 30; ++i) lengths[i] = 5; build_huffman(lengths, 30, &fixed_dists); } /** * Decode a symbol from the source using a Huffman table. */ static int decode(struct inflate_context * ctx, struct huff * huff) { int count = 0, cur = 0; for (int i = 1; cur >= 0; i++) { cur = (cur << 1) | read_bit(ctx); /* Shift */ count += huff->counts[i]; cur -= huff->counts[i]; } return huff->symbols[count + cur]; } /** * Emit one byte to the output, maintaining the ringbuffer. * The ringbuffer ensures we can always look back 32K bytes * while keeping output streaming. */ static void emit(struct inflate_context * ctx, unsigned char byte) { if (ctx->ring->pointer == 32768) { ctx->ring->pointer = 0; } ctx->ring->data[ctx->ring->pointer] = byte; ctx->write_output(ctx, byte); ctx->ring->pointer++; } /** * Look backwards in the output ring buffer. */ static uint8_t peek(struct inflate_context * ctx, int offset) { return ctx->ring->data[(ctx->ring->pointer - offset) % 32768]; } /** * Decompress a block of Huffman-encoded data. */ static int inflate(struct inflate_context * ctx, struct huff * huff_len, struct huff * huff_dist) { /* These are the extra bits for lengths from the tables in section 3.2.5 * Extra Extra Extra * Code Bits Length(s) Code Bits Lengths Code Bits Length(s) * ---- ---- ------ ---- ---- ------- ---- ---- ------- * 257 0 3 267 1 15,16 277 4 67-82 * 258 0 4 268 1 17,18 278 4 83-98 * 259 0 5 269 2 19-22 279 4 99-114 * 260 0 6 270 2 23-26 280 4 115-130 * 261 0 7 271 2 27-30 281 5 131-162 * 262 0 8 272 2 31-34 282 5 163-194 * 263 0 9 273 3 35-42 283 5 195-226 * 264 0 10 274 3 43-50 284 5 227-257 * 265 1 11,12 275 3 51-58 285 0 258 * 266 1 13,14 276 3 59-66 */ static const uint16_t lens[] = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258 }; static const uint16_t lext[] = { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; /* Extra bits for distances.... * Extra Extra Extra * Code Bits Dist Code Bits Dist Code Bits Distance * ---- ---- ---- ---- ---- ------ ---- ---- -------- * 0 0 1 10 4 33-48 20 9 1025-1536 * 1 0 2 11 4 49-64 21 9 1537-2048 * 2 0 3 12 5 65-96 22 10 2049-3072 * 3 0 4 13 5 97-128 23 10 3073-4096 * 4 1 5,6 14 6 129-192 24 11 4097-6144 * 5 1 7,8 15 6 193-256 25 11 6145-8192 * 6 2 9-12 16 7 257-384 26 12 8193-12288 * 7 2 13-16 17 7 385-512 27 12 12289-16384 * 8 3 17-24 18 8 513-768 28 13 16385-24576 * 9 3 25-32 19 8 769-1024 29 13 24577-32768 */ static const uint16_t dists[] = { 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577 }; static const uint16_t dext[] = { 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13 }; while (1) { int symbol = decode(ctx, huff_len); if (symbol == 256) { break; } if (symbol < 256) { emit(ctx, symbol); } else if (symbol == 256) { /* "The literal/length symbol 256 (end of data), ..." */ break; } else { int length, distance, offset; symbol -= 257; length = read_bits(ctx, lext[symbol]) + lens[symbol]; distance = decode(ctx, huff_dist); offset = read_bits(ctx, dext[distance]) + dists[distance]; for (int i = 0; i < length; ++i) { uint8_t b = peek(ctx, offset); emit(ctx, b); } } } return 0; } /** * Decode a dynamic Huffman block. */ static void decode_huffman(struct inflate_context * ctx) { /* Ordering of code length codes: * (HCLEN + 4) x 3 bits: code lengths for the code length * alphabet given just above, in the order: ... */ static const uint8_t clens[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; unsigned int literals, distances, clengths; uint8_t lengths[320] = {0}; literals = 257 + read_bits(ctx, 5); /* 5 Bits: HLIT ... 257 */ distances = 1 + read_bits(ctx, 5); /* 5 Bits: HDIST ... 1 */ clengths = 4 + read_bits(ctx, 4); /* 4 Bits: HCLEN ... 4 */ /* (HCLEN + 4) x 3 bits... */ for (unsigned int i = 0; i < clengths; ++i) { lengths[clens[i]] = read_bits(ctx, 3); } struct huff codes; build_huffman(lengths, 19, &codes); /* Decode symbols: * HLIT + 257 code lengths for the literal/length alphabet... * HDIST + 1 code lengths for the distance alphabet... */ unsigned int count = 0; while (count < literals + distances) { int symbol = decode(ctx, &codes); if (symbol < 16) { /* 0 - 15: Represent code lengths of 0-15 */ lengths[count++] = symbol; } else if (symbol < 19) { int rep = 0, length; if (symbol == 16) { /* 16: Copy the previous code length 3-6 times */ rep = lengths[count-1]; length = read_bits(ctx, 2) + 3; /* The next 2 bits indicate repeat length */ } else if (symbol == 17) { /* Repeat a code length of 0 for 3 - 10 times */ length = read_bits(ctx, 3) + 3; /* 3 bits of length */ } else if (symbol == 18) { /* Repeat a code length of 0 for 11 - 138 times */ length = read_bits(ctx, 7) + 11; /* 7 bits of length */ } do { lengths[count++] = rep; length--; } while (length); } else { break; } } /* Build tables from lenghts decoded above */ struct huff huff_len; build_huffman(lengths, literals, &huff_len); struct huff huff_dist; build_huffman(lengths + literals, distances, &huff_dist); inflate(ctx, &huff_len, &huff_dist); } /** * Decode an uncompressed block. */ static int uncompressed(struct inflate_context * ctx) { /* Reset byte alignment */ ctx->bit_buffer = 0; ctx->buffer_size = 0; /* "The rest of the block consists of the following information:" * 0 1 2 3 4... * +---+---+---+---+================================+ * | LEN | NLEN |... LEN bytes of literal data...| * +---+---+---+---+================================+ */ uint16_t len = read_16le(ctx); /* "the number of data bytes in the block" */ uint16_t nlen = read_16le(ctx); /* "the one's complement of LEN */ /* Sanity check - does the ones-complement length actually match? */ if ((nlen & 0xFFFF) != (~len & 0xFFFF)) { return 1; } /* Emit LEN bytes from the source to the output */ for (int i = 0; i < len; ++i) { emit(ctx, ctx->get_input(ctx)); } return 0; } static struct huff_ring data = {0, {0}}; /** * Decompress DEFLATE-compressed data. */ int deflate_decompress(struct inflate_context * ctx) { ctx->bit_buffer = 0; ctx->buffer_size = 0; build_fixed(); if (!ctx->ring) { ctx->ring = &data; } /* read compressed data */ while (1) { /* Read bit */ int is_final = read_bit(ctx); int type = read_bits(ctx, 2); switch (type) { case 0x00: /* BTYPE=00 Non-compressed blocks */ uncompressed(ctx); break; case 0x01: /* BYTPE=01 Compressed with fixed Huffman codes */ inflate(ctx, &fixed_lengths, &fixed_dists); break; case 0x02: /* BTYPE=02 Compression with dynamic Huffman codes */ decode_huffman(ctx); break; case 0x03: return 1; } if (is_final) { break; } } return 0; } #define GZIP_FLAG_TEXT (1 << 0) #define GZIP_FLAG_HCRC (1 << 1) #define GZIP_FLAG_EXTR (1 << 2) #define GZIP_FLAG_NAME (1 << 3) #define GZIP_FLAG_COMM (1 << 4) static unsigned int read_32le(struct inflate_context * ctx) { unsigned int a, b, c, d; a = ctx->get_input(ctx); b = ctx->get_input(ctx); c = ctx->get_input(ctx); d = ctx->get_input(ctx); return (d << 24) | (c << 16) | (b << 8) | (a << 0); } int gzip_decompress(struct inflate_context * ctx) { /* Read gzip headers */ if (ctx->get_input(ctx) != 0x1F) return 1; if (ctx->get_input(ctx) != 0x8B) return 1; unsigned int cm = ctx->get_input(ctx); if (cm != 8) return 1; unsigned int flags = ctx->get_input(ctx); /* Read mtime */ unsigned int mtime = read_32le(ctx); (void)mtime; /* Read extra flags */ unsigned int xflags = ctx->get_input(ctx); (void)xflags; /* Read and discord OS flag */ unsigned int os = ctx->get_input(ctx); (void)os; /* Extra bytes */ if (flags & GZIP_FLAG_EXTR) { unsigned short size = read_16le(ctx); for (unsigned int i = 0; i < size; ++i) ctx->get_input(ctx); } if (flags & GZIP_FLAG_NAME) { unsigned int c; while ((c = ctx->get_input(ctx)) != 0); } if (flags & GZIP_FLAG_COMM) { unsigned int c; while ((c = ctx->get_input(ctx)) != 0); } unsigned int crc16 = 0; if (flags & GZIP_FLAG_HCRC) { crc16 = read_16le(ctx); } (void)crc16; int status = deflate_decompress(ctx); /* Read CRC and decompressed size from end of input */ unsigned int crc32 = read_32le(ctx); unsigned int dsize = read_32le(ctx); (void)crc32; (void)dsize; return status; } ================================================ FILE: lib/jpeg.c ================================================ /** * @brief libtoaru_jpeg: Decode simple JPEGs. * * @copyright * 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 * * Adapted from Raul Aguaviva's Python "micro JPEG visualizer": * * MIT License * * Copyright (c) 2017 Raul Aguaviva * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ #include #include #include #include #include #include #if !defined(NO_SSE) && defined(__x86_64__) #include #include #endif #if 0 #include #define TRACE_APP_NAME "jpeg" #else #define TRACE(...) #endif static sprite_t * sprite = NULL; /* Byte swap short (because JPEG uses big-endian values) */ static void swap16(uint16_t * val) { char * a = (char *)val; char b = a[0]; a[0] = a[1]; a[1] = b; } /* JPEG compontent zig-zag ordering */ static int zigzag[] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 }; static uint8_t quant_mapping[3] = {0}; static uint8_t quant[8][64]; static int clamp(int col) { if (col > 255) return 255; if (col < 0) return 0; return col; } /* YCbCr to RGB conversion */ static void color_conversion( float Y, float Cb, float Cr, int *R, int *G, int *B ) { float r = (Cr*(2.0-2.0*0.299) + Y); float b = (Cb*(2.0-2.0*0.114) + Y); float g = (Y - 0.144 * b - 0.229 * r) / 0.587; *R = clamp(r + 128); *G = clamp(g + 128); *B = clamp(b + 128); } static int xy_to_lin(int x, int y) { return x + y * 8; } struct huffman_table { uint8_t lengths[16]; uint8_t elements[256]; } huffman_tables[256] = {0}; struct stream { FILE * file; uint8_t byte; int have; int pos; }; static void define_quant_table(FILE * f, int len) { TRACE("Defining quant table"); while (len > 0) { uint8_t hdr; fread(&hdr, 1, 1, f); fread(&quant[(hdr) & 0xF], 64, 1, f); len -= 65; } TRACE("Done"); } static void baseline_dct(FILE * f, int len) { struct dct { uint8_t hdr; uint16_t height; uint16_t width; uint8_t components; } __attribute__((packed)) dct; fread(&dct, sizeof(struct dct), 1, f); /* Read image dimensions, each as big-endian 16-bit values */ uint16_t h = dct.height; uint16_t w = dct.width; swap16(&h); swap16(&w); dct.height = h; dct.width = w; /* We read 7 bytes */ len -= sizeof(struct dct); TRACE("Image dimensions are %d×%d", dct.width, dct.height); sprite->width = dct.width; sprite->height = dct.height; sprite->bitmap = malloc(sizeof(uint32_t) * sprite->width * sprite->height); sprite->masks = NULL; sprite->alpha = 0; sprite->blank = 0; TRACE("Loading quantization mappings..."); for (int i = 0; i < dct.components; ++i) { /* Quant mapping */ struct { uint8_t id; uint8_t samp; uint8_t qtb_id; } __attribute__((packed)) tmp; fread(&tmp, sizeof(tmp), 1, f); /* There should only be three of these for the images we support. */ if (i > 3) { abort(); } quant_mapping[i] = tmp.qtb_id; /* 3 bytes were read */ len -= 3; } /* Skip whatever else might be in this section */ if (len > 0) { fseek(f, len, SEEK_CUR); } } static void define_huffman_table(FILE * f, int len) { TRACE("Loading Huffman tables..."); while (len > 0) { /* Read header ID */ uint8_t hdr; fread(&hdr, 1, 1, f); len--; /* Read length table */ fread(huffman_tables[hdr].lengths, 16, 1, f); len -= 16; /* Read Huffman table entries */ int o = 0; for (int i = 0; i < 16; ++i) { int l = huffman_tables[hdr].lengths[i]; fread(&huffman_tables[hdr].elements[o], l, 1, f); o += l; len -= l; } } /* Skip rest of section */ if (len > 0) { fseek(f, len, SEEK_CUR); } } struct idct { float base[64]; }; /** * norm_coeff[0] = 0.35355339059 * norm_coeff[1] = 0.5 */ static float cosines[8][8] = { { 0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059,0.35355339059 }, { 0.490392640202,0.415734806151,0.27778511651,0.0975451610081,-0.0975451610081,-0.27778511651,-0.415734806151,-0.490392640202 }, { 0.461939766256,0.191341716183,-0.191341716183,-0.461939766256,-0.461939766256,-0.191341716183,0.191341716183,0.461939766256 }, { 0.415734806151,-0.0975451610081,-0.490392640202,-0.27778511651,0.27778511651,0.490392640202,0.0975451610081,-0.415734806151 }, { 0.353553390593,-0.353553390593,-0.353553390593,0.353553390593,0.353553390593,-0.353553390593,-0.353553390593,0.353553390593 }, { 0.27778511651,-0.490392640202,0.0975451610081,0.415734806151,-0.415734806151,-0.0975451610081,0.490392640202,-0.27778511651 }, { 0.191341716183,-0.461939766256,0.461939766256,-0.191341716183,-0.191341716183,0.461939766256,-0.461939766256,0.191341716183 }, { 0.0975451610081,-0.27778511651,0.415734806151,-0.490392640202,0.490392640202,-0.415734806151,0.27778511651,-0.0975451610081 }, }; static float premul[8][8][8][8]= {{{{0}}}}; static void add_idc(struct idct * self, int n, int m, int coeff) { #if defined(NO_SSE) || !defined(__x86_64__) for (int y = 0; y < 8; ++y) { for (int x = 0; x < 8; ++x) { self->base[xy_to_lin(x, y)] += premul[n][m][y][x] * coeff; } } #else __m128 c = _mm_set_ps(coeff,coeff,coeff,coeff); for (int y = 0; y < 8; ++y) { __m128 a, b; /* base[y][x] = base[y][x] + premul[n][m][y][x] * coeff */ /* x = 0..3 */ a = _mm_load_ps(&premul[n][m][y][0]); a = _mm_mul_ps(a,c); b = _mm_load_ps(&self->base[xy_to_lin(0,y)]); a = _mm_add_ps(a,b); _mm_store_ps(&self->base[xy_to_lin(0,y)], a); /* x = 4..7 */ a = _mm_load_ps(&premul[n][m][y][4]); a = _mm_mul_ps(a,c); b = _mm_load_ps(&self->base[xy_to_lin(4,y)]); a = _mm_add_ps(a,b); _mm_store_ps(&self->base[xy_to_lin(4,y)], a); } #endif } static void add_zigzag(struct idct * self, int zi, int coeff) { int i = zigzag[zi]; int n = i & 0x7; int m = i >> 3; add_idc(self, n, m, coeff); } /* Read a bit from the stream */ static int get_bit(struct stream * st) { while ((st->pos >> 3) >= st->have) { /* We have finished using the current byte and need to read another one */ int t = fgetc(st->file); if (t < 0) { /* EOF */ st->byte = 0; } else { st->byte = t; } if (st->byte == 0xFF) { /* * If we see 0xFF, it's followed by a 0x00 * that should be skipped. */ int tmp = fgetc(st->file); if (tmp != 0) { /* * If it's *not*, we reached the end of the file - but * this shouldn't happen. */ st->byte = 0; } } /* We've seen a new byte */ st->have++; } /* Extract appropriate bit from this byte */ uint8_t b = st->byte; int s = 7 - (st->pos & 0x7); /* We move forward one position in the bit stream */ st->pos += 1; return (b >> s) & 1; } /* Advance forward and get the n'th next bit */ static int get_bitn(struct stream * st, int l) { int val = 0; for (int i = 0; i < l; ++i) { val = val * 2 + get_bit(st); } return val; } /* * Read a Huffman code by reading bits and using * the Huffman table. */ static int get_code(struct huffman_table * table, struct stream * st) { int val = 0; int off = 0; int ini = 0; for (int i = 0; i < 16; ++i) { val = val * 2 + get_bit(st); if (table->lengths[i] > 0) { if (val - ini < table->lengths[i]) { return table->elements[off + val - ini]; } ini = ini + table->lengths[i]; off += table->lengths[i]; } ini *= 2; } /* Invalid */ return -1; } /* Decode Huffman codes to values */ static int decode(int code, int bits) { int l = 1L << (code - 1); if (bits >= l) { return bits; } else { return bits - (2 * l - 1); } } /* Build IDCT matrix */ static struct idct * build_matrix(struct idct * i, struct stream * st, int idx, uint8_t * quant, int oldcoeff, int * outcoeff) { memset(i, 0, sizeof(struct idct)); int code = get_code(&huffman_tables[idx], st); int bits = get_bitn(st, code); int dccoeff = decode(code, bits) + oldcoeff; add_zigzag(i, 0, dccoeff * quant[0]); int l = 1; while (l < 64) { code = get_code(&huffman_tables[16+idx], st); if (code == 0) break; if (code > 15) { l += (code >> 4); code = code & 0xF; } bits = get_bitn(st, code); int coeff = decode(code, bits); add_zigzag(i, l, coeff * quant[l]); l += 1; } *outcoeff = dccoeff; return i; } /* Set pixel in sprite buffer with bounds checking */ static void set_pixel(int x, int y, uint32_t color) { if ((x < sprite->width) && (y < sprite->height)) { SPRITE(sprite,x,y) = color; } } /* Concvert YCbCr values to RGB pixels */ static void draw_matrix(int x, int y, struct idct * L, struct idct * cb, struct idct * cr) { for (int yy = 0; yy < 8; ++yy) { for (int xx = 0; xx < 8; ++xx) { int o = xy_to_lin(xx,yy); int r, g, b; color_conversion(L->base[o], cb->base[o], cr->base[o], &r, &g, &b); uint32_t c = 0xFF000000 | (r << 16) | (g << 8) | b; set_pixel((x * 8 + xx), (y * 8 + yy), c); } } } static void start_of_scan(FILE * f, int len) { TRACE("Reading image data"); /* Skip header */ fseek(f, len, SEEK_CUR); /* Initialize bit stream */ struct stream _st = {0}; _st.file = f; struct stream * st = &_st; int old_lum = 0; int old_crd = 0; int old_cbd = 0; for (int y = 0; y < sprite->height / 8 + !!(sprite->height & 0x7); ++y) { TRACE("Star row %d", y ); for (int x = 0; x < sprite->width / 8 + !!(sprite->width & 0x7); ++x) { if (y >= 134) { TRACE("Start col %d", x); } /* Build matrices */ struct idct matL, matCr, matCb; build_matrix(&matL, st, 0, quant[quant_mapping[0]], old_lum, &old_lum); build_matrix(&matCb, st, 1, quant[quant_mapping[1]], old_cbd, &old_cbd); build_matrix(&matCr, st, 1, quant[quant_mapping[2]], old_crd, &old_crd); if (y >= 134) { TRACE("Draw col %d", x); } draw_matrix(x, y, &matL, &matCb, &matCr); } } TRACE("Done."); } int load_sprite_jpg(sprite_t * tsprite, char * filename) { FILE * f = fopen(filename, "r"); if (!f) { return 1; } sprite = tsprite; memset(huffman_tables, 0, sizeof(huffman_tables)); if (premul[0][0][0][0] == 0.0) { for (int n = 0; n < 8; ++n) { for (int m = 0; m < 8; ++m) { for (int y = 0; y < 8; ++y) { for (int x = 0; x < 8; ++x) { premul[n][m][y][x] = cosines[n][x] * cosines[m][y]; } } } } } while (1) { /* Read a header */ uint16_t hdr; int r = fread(&hdr, 2, 1, f); if (r <= 0) { /* EOF */ break; } /* These headers are stored big-endian */ swap16(&hdr); if (hdr == 0xffd8) { /* No data */ continue; } else if (hdr == 0xffd9) { /* End of file */ break; } else { /* Regular sections with data start with a length */ uint16_t len; fread(&len, 2, 1, f); swap16(&len); /* Subtract two because the length includes itself */ len -= 2; if (hdr == 0xffdb) { define_quant_table(f, len); } else if (hdr == 0xffc0) { baseline_dct(f, len); } else if (hdr == 0xffc4) { define_huffman_table(f, len); } else if (hdr == 0xffda) { start_of_scan(f, len); /* End immediately after reading the data */ break; } else { TRACE("Unknown header\n"); fseek(f, len, SEEK_CUR); } } } fclose(f); return 0; } ================================================ FILE: lib/json.c ================================================ /** * @brief JSON parser. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include typedef struct JSON_Value Value; /* Internal usage */ struct JSON_Context { const char * string; int c; const char * error; }; void json_free(Value * v) { if (v->type == JSON_TYPE_STRING) { free(v->string); } if (v->type == JSON_TYPE_OBJECT) { hashmap_free(v->object); free(v->object); } if (v->type == JSON_TYPE_ARRAY) { foreach(node, v->array) { json_free(node->value); } list_free(v->array); free(v->array); } free(v); } static Value * value(struct JSON_Context * ctx); static int peek(struct JSON_Context * ctx) { return ctx->string[ctx->c]; } static void advance(struct JSON_Context * ctx) { ctx->c++; } static void whitespace(struct JSON_Context * ctx) { while (1) { int ch = peek(ctx); if (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t') { advance(ctx); } else { break; } } } static Value * string(struct JSON_Context * ctx) { if (peek(ctx) != '"') return NULL; advance(ctx); int size = 4; char * tmp = malloc(4); tmp[0] = 0; int used = 0; #define add(c) do { \ if (used + 1 == size) { \ size *= 2; \ tmp = realloc(tmp, size); \ } \ tmp[used] = c; \ tmp[used+1] = 0; \ used++; \ } while (0) while (1) { int ch = peek(ctx); if (ch == 0) goto string_error; if (ch == '"') { break; } else if (ch == '\\') { advance(ctx); ch = peek(ctx); if (ch == '"') add('"'); else if (ch == '\\') add('\\'); else if (ch == '/') add('/'); else if (ch == 'b') add('\b'); else if (ch == 'f') add('\f'); else if (ch == 'n') add('\n'); else if (ch == 'r') add('\r'); else if (ch == 't') add('\t'); else if (ch == 'u') { /* Parse hex */ advance(ctx); char hex_digits[5]; if (!isxdigit(peek(ctx))) goto string_error; hex_digits[0] = peek(ctx); advance(ctx); if (!isxdigit(peek(ctx))) goto string_error; hex_digits[1] = peek(ctx); advance(ctx); if (!isxdigit(peek(ctx))) goto string_error; hex_digits[2] = peek(ctx); advance(ctx); if (!isxdigit(peek(ctx))) goto string_error; hex_digits[3] = peek(ctx); /* will be advanced later */ hex_digits[4] = 0; uint32_t val = strtoul(hex_digits, NULL, 16); if (val < 0x0080) { add(val); } else if (val < 0x0800) { add(0xC0 | (val >> 6)); add(0x80 | (val & 0x3F)); } else { add(0xE0 | (val >> 12)); add(0x80 | ((val >> 6) & 0x3F)); add(0x80 | (val & 0x3F)); } } else { goto string_error; } advance(ctx); } else { add(ch); advance(ctx); } } if (peek(ctx) != '"') { ctx->error = "Unexpected EOF?"; goto string_error; } advance(ctx); Value * out = malloc(sizeof(Value)); out->type = JSON_TYPE_STRING; out->string = strdup(tmp); free(tmp); return out; string_error: free(tmp); return NULL; } static Value * object(struct JSON_Context * ctx) { if (peek(ctx) != '{') { ctx->error = "Expected { (internal error)"; return NULL; } advance(ctx); Value * out; hashmap_t * output = hashmap_create(10); output->hash_val_free = (void (*)(void *))json_free; whitespace(ctx); if (peek(ctx) == '}') { advance(ctx); goto _object_done; } while (1) { whitespace(ctx); Value * s = string(ctx); if (!s) { ctx->error = "Expected string"; break; } whitespace(ctx); if (peek(ctx) != ':') { ctx->error = "Expected :"; break; } advance(ctx); Value * v = value(ctx); hashmap_set(output, s->string, v); json_free(s); if (peek(ctx) == '}') { advance(ctx); goto _object_done; } if (peek(ctx) != ',') { ctx->error = "Expected , or {"; break; } advance(ctx); } hashmap_free(output); return NULL; _object_done: out = malloc(sizeof(Value)); out->type = JSON_TYPE_OBJECT; out->object = output; return out; } static Value * boolean(struct JSON_Context * ctx) { int value = -1; if (peek(ctx) == 't') { advance(ctx); if (peek(ctx) != 'r') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 'u') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 'e') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); value = 1; } else if (peek(ctx) == 'f') { advance(ctx); if (peek(ctx) != 'a') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 's') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); if (peek(ctx) != 'e') { ctx->error = "Invalid literal while parsing bool"; return NULL; } advance(ctx); value = 0; } else { ctx->error = "Invalid literal while parsing bool"; return NULL; } Value * out = malloc(sizeof(Value)); out->type = JSON_TYPE_BOOL; out->boolean = value; return out; } static Value * null(struct JSON_Context * ctx) { if (peek(ctx) != 'n') { ctx->error = "Invalid literal while parsing null"; return NULL; } advance(ctx); if (peek(ctx) != 'u') { ctx->error = "Invalid literal while parsing null"; return NULL; } advance(ctx); if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing null"; return NULL; } advance(ctx); if (peek(ctx) != 'l') { ctx->error = "Invalid literal while parsing null"; return NULL; } advance(ctx); Value * out = malloc(sizeof(Value)); out->type = JSON_TYPE_NULL; return out; } static Value * number(struct JSON_Context * ctx) { double value = 0; int sign = 1; if (peek(ctx) == '-') { /* Negative */ sign = -1; advance(ctx); } if (peek(ctx) == '0') { advance(ctx); } else if (isdigit(peek(ctx))) { /* Read any digit */ value = peek(ctx) - '0'; advance(ctx); while (isdigit(peek(ctx))) { value *= 10; value += peek(ctx) - '0'; advance(ctx); } } else { ctx->error = "Expected digit"; return NULL; } if (peek(ctx) == '.') { /* Read fractional part */ advance(ctx); double multiplier = 0.1; /* read at least one digit */ if (!isdigit(peek(ctx))) { ctx->error = "Expected digit"; return NULL; } while (isdigit(peek(ctx))) { value += multiplier * (peek(ctx) - '0'); multiplier *= 0.1; advance(ctx); } } if (peek(ctx) == 'e' || peek(ctx) == 'E') { /* Read exponent */ int exp_sign = 1; advance(ctx); if (peek(ctx) == '+') advance(ctx); else if (peek(ctx) == '-') { exp_sign = -1; advance(ctx); } /* read digits */ if (!isdigit(peek(ctx))) { ctx->error = "Expected digit"; return NULL; } double exp = peek(ctx) - '0'; advance(ctx); while (isdigit(peek(ctx))) { exp *= 10; exp += peek(ctx) - '0'; advance(ctx); } value = value * pow(10.0,exp * exp_sign); } Value * out = malloc(sizeof(Value)); out->type = JSON_TYPE_NUMBER; out->number = value * sign; return out; } static Value * array(struct JSON_Context * ctx) { if (peek(ctx) != '[') return NULL; advance(ctx); whitespace(ctx); list_t * output = list_create(); Value * out; if (peek(ctx) == ']') { advance(ctx); goto _array_done; } while (1) { Value * next = value(ctx); if (!next) break; list_insert(output, next); if (peek(ctx) == ']') { advance(ctx); goto _array_done; } if (peek(ctx) != ',') { ctx->error = "Expected ,"; break; } advance(ctx); } /* uh oh */ foreach(node, output) { json_free(node->value); } list_free(output); free(output); return NULL; _array_done: out = malloc(sizeof(Value)); out->type = JSON_TYPE_ARRAY; out->array = output; return out; } #define WHITE(c) { \ Value * out = c; \ whitespace(ctx); \ return out; \ } static Value * value(struct JSON_Context * ctx) { whitespace(ctx); if (peek(ctx) == '"') WHITE(string(ctx)) else if (peek(ctx) == '{') WHITE(object(ctx)) else if (peek(ctx) == '[') WHITE(array(ctx)) else if (peek(ctx) == '-' || isdigit(peek(ctx))) WHITE(number(ctx)) else if (peek(ctx) == 't') WHITE(boolean(ctx)) else if (peek(ctx) == 'f') WHITE(boolean(ctx)) else if (peek(ctx) == 'n') WHITE(null(ctx)) ctx->error = "Unexpected value"; return NULL; } Value * json_parse(const char * str) { struct JSON_Context ctx; ctx.string = str; ctx.c = 0; ctx.error = NULL; Value * out = value(&ctx); #if 0 if (!out) { fprintf(stderr, "JSON parse error at %d (%c)\n", ctx.c, ctx.string[ctx.c]); fprintf(stderr, "%s\n", ctx.error); fprintf(stderr, "%s\n", ctx.string); for (int i = 0; i < ctx.c; ++i) { fprintf(stderr, " "); } fprintf(stderr, "^\n"); } #endif return out; } Value * json_parse_file(const char * filename) { FILE * f = fopen(filename, "r"); if (!f) return NULL; fseek(f, 0, SEEK_END); size_t size = ftell(f); fseek(f, 0, SEEK_SET); char * tmp = malloc(size + 1); fread(tmp, size, 1, f); tmp[size] = 0; fclose(f); Value * out = json_parse(tmp); free(tmp); return out; } ================================================ FILE: lib/kbd.c ================================================ /** * @brief General-purpose keyboard conversion library. * * This provides similar functionality to xkb: * - It provides mappings for keyboards from locales * - It translates incoming key presses to key names * - It translates incoming keys to escape sequences * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2018 K. Lange */ #include #include #define DEBUG_SCANCODES 0 #define KEY_UP_MASK 0x80 #define KEY_CODE_MASK 0x7F #define KEY_CTRL_MASK 0x40 #define norm 0x01 #define spec 0x02 #define func 0x03 #define SET_UNSET(a,b,c) (a) = (c) ? ((a) | (b)) : ((a) & ~(b)) char key_method[] = { /* 00 */ 0, spec, norm, norm, norm, norm, norm, norm, /* 08 */ norm, norm, norm, norm, norm, norm, norm, norm, /* 10 */ norm, norm, norm, norm, norm, norm, norm, norm, /* 18 */ norm, norm, norm, norm, norm, spec, norm, norm, /* 20 */ norm, norm, norm, norm, norm, norm, norm, norm, /* 28 */ norm, norm, spec, norm, norm, norm, norm, norm, /* 30 */ norm, norm, norm, norm, norm, norm, spec, norm, /* 38 */ spec, norm, spec, func, func, func, func, func, /* 40 */ func, func, func, func, func, spec, spec, spec, /* 48 */ spec, spec, spec, spec, spec, spec, spec, spec, /* 50 */ spec, spec, spec, spec, spec, spec, spec, func, /* 58 */ func, spec, spec, spec, spec, spec, spec, spec, /* 60 */ spec, spec, spec, spec, spec, spec, spec, spec, /* 68 */ spec, spec, spec, spec, spec, spec, spec, spec, /* 70 */ spec, spec, spec, spec, spec, spec, spec, spec, /* 78 */ spec, spec, spec, spec, spec, spec, spec, spec, }; char kbd_us[128] = { 0, 27, '1','2','3','4','5','6','7','8','9','0', '-','=','\b', '\t', /* tab */ 'q','w','e','r','t','y','u','i','o','p','[',']','\n', 0, /* control */ 'a','s','d','f','g','h','j','k','l',';','\'', '`', 0, /* left shift */ '\\','z','x','c','v','b','n','m',',','.','/', 0, /* right shift */ '*', 0, /* alt */ ' ', /* space */ 0, /* caps lock */ 0, /* F1 [59] */ 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ... F10 */ 0, /* 69 num lock */ 0, /* scroll lock */ 0, /* home */ 0, /* up */ 0, /* page up */ '-', 0, /* left arrow */ 0, 0, /* right arrow */ '+', 0, /* 79 end */ 0, /* down */ 0, /* page down */ 0, /* insert */ 0, /* delete */ 0, 0, 0, 0, /* F11 */ 0, /* F12 */ 0, /* everything else */ }; char kbd_us_l2[128] = { 0, 27, '!','@','#','$','%','^','&','*','(',')', '_','+','\b', '\t', /* tab */ 'Q','W','E','R','T','Y','U','I','O','P','{','}','\n', 0, /* control */ 'A','S','D','F','G','H','J','K','L',':','"', '~', 0, /* left shift */ '|','Z','X','C','V','B','N','M','<','>','?', 0, /* right shift */ '*', 0, /* alt */ ' ', /* space */ 0, /* caps lock */ 0, /* F1 [59] */ 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ... F10 */ 0, /* 69 num lock */ 0, /* scroll lock */ 0, /* home */ 0, /* up */ 0, /* page up */ '-', 0, /* left arrow */ 0, 0, /* right arrow */ '+', 0, /* 79 end */ 0, /* down */ 0, /* page down */ 0, /* insert */ 0, /* delete */ 0, 0, 0, 0, /* F11 */ 0, /* F12 */ 0, /* everything else */ }; #define KEY_SCANCODE_F1 0x3b #define KEY_SCANCODE_F2 0x3c #define KEY_SCANCODE_F3 0x3d #define KEY_SCANCODE_F4 0x3e #define KEY_SCANCODE_F5 0x3f #define KEY_SCANCODE_F6 0x40 #define KEY_SCANCODE_F7 0x41 #define KEY_SCANCODE_F8 0x42 #define KEY_SCANCODE_F9 0x43 #define KEY_SCANCODE_F10 0x44 #define KEY_SCANCODE_F11 0x57 #define KEY_SCANCODE_F12 0x58 #define KEY_SCANCODE_NUM_1 0x4f #define KEY_SCANCODE_NUM_2 0x50 #define KEY_SCANCODE_NUM_3 0x51 #define KEY_SCANCODE_NUM_4 0x4B #define KEY_SCANCODE_NUM_5 0x4C #define KEY_SCANCODE_NUM_6 0x4D #define KEY_SCANCODE_NUM_7 0x47 #define KEY_SCANCODE_NUM_8 0x48 #define KEY_SCANCODE_NUM_9 0x49 #define KEY_SCANCODE_NUM_0 0x52 #define KEY_SCANCODE_NUM_DOT 0x53 #define KEY_SCANCODE_NUM_MIN 0x4a #define KEY_SCANCODE_NUM_ADD 0x4e #define KEY_SCANCODE_NUM_LK 0x45 #define KEY_SCANCODE_SCROLL 0x46 int kbd_scancode(key_event_state_t * state, unsigned char c, key_event_t * event) { /* Convert scancodes to a series of keys */ event->keycode = 0; event->action = 0; event->modifiers = 0; event->key = 0; #if DEBUG_SCANCODES fprintf(stderr, "[%d] %d\n", state->kbd_s_state, (int)c); #endif event->modifiers |= state->kl_ctrl ? KEY_MOD_LEFT_CTRL : 0; event->modifiers |= state->kl_shift ? KEY_MOD_LEFT_SHIFT : 0; event->modifiers |= state->kl_alt ? KEY_MOD_LEFT_ALT : 0; event->modifiers |= state->kl_super ? KEY_MOD_LEFT_SUPER : 0; event->modifiers |= state->kr_ctrl ? KEY_MOD_RIGHT_CTRL : 0; event->modifiers |= state->kr_shift ? KEY_MOD_RIGHT_SHIFT : 0; event->modifiers |= state->kr_alt ? KEY_MOD_RIGHT_ALT : 0; event->modifiers |= state->kr_super ? KEY_MOD_RIGHT_SUPER : 0; if (!state->kbd_s_state) { if (c == 0xE0) { state->kbd_s_state = 1; return 0; } event->action = (c & KEY_UP_MASK) ? KEY_ACTION_UP : KEY_ACTION_DOWN; c &= 0x7F; int down = (event->action == KEY_ACTION_DOWN); switch (key_method[c]) { case norm: { event->keycode = kbd_us[c]; if (state->k_ctrl) { int s = kbd_us[c]; if (s >= 'a' && s <= 'z') s -= 'a' - 'A'; if (s == '-') s = '_'; if (s == '`') s = '@'; int out = (int)(s - KEY_CTRL_MASK); if (out < 0 || out > 0x1F) { event->key = kbd_us[c]; } else { event->key = out; } } else { event->key = state->k_shift ? kbd_us_l2[c] : kbd_us[c]; } } return 1; case spec: switch (c) { case 0x01: event->key = '\033'; event->keycode = KEY_ESCAPE; return 1; case 0x1D: state->k_ctrl = down; state->kl_ctrl = down; event->keycode = KEY_LEFT_CTRL; SET_UNSET(event->modifiers, KEY_MOD_LEFT_CTRL, down); return 1; case 0x2A: state->k_shift = down; state->kl_shift = down; event->keycode = KEY_LEFT_SHIFT; SET_UNSET(event->modifiers, KEY_MOD_LEFT_SHIFT, down); return 1; case 0x36: state->k_shift = down; state->kr_shift = down; event->keycode = KEY_RIGHT_SHIFT; SET_UNSET(event->modifiers, KEY_MOD_RIGHT_SHIFT, down); return 1; case 0x38: state->k_alt = down; state->kl_alt = down; event->keycode = KEY_LEFT_ALT; SET_UNSET(event->modifiers, KEY_MOD_LEFT_ALT, down); return 1; case KEY_SCANCODE_NUM_0: event->keycode = KEY_NUM_0; event->key = '0'; return 1; case KEY_SCANCODE_NUM_1: event->keycode = KEY_NUM_1; event->key = '1'; return 1; case KEY_SCANCODE_NUM_2: event->keycode = KEY_NUM_2; event->key = '2'; return 1; case KEY_SCANCODE_NUM_3: event->keycode = KEY_NUM_3; event->key = '3'; return 1; case KEY_SCANCODE_NUM_4: event->keycode = KEY_NUM_4; event->key = '4'; return 1; case KEY_SCANCODE_NUM_5: event->keycode = KEY_NUM_5; event->key = '5'; return 1; case KEY_SCANCODE_NUM_6: event->keycode = KEY_NUM_6; event->key = '6'; return 1; case KEY_SCANCODE_NUM_7: event->keycode = KEY_NUM_7; event->key = '7'; return 1; case KEY_SCANCODE_NUM_8: event->keycode = KEY_NUM_8; event->key = '8'; return 1; case KEY_SCANCODE_NUM_9: event->keycode = KEY_NUM_9; event->key = '9'; return 1; case KEY_SCANCODE_NUM_DOT: event->keycode = KEY_NUM_DOT; event->key = '.'; return 1; case KEY_SCANCODE_NUM_MIN: event->keycode = KEY_NUM_MINUS; event->key = '-'; return 1; case KEY_SCANCODE_NUM_ADD: event->keycode = KEY_NUM_PLUS; event->key = '+'; return 1; default: break; } break; case func: switch (c) { case KEY_SCANCODE_F1: event->keycode = KEY_F1; return 1; case KEY_SCANCODE_F2: event->keycode = KEY_F2; return 1; case KEY_SCANCODE_F3: event->keycode = KEY_F3; return 1; case KEY_SCANCODE_F4: event->keycode = KEY_F4; return 1; case KEY_SCANCODE_F5: event->keycode = KEY_F5; return 1; case KEY_SCANCODE_F6: event->keycode = KEY_F6; return 1; case KEY_SCANCODE_F7: event->keycode = KEY_F7; return 1; case KEY_SCANCODE_F8: event->keycode = KEY_F8; return 1; case KEY_SCANCODE_F9: event->keycode = KEY_F9; return 1; case KEY_SCANCODE_F10: event->keycode = KEY_F10; return 1; case KEY_SCANCODE_F11: event->keycode = KEY_F11; return 1; case KEY_SCANCODE_F12: event->keycode = KEY_F12; return 1; } break; default: break; } return 0; } else if (state->kbd_s_state == 1) { state->kbd_s_state = 0; event->action = (c & KEY_UP_MASK) ? KEY_ACTION_UP : KEY_ACTION_DOWN; c &= 0x7F; int down = (event->action == KEY_ACTION_DOWN); switch (c) { case 0x5B: state->k_super = down; state->kl_super = down; event->keycode = KEY_LEFT_SUPER; SET_UNSET(event->modifiers, KEY_MOD_LEFT_SUPER, down); return 1; case 0x5C: state->k_super = down; state->kr_super = down; event->keycode = KEY_RIGHT_SUPER; SET_UNSET(event->modifiers, KEY_MOD_RIGHT_SUPER, down); return 1; case 0x1D: state->kr_ctrl = down; state->k_ctrl = down; event->keycode = KEY_RIGHT_CTRL; SET_UNSET(event->modifiers, KEY_MOD_RIGHT_CTRL, down); return 1; case 0x38: state->kr_alt = down; state->k_alt = down; event->keycode = KEY_RIGHT_ALT; SET_UNSET(event->modifiers, KEY_MOD_RIGHT_ALT, down); return 1; case 0x48: event->keycode = KEY_ARROW_UP; return 1; case 0x4D: event->keycode = KEY_ARROW_RIGHT; return 1; case 0x47: event->keycode = KEY_HOME; return 1; case 0x49: event->keycode = KEY_PAGE_UP; return 1; case 0x4B: event->keycode = KEY_ARROW_LEFT; return 1; case 0x4F: event->keycode = KEY_END; return 1; case 0x50: event->keycode = KEY_ARROW_DOWN; return 1; case 0x51: event->keycode = KEY_PAGE_DOWN; return 1; case 0x52: event->keycode = KEY_INSERT; return 1; case 0x53: event->keycode = KEY_DEL; return 1; case 0x35: event->keycode = KEY_NUM_DIV; event->key = '/'; return 1; case 0x1C: event->keycode = KEY_NUM_ENTER; event->key = '\n'; return 1; case 0x37: event->keycode = KEY_PRINT_SCREEN; return 1; case 0x5D: event->keycode = KEY_APP; return 1; default: break; } } return 0; } ================================================ FILE: lib/kuroko/_waitpid.c ================================================ #include #include #include #include #include #include #include KRK_Function(waitpid) { int pid = -1; int options = 0; if (!krk_parseArgs("|ii",(const char*[]){"pid","options"}, &pid, &options)) return NONE_VAL(); int status = 0; pid_t result = waitpid(pid, &status, options); if (result == -1) { return krk_runtimeError(KRK_EXC(OSError), "%s", strerror(errno)); } krk_push(OBJECT_VAL(krk_newTuple(2))); AS_TUPLE(krk_peek(0))->values.values[0] = INTEGER_VAL(result); AS_TUPLE(krk_peek(0))->values.values[1] = INTEGER_VAL(status); AS_TUPLE(krk_peek(0))->values.count = 2; return krk_pop(); } KRK_Module(_waitpid) { BIND_FUNC(module,waitpid); #define BIND_CONST(name) krk_attachNamedValue(&module->fields, #name, INTEGER_VAL(name)) BIND_CONST(WNOHANG); BIND_CONST(WUNTRACED); BIND_CONST(WSTOPPED); BIND_CONST(WNOKERN); } ================================================ FILE: lib/kuroko/_yutani2.c ================================================ #include #include #include #include #include #include #include #include #include #include static KrkInstance * _module; #define CURRENT_NAME self #define WRAP_TYPE(kname,yname,pname,...) \ static KrkClass * kname; \ struct _yutani_ ## kname { \ KrkInstance inst; \ yname * pname; \ __VA_ARGS__ \ }; \ __attribute__((unused)) \ static int _init_check_ ## kname (struct _yutani_ ## kname * self) { \ return self->pname != NULL; \ } #define NO_REINIT(type) do { if (_init_check_ ## type (self)) return krk_runtimeError(vm.exceptions->typeError, "Can not reinit " #type); } while (0) #define INIT_CHECK(type) do { if (!_init_check_ ## type (self)) return krk_runtimeError(vm.exceptions->typeError, #type " object uninitialized"); } while (0) WRAP_TYPE(Message,yutani_msg_t,msg); #define IS_Message(o) (krk_isInstanceOf(o,Message)) #define AS_Message(o) ((struct _yutani_Message*)AS_OBJECT(o)) static void _Message_gcsweep(KrkInstance * _self) { struct _yutani_Message * self = (void*)_self; free(self->msg); } #define CURRENT_CTYPE struct _yutani_Message* KRK_StaticMethod(Message,__new__) { return krk_runtimeError(vm.exceptions->typeError, "can not instantiate Message"); } /* Base type stuff */ KRK_Method(Message,msg_magic) { ATTRIBUTE_NOT_ASSIGNABLE(); return INTEGER_VAL(self->msg->magic); } KRK_Method(Message,msg_type) { ATTRIBUTE_NOT_ASSIGNABLE(); return INTEGER_VAL(self->msg->type); } KRK_Method(Message,msg_size) { ATTRIBUTE_NOT_ASSIGNABLE(); return INTEGER_VAL(self->msg->size); } KRK_Method(Message,__repr__) { struct StringBuilder sb = {0}; pushStringBuilder(&sb,'<'); const char * typeName = krk_typeName(argv[0]); pushStringBuilderStr(&sb, typeName, strlen(typeName)); pushStringBuilder(&sb, '>'); return finishStringBuilder(&sb); } #undef CURRENT_CTYPE /* Subclass stuff */ #define MSG_CLS(type) Message_ ## type #define WRAP_PROP_INT(cls,kname) KRK_Method(cls,kname) { return INTEGER_VAL(self->kname); } #define WRAP_PROP_BOOL(cls,kname) KRK_Method(cls,kname) { return BOOLEAN_VAL(self->kname); } static KrkClass * MSG_CLS(Welcome); #define AS_Message_Welcome(o) ((struct yutani_msg_welcome*)AS_Message(o)->msg->data) #define IS_Message_Welcome(o) (krk_isInstanceOf(o,MSG_CLS(Welcome))) #define CURRENT_CTYPE struct yutani_msg_welcome * WRAP_PROP_INT(Message_Welcome,display_width) WRAP_PROP_INT(Message_Welcome,display_height) #undef CURRENT_CTYPE static KrkClass * MSG_CLS(WindowMouseEvent); #define AS_Message_WindowMouseEvent(o) ((struct yutani_msg_window_mouse_event*)AS_Message(o)->msg->data) #define IS_Message_WindowMouseEvent(o) (krk_isInstanceOf(o,MSG_CLS(WindowMouseEvent))) #define CURRENT_CTYPE struct yutani_msg_window_mouse_event * WRAP_PROP_INT(Message_WindowMouseEvent,wid) WRAP_PROP_INT(Message_WindowMouseEvent,new_x) WRAP_PROP_INT(Message_WindowMouseEvent,new_y) WRAP_PROP_INT(Message_WindowMouseEvent,old_x) WRAP_PROP_INT(Message_WindowMouseEvent,old_y) WRAP_PROP_INT(Message_WindowMouseEvent,buttons) WRAP_PROP_INT(Message_WindowMouseEvent,command) WRAP_PROP_INT(Message_WindowMouseEvent,modifiers) #undef CURRENT_CTYPE #define AS_Message_WindowFocusChange(o) ((struct yutani_msg_window_focus_change*)AS_Message(o)->msg->data) #define IS_Message_WindowFocusChange(o) (krk_isInstanceOf(o,MSG_CLS(WindowFocusChange))) #define CURRENT_CTYPE struct yutani_msg_window_focus_change * static KrkClass * MSG_CLS(WindowFocusChange); WRAP_PROP_INT(Message_WindowFocusChange,wid) WRAP_PROP_BOOL(Message_WindowFocusChange,focused) #undef CURRENT_CTYPE #define AS_Message_ResizeOffer(o) ((struct yutani_msg_window_resize*)AS_Message(o)->msg->data) #define IS_Message_ResizeOffer(o) (krk_isInstanceOf(o,MSG_CLS(ResizeOffer))) #define CURRENT_CTYPE struct yutani_msg_window_resize * static KrkClass * MSG_CLS(ResizeOffer); WRAP_PROP_INT(Message_ResizeOffer,wid) WRAP_PROP_INT(Message_ResizeOffer,width) WRAP_PROP_INT(Message_ResizeOffer,height) WRAP_PROP_INT(Message_ResizeOffer,bufid) #undef CURRENT_CTYPE #define AS_Message_WindowAdvertise(o) ((struct yutani_msg_window_advertise*)AS_Message(o)->msg->data) #define IS_Message_WindowAdvertise(o) (krk_isInstanceOf(o,MSG_CLS(WindowAdvertise))) #define CURRENT_CTYPE struct yutani_msg_window_advertise * static KrkClass * MSG_CLS(WindowAdvertise); WRAP_PROP_INT(Message_WindowAdvertise,wid) WRAP_PROP_INT(Message_WindowAdvertise,flags) WRAP_PROP_INT(Message_WindowAdvertise,size) WRAP_PROP_INT(Message_WindowAdvertise,width) WRAP_PROP_INT(Message_WindowAdvertise,height) WRAP_PROP_INT(Message_WindowAdvertise,bufid) KRK_Method(Message_WindowAdvertise,name) { char * s = self->strings; size_t l = strlen(s); return OBJECT_VAL(krk_copyString(s,l)); } KRK_Method(Message_WindowAdvertise,icon) { char * s = self->strings + self->icon; size_t l = strlen(s); return OBJECT_VAL(krk_copyString(s,l)); } #undef CURRENT_CTYPE #define AS_Message_WindowMove(o) ((struct yutani_msg_window_move*)AS_Message(o)->msg->data) #define IS_Message_WindowMove(o) (krk_isInstanceOf(o,MSG_CLS(WindowMove))) #define CURRENT_CTYPE struct yutani_msg_window_move * static KrkClass * MSG_CLS(WindowMove); WRAP_PROP_INT(Message_WindowMove,wid) WRAP_PROP_INT(Message_WindowMove,x) WRAP_PROP_INT(Message_WindowMove,y) #undef CURRENT_CTYPE #define AS_Message_KeyEvent(o) ((struct yutani_msg_key_event*)AS_Message(o)->msg->data) #define IS_Message_KeyEvent(o) (krk_isInstanceOf(o,MSG_CLS(KeyEvent))) #define CURRENT_CTYPE struct yutani_msg_key_event * static KrkClass * MSG_CLS(KeyEvent); WRAP_PROP_INT(Message_KeyEvent,wid) #define WRAP_PROP_FROM(f,p) KRK_Method(Message_KeyEvent,p) { return INTEGER_VAL(self->f.p); } WRAP_PROP_FROM(event,keycode) WRAP_PROP_FROM(event,modifiers) WRAP_PROP_FROM(event,action) WRAP_PROP_FROM(event,key) WRAP_PROP_FROM(state,kbd_state) WRAP_PROP_FROM(state,kbd_s_state) WRAP_PROP_FROM(state,k_ctrl) WRAP_PROP_FROM(state,k_shift) WRAP_PROP_FROM(state,k_alt) WRAP_PROP_FROM(state,k_super) WRAP_PROP_FROM(state,kl_ctrl) WRAP_PROP_FROM(state,kl_shift) WRAP_PROP_FROM(state,kl_alt) WRAP_PROP_FROM(state,kl_super) WRAP_PROP_FROM(state,kr_ctrl) WRAP_PROP_FROM(state,kr_shift) WRAP_PROP_FROM(state,kr_alt) WRAP_PROP_FROM(state,kr_super) WRAP_PROP_FROM(state,kbd_esc_buf) #undef CURRENT_CTYPE #define AS_Message_WindowClose(o) ((struct yutani_msg_key_event*)AS_Message(o)->msg->data) #define IS_Message_WindowClose(o) (krk_isInstanceOf(o,MSG_CLS(WindowClose))) #define CURRENT_CTYPE struct yutani_msg_key_event * static KrkClass * MSG_CLS(WindowClose); WRAP_PROP_INT(Message_WindowClose,wid) #undef CURRENT_CTYPE WRAP_TYPE(YutaniCtx,yutani_t,yctx); #define AS_YutaniCtx(o) ((struct _yutani_YutaniCtx*)AS_OBJECT(o)) #define IS_YutaniCtx(o) (krk_isInstanceOf(o,YutaniCtx)) #define CURRENT_CTYPE struct _yutani_YutaniCtx* static struct _yutani_YutaniCtx * yctxInstance; KRK_StaticMethod(YutaniCtx,__new__) { if (yctxInstance) return OBJECT_VAL(yctxInstance); KrkClass * cls; if (!krk_parseArgs("O!:YutaniCtx", (const char*[]){"cls"}, KRK_BASE_CLASS(type), &cls)) { return NONE_VAL(); } CURRENT_CTYPE self = (CURRENT_CTYPE)krk_newInstance(cls); krk_push(OBJECT_VAL(self)); yutani_t * yctx = yutani_init(); if (!yctx) return krk_runtimeError(vm.exceptions->ioError, "Failed to connect to compositor."); yctxInstance = self; self->yctx = yctx; init_decorations(); krk_attachNamedObject(&_module->fields, "_yutani_t", (KrkObj*)self); return krk_pop(); } KRK_Method(YutaniCtx,display_width) { return INTEGER_VAL(self->yctx->display_width); } KRK_Method(YutaniCtx,display_height) { return INTEGER_VAL(self->yctx->display_height); } static KrkValue makeMessage(yutani_msg_t * result) { if (!result) return NONE_VAL(); KrkClass * msgType = Message; switch (result->type) { case YUTANI_MSG_WELCOME: msgType = Message_Welcome; break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: msgType = Message_WindowMouseEvent; break; case YUTANI_MSG_WINDOW_FOCUS_CHANGE: msgType = Message_WindowFocusChange; break; case YUTANI_MSG_RESIZE_OFFER: msgType = Message_ResizeOffer; break; case YUTANI_MSG_WINDOW_ADVERTISE: msgType = Message_WindowAdvertise; break; case YUTANI_MSG_WINDOW_MOVE: msgType = Message_WindowMove; break; case YUTANI_MSG_KEY_EVENT: msgType = Message_KeyEvent; break; case YUTANI_MSG_WINDOW_CLOSE: msgType = Message_WindowClose; break; default: break; } struct _yutani_Message * out = (void*)krk_newInstance(msgType); out->msg = result; return OBJECT_VAL(out); } KRK_Method(YutaniCtx,poll) { int sync = 1; if (!krk_parseArgs(".|p", (const char *[]){"sync"}, &sync)) return NONE_VAL(); yutani_msg_t * result = sync ? yutani_poll(self->yctx) : yutani_poll_async(self->yctx); return makeMessage(result); } KRK_Method(YutaniCtx,wait_for) { int msgtype; if (!krk_parseArgs(".i",(const char*[]){"msgtype"}, &msgtype)) return NONE_VAL(); yutani_msg_t * result = yutani_wait_for(self->yctx, msgtype); return makeMessage(result); } KRK_Method(YutaniCtx,subscribe) { yutani_subscribe_windows(self->yctx); return NONE_VAL(); } KRK_Method(YutaniCtx,unsubscribe) { yutani_unsubscribe_windows(self->yctx); return NONE_VAL(); } KRK_Method(YutaniCtx,query_windows) { yutani_query_windows(self->yctx); return NONE_VAL(); } KRK_Method(YutaniCtx,fileno) { return INTEGER_VAL(fileno(self->yctx->sock)); } KRK_Method(YutaniCtx,query) { return INTEGER_VAL(yutani_query(self->yctx)); } KRK_Method(YutaniCtx,menu_process_event) { struct _yutani_Message * message = NULL; if (!krk_parseArgs(".O!",(const char *[]){"message"}, Message, &message)) return NONE_VAL(); return INTEGER_VAL(menu_process_event(self->yctx, message->msg)); } #undef CURRENT_CTYPE WRAP_TYPE(GraphicsContext,gfx_context_t,ctx, int doubleBuffered; ); WRAP_TYPE(Sprite,gfx_context_t,ctx, int doubleBuffered; sprite_t * sprite; ); WRAP_TYPE(Window,gfx_context_t,ctx, int doubleBuffered; yutani_window_t * window; KrkValue title; KrkValue icon; int closed; ); WRAP_TYPE(Subregion,gfx_context_t,ctx, int doubleBuffered; int x; int y; ); #define IS_GraphicsContext(o) (krk_isInstanceOf(o,GraphicsContext)) #define AS_GraphicsContext(o) ((struct _yutani_GraphicsContext*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_GraphicsContext* #define CHECK_GFX() do { if (!self->ctx) return krk_runtimeError(vm.exceptions->valueError, "invalid context"); } while (0) KRK_StaticMethod(GraphicsContext,__new__) { KrkClass * cls; int _argc; const KrkValue * _argv; if (!krk_parseArgs("O!*~",(const char*[]){"cls"},KRK_BASE_CLASS(type),&cls,&_argc,&_argv)) return NONE_VAL(); if (!krk_isSubClass(cls, GraphicsContext)) { return krk_runtimeError(vm.exceptions->typeError, "%S is not a subclass of GraphicsContext", cls->name); } if (cls == GraphicsContext) { return krk_runtimeError(vm.exceptions->typeError, "Can not create GraphicsContext"); } return OBJECT_VAL(krk_newInstance(cls)); } KRK_Method(GraphicsContext,fill) { uint32_t color; if (!krk_parseArgs(".I",(const char*[]){"color"},&color)) return NONE_VAL(); CHECK_GFX(); draw_fill(self->ctx, color); return NONE_VAL(); } KRK_Method(GraphicsContext,flip) { CHECK_GFX(); if (self->doubleBuffered) { flip(self->ctx); } return NONE_VAL(); } KRK_Method(GraphicsContext,blur) { int radius = 2; if (!krk_parseArgs(".|I",(const char*[]){"radius"}, &radius)) return NONE_VAL(); CHECK_GFX(); blur_context_box(self->ctx, radius); return NONE_VAL(); } KRK_Method(GraphicsContext,line) { int x0, x1, y0, y1; uint32_t color; KrkValue thickness = NONE_VAL(); if (!krk_parseArgs(".iiiiI|V",(const char*[]){"x0","x1","y0","y1","color","thickness"}, &x0, &x1, &y0, &y1, &color, &thickness)) return NONE_VAL(); if (IS_NONE(thickness)) { draw_line(self->ctx, x0, x1, y0, y1, color); } else if (IS_INTEGER(thickness)) { draw_line_thick(self->ctx, x0, x1, y0, y1, color, AS_INTEGER(thickness)); } else if (IS_FLOATING(thickness)) { draw_line_aa(self->ctx, x0, x1, y0, y1, color, AS_FLOATING(thickness)); } else { TYPE_ERROR(int or float,thickness); } return NONE_VAL(); } KRK_Method(GraphicsContext,rect) { int x, y; unsigned int width, height; uint32_t color; int solid = 0; unsigned int radius = 0; if (!krk_parseArgs(".iiIII|pI",(const char*[]){"x","y","width","height","color","solid","radius"}, &x, &y, &width, &height, &color, &solid, &radius)) return NONE_VAL(); if (solid && radius) { return krk_runtimeError(vm.exceptions->valueError, "can not combine 'radius' and 'solid'"); } CHECK_GFX(); if (radius) { draw_rounded_rectangle(self->ctx, x, y, width, height, radius, color); } else if (solid) { draw_rectangle_solid(self->ctx, x, y, width, height, color); } else { draw_rectangle(self->ctx, x, y, width, height, color); } return NONE_VAL(); } KRK_Method(GraphicsContext,width) { CHECK_GFX(); return INTEGER_VAL(self->ctx->width); } KRK_Method(GraphicsContext,height) { CHECK_GFX(); return INTEGER_VAL(self->ctx->height); } KRK_Method(GraphicsContext,isDoubleBuffered) { CHECK_GFX(); return BOOLEAN_VAL(self->doubleBuffered); } KRK_Method(GraphicsContext,__setitem__) { CHECK_GFX(); KrkTuple * coord = NULL; uint32_t color = 0; if (!krk_parseArgs( ".O!I",(const char*[]){"coord","color"}, KRK_BASE_CLASS(tuple), &coord, &color)) { return NONE_VAL(); } if (!coord || coord->values.count != 2 || !IS_INTEGER(coord->values.values[0]) || !IS_INTEGER(coord->values.values[1])) { return krk_runtimeError(vm.exceptions->typeError, "coord must be (int,int)"); } int x = AS_INTEGER(coord->values.values[0]); int y = AS_INTEGER(coord->values.values[1]); if (x < 0 || x >= self->ctx->width || y < 0 || y >= self->ctx->height) { return krk_runtimeError(vm.exceptions->indexError, "coordinate out of bounds"); } GFX(self->ctx,x,y) = color; return INTEGER_VAL(color); } KRK_Method(GraphicsContext,__getitem__) { CHECK_GFX(); KrkTuple * coord = NULL; if (!krk_parseArgs( ".O!",(const char*[]){"coord"}, KRK_BASE_CLASS(tuple), &coord)) { return NONE_VAL(); } if (!coord || coord->values.count != 2 || !IS_INTEGER(coord->values.values[0]) || !IS_INTEGER(coord->values.values[1])) { return krk_runtimeError(vm.exceptions->typeError, "coord must be (int,int)"); } int x = AS_INTEGER(coord->values.values[0]); int y = AS_INTEGER(coord->values.values[1]); if (x < 0 || x >= self->ctx->width || y < 0 || y >= self->ctx->height) { return krk_runtimeError(vm.exceptions->indexError, "coordinate out of bounds"); } return INTEGER_VAL(GFX(self->ctx,x,y)); } KRK_Method(GraphicsContext,draw_sprite) { CHECK_GFX(); struct _yutani_Sprite * sprite; int x = 0; int y = 0; double alpha = 1.0; double rotation = 0.0; KrkTuple * scale = NULL; uint32_t color = 0; if (!krk_parseArgs( ".O!|iiddO!I",(const char*[]){"sprite","x","y","alpha","rotation","scale","color"}, Sprite, &sprite, &x, &y, &alpha, &rotation, KRK_BASE_CLASS(tuple), &scale, &color)) { return NONE_VAL(); } if (scale) { if (scale->values.count != 2 || !IS_INTEGER(scale->values.values[0]) || !IS_INTEGER(scale->values.values[1])) { return krk_runtimeError(vm.exceptions->typeError, "scale must be (int,int)"); } int32_t width = AS_INTEGER(scale->values.values[0]); int32_t height = AS_INTEGER(scale->values.values[1]); if (alpha == 1.0) { draw_sprite_scaled(self->ctx, sprite->sprite, x, y, width, height); } else { draw_sprite_scaled_alpha(self->ctx, sprite->sprite, x, y, width, height, alpha); } } else if (color) { draw_sprite_alpha_paint(self->ctx, sprite->sprite, x, y, alpha, color); } else if (rotation != 0.0) { draw_sprite_rotate(self->ctx, sprite->sprite, x, y, rotation, alpha); } else if (alpha == 1.0) { draw_sprite(self->ctx, sprite->sprite, x, y); } else { draw_sprite_alpha(self->ctx, sprite->sprite, x, y, alpha); } return NONE_VAL(); } #undef CURRENT_CTYPE static void _yutani_Sprite_gcsweep(KrkInstance * _self) { struct _yutani_Sprite * self = (void*)_self; if (self->sprite) sprite_free(self->sprite); if (self->ctx) release_graphics_yutani(self->ctx); } #define IS_Sprite(o) (krk_isInstanceOf(o,Sprite)) #define AS_Sprite(o) ((struct _yutani_Sprite*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_Sprite* /** * Sprite(file=None,*,width=0,height=0) * * Either file or width/height need to be specified, but not both. */ KRK_Method(Sprite,__init__) { const char * filename = NULL; unsigned int width = 0; unsigned int height = 0; if (!krk_parseArgs( ".|z$II:Sprite",(const char*[]){"file","width","height"}, &filename, &width, &height )) { return NONE_VAL(); } if ((!filename && (!width || !height)) || (filename && (width || height))) { return krk_runtimeError(vm.exceptions->argumentError, "Either 'file' or both of 'width' and 'height' must be provided, but not both."); } NO_REINIT(Sprite); sprite_t * sprite; /* Set up sprite */ if (!filename) { /* Want to build a new sprite */ sprite = create_sprite(width, height, ALPHA_EMBEDDED); } else { sprite = malloc(sizeof(sprite_t)); if (load_sprite(sprite, filename)) { free(sprite); return krk_runtimeError(vm.exceptions->ioError, "could not load sprite from '%s'", filename); } } /* Initialize representative graphics context */ gfx_context_t * ctx = init_graphics_sprite(sprite); self->ctx = ctx; self->sprite = sprite; /* Keep the file if we had one */ krk_attachNamedValue(&self->inst.fields, "file", filename ? OBJECT_VAL(krk_copyString(filename,strlen(filename))) : NONE_VAL()); return NONE_VAL(); } KRK_Method(Sprite,__repr__) { KrkValue file = NONE_VAL(); krk_tableGet_fast(&self->inst.fields, S("file"), &file); INIT_CHECK(Sprite); if (!IS_NONE(file)) { return krk_stringFromFormat("Sprite(file=%R,width=%u,height=%u)", file, self->sprite->width, self->sprite->height); } else { return krk_stringFromFormat("Sprite(width=%u,height=%u)", self->sprite->width, self->sprite->height); } } KRK_Method(Sprite,free) { INIT_CHECK(Sprite); if (self->sprite) sprite_free(self->sprite); if (self->ctx) release_graphics_yutani(self->ctx); self->sprite = NULL; self->ctx = NULL; return NONE_VAL(); } #undef CURRENT_CTYPE static void _yutani_Window_gcscan(KrkInstance * _self) { struct _yutani_Window * self = (void*)_self; krk_markValue(self->title); krk_markValue(self->icon); } #define IS_Window(o) (krk_isInstanceOf(o,Window)) #define AS_Window(o) ((struct _yutani_Window*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_Window* static void update_window_title(struct _yutani_Window * self) { if (IS_STRING(self->title)) { if (IS_STRING(self->icon)) { yutani_window_advertise_icon(yctxInstance->yctx, self->window, AS_CSTRING(self->title), AS_CSTRING(self->icon)); } else { yutani_window_advertise(yctxInstance->yctx, self->window, AS_CSTRING(self->title)); } } /* TODO Update decorations */ } KRK_Method(Window,__init__) { unsigned int width; unsigned int height; unsigned int flags = 0; KrkValue title = NONE_VAL(); KrkValue icon = NONE_VAL(); int doublebuffer = 1; if (!yctxInstance) return krk_runtimeError(vm.exceptions->valueError, "Compositor is not initialized"); if (!krk_parseArgs( ".II|IV!V!p:Window",(const char *[]){"width","height","flags","title","icon","doublebuffer"}, &width, &height, &flags, KRK_BASE_CLASS(str), &title, KRK_BASE_CLASS(str), &icon, &doublebuffer )) { return NONE_VAL(); } NO_REINIT(Window); self->window = yutani_window_create_flags(yctxInstance->yctx, width, height, flags); self->doubleBuffered = doublebuffer; self->ctx = doublebuffer ? init_graphics_yutani_double_buffer(self->window) : init_graphics_yutani(self->window); self->title = title; self->icon = icon; self->closed = 0; update_window_title(self); return NONE_VAL(); } KRK_Method(Window,title) { INIT_CHECK(Window); if (argc > 1) { if (!IS_STRING(argv[1]) && !IS_NONE(argv[1])) return TYPE_ERROR(str,argv[1]); self->title = argv[1]; update_window_title(self); } return self->title; } KRK_Method(Window,icon) { INIT_CHECK(Window); if (argc > 1) { if (!IS_STRING(argv[1]) && !IS_NONE(argv[1])) return TYPE_ERROR(str,argv[1]); self->icon = argv[1]; update_window_title(self); } return self->icon; } KRK_Method(Window,wid) { INIT_CHECK(Window); return INTEGER_VAL(self->window->wid); } KRK_Method(Window,x) { INIT_CHECK(Window); return INTEGER_VAL(self->window->x); } KRK_Method(Window,y) { INIT_CHECK(Window); return INTEGER_VAL(self->window->y); } KRK_Method(Window,focused) { INIT_CHECK(Window); if (argc > 1) { if (!IS_BOOLEAN(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "focused must be bool, not %T", argv[1]); self->window->focused = AS_BOOLEAN(argv[1]); } return BOOLEAN_VAL(self->window->focused); } KRK_Method(Window,closed) { return INTEGER_VAL(self->closed); } KRK_Method(Window,__repr__) { INIT_CHECK(Window); if (!self->window) { return krk_stringFromFormat("Window(title=%R,closed=True)", self->title); } return krk_stringFromFormat("Window(wid=%d,title=%R,width=%d,height=%d)", self->window->wid, self->title, self->window->width, self->window->height); } KRK_Method(Window,flip) { INIT_CHECK(Window); if (self->doubleBuffered) { flip(self->ctx); } yutani_flip(yctxInstance->yctx, self->window); return NONE_VAL(); } KRK_Method(Window,move) { int x, y; if (!krk_parseArgs(".ii", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); INIT_CHECK(Window); yutani_window_move(yctxInstance->yctx, self->window, x, y); return NONE_VAL(); } KRK_Method(Window,close) { INIT_CHECK(Window); yutani_close(yctxInstance->yctx, self->window); self->window = NULL; release_graphics_yutani(self->ctx); self->ctx = NULL; self->closed = 1; return NONE_VAL(); } KRK_Method(Window,set_stack) { unsigned int z; if (!krk_parseArgs(".I", (const char*[]){"z"}, &z)) return NONE_VAL(); INIT_CHECK(Window); yutani_set_stack(yctxInstance->yctx, self->window, z); return NONE_VAL(); } KRK_Method(Window,special_request) { unsigned int request; if (!krk_parseArgs(".I", (const char*[]){"request"}, &request)) return NONE_VAL(); INIT_CHECK(Window); yutani_special_request(yctxInstance->yctx, self->window, request); return NONE_VAL(); } KRK_Method(Window,resize) { unsigned int width, height; if (!krk_parseArgs(".II", (const char*[]){"width","height"}, &width, &height)) return NONE_VAL(); INIT_CHECK(Window); yutani_window_resize(yctxInstance->yctx, self->window, width, height); return NONE_VAL(); } KRK_Method(Window,resize_start) { unsigned int direction; if (!krk_parseArgs(".I", (const char*[]){"direction"}, &direction)) return NONE_VAL(); INIT_CHECK(Window); yutani_window_resize_start(yctxInstance->yctx, self->window, direction); return NONE_VAL(); } KRK_Method(Window,resize_done) { INIT_CHECK(Window); yutani_window_resize_done(yctxInstance->yctx, self->window); return NONE_VAL(); } KRK_Method(Window,resize_offer) { unsigned int width, height; if (!krk_parseArgs(".II", (const char*[]){"width","height"}, &width, &height)) return NONE_VAL(); INIT_CHECK(Window); yutani_window_resize_offer(yctxInstance->yctx, self->window, width, height); return NONE_VAL(); } KRK_Method(Window,resize_accept) { unsigned int width, height; if (!krk_parseArgs(".II", (const char*[]){"width","height"}, &width, &height)) return NONE_VAL(); INIT_CHECK(Window); yutani_window_resize_accept(yctxInstance->yctx, self->window, width, height); return NONE_VAL(); } KRK_Method(Window,update_shape) { unsigned int threshold; if (!krk_parseArgs(".I", (const char*[]){"threshold"}, &threshold)) return NONE_VAL(); INIT_CHECK(Window); yutani_window_update_shape(yctxInstance->yctx, self->window, threshold); return NONE_VAL(); } KRK_Method(Window,show_mouse) { unsigned int mouse; if (!krk_parseArgs(".I", (const char*[]){"mouse"}, &mouse)) return NONE_VAL(); INIT_CHECK(Window); yutani_window_show_mouse(yctxInstance->yctx, self->window, mouse); return NONE_VAL(); } KRK_Method(Window,warp_mouse) { int x, y; if (!krk_parseArgs(".ii", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); INIT_CHECK(Window); yutani_window_warp_mouse(yctxInstance->yctx, self->window, x, y); return NONE_VAL(); } KRK_Method(Window,reinit) { INIT_CHECK(Window); reinit_graphics_yutani(self->ctx, self->window); return NONE_VAL(); } #undef CURRENT_CTYPE static void _yutani_Subregion_gcsweep(KrkInstance * _self) { struct _yutani_Subregion * self = (void*)_self; if (self->ctx) { if (self->ctx->clips) { free(self->ctx->clips); } free(self->ctx); self->ctx = NULL; } } #define IS_Subregion(o) (krk_isInstanceOf(o,Subregion)) #define AS_Subregion(o) ((struct _yutani_Subregion*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_Subregion* KRK_Method(Subregion,__init__) { struct _yutani_GraphicsContext * ctx; int x, y, w, h; if (!krk_parseArgs( ".O!iiii:Subregion", (const char*[]){"ctx","x","y","w","h"}, GraphicsContext, &ctx, &x, &y, &w, &h)) { return NONE_VAL(); } NO_REINIT(Subregion); if (!ctx->ctx) return krk_runtimeError(vm.exceptions->typeError, "ctx is not initialized"); if (w < 0 || h < 0) return krk_runtimeError(vm.exceptions->typeError, "invalid subregion"); if (x < 0) { w += x; x = 0; } if (y < 0) { h += y; y = 0; } if (x >= ctx->ctx->width || y >= ctx->ctx->height) { x = 0; y = 0; w = 0; h = 0; } if (x + w > ctx->ctx->width) { w = ctx->ctx->width - x; } if (y + h > ctx->ctx->height) { h = ctx->ctx->height - y; } gfx_context_t * sub = init_graphics_subregion(ctx->ctx, x, y, w, h); self->ctx = sub; self->doubleBuffered = ctx->doubleBuffered; self->x = x; self->y = y; krk_attachNamedObject(&self->inst.fields, "parent", (KrkObj*)ctx); return NONE_VAL(); } KRK_Method(Subregion,offset_x) { ATTRIBUTE_NOT_ASSIGNABLE(); return INTEGER_VAL(self->x); } KRK_Method(Subregion,offset_y) { ATTRIBUTE_NOT_ASSIGNABLE(); return INTEGER_VAL(self->y); } #undef CURRENT_CTYPE static KrkClass * TransformMatrix; struct _yutani_TransformMatrix { KrkInstance inst; gfx_matrix_t matrix; }; #define IS_TransformMatrix(o) (krk_isInstanceOf(o,TransformMatrix)) #define AS_TransformMatrix(o) ((struct _yutani_TransformMatrix*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_TransformMatrix* KRK_Method(TransformMatrix,__init__) { double a = 1.0, b = 0, tx = 0, c = 0, d = 1.0, ty = 0; if (!krk_parseArgs(".|dddddd:TransformMatrix", (const char*[]){"a","b","tx","c","d","ty"}, &a,&b,&tx,&c,&d,&ty)) return NONE_VAL(); self->matrix[0][0] = a; self->matrix[0][1] = b; self->matrix[0][2] = tx; self->matrix[1][0] = c; self->matrix[1][1] = d; self->matrix[1][2] = ty; return NONE_VAL(); } #define MATRIX_VAR(name,row,col) KRK_Method(TransformMatrix,name) { \ double x = self->matrix[row][col]; \ if (!krk_parseArgs(".|d",(const char*[]){"val"},&x)) return NONE_VAL(); \ self->matrix[row][col] = x; \ return FLOATING_VAL(x); \ } MATRIX_VAR(a,0,0) MATRIX_VAR(b,0,1) MATRIX_VAR(tx,0,1) MATRIX_VAR(c,1,0) MATRIX_VAR(d,1,1) MATRIX_VAR(ty,1,1) KRK_Method(TransformMatrix,__repr__) { struct StringBuilder sb = {}; KrkValue floats[6]; for (int i = 0; i < 6; ++i) { floats[i] = FLOATING_VAL(self->matrix[i/3][i%3]); } krk_pushStringBuilderFormat(&sb, "TransformMatrix[ [%R,%R,%R] [%R,%R,%R] ]", floats[0], floats[1], floats[2], floats[3], floats[4], floats[5]); return krk_finishStringBuilder(&sb); } KRK_Method(TransformMatrix,scale) { double x, y; if (!krk_parseArgs(".dd", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); gfx_matrix_scale(self->matrix,x,y); return NONE_VAL(); } KRK_Method(TransformMatrix,translate) { double x, y; if (!krk_parseArgs(".dd", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); gfx_matrix_translate(self->matrix,x,y); return NONE_VAL(); } KRK_Method(TransformMatrix,rotate) { double r; if (!krk_parseArgs(".d", (const char*[]){"r"}, &r)) return NONE_VAL(); gfx_matrix_rotate(self->matrix,r); return NONE_VAL(); } KRK_Method(TransformMatrix,shear) { double x, y; if (!krk_parseArgs(".dd", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); gfx_matrix_shear(self->matrix,x,y); return NONE_VAL(); } KRK_Method(TransformMatrix,apply) { double x, y; if (!krk_parseArgs(".dd", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); KrkTuple * out = krk_newTuple(2); krk_push(OBJECT_VAL(out)); double o_x, o_y; gfx_apply_matrix(x,y,self->matrix,&o_x,&o_y); out->values.values[out->values.count++] = FLOATING_VAL(o_x); out->values.values[out->values.count++] = FLOATING_VAL(o_y); return krk_pop(); } #undef CURRENT_CTYPE WRAP_TYPE(Font,struct TT_Font,fontData, int fontSize; uint32_t fontColor; ); #define IS_Font(o) (krk_isInstanceOf(o,Font)) #define AS_Font(o) ((struct _yutani_Font*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_Font* WRAP_TYPE(TTShape,struct TT_Shape, shape); #define IS_TTShape(o) (krk_isInstanceOf(o,TTShape)) #define AS_TTShape(o) ((struct _yutani_TTShape*)AS_OBJECT(o)) WRAP_TYPE(TTContour,struct TT_Contour, contour); #define IS_TTContour(o) (krk_isInstanceOf(o,TTContour)) #define AS_TTContour(o) ((struct _yutani_TTContour*)AS_OBJECT(o)) #define CHECK_FONT() do { if (!self->fontData) return krk_runtimeError(vm.exceptions->valueError, "font is uninitialized"); } while (0) static void _yutani_Font_gcsweep(KrkInstance * _self) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self; if (self->fontData) free(self->fontData); } KRK_Method(Font,__init__) { const char * filename; int size; uint32_t color = rgb(0,0,0); if (!krk_parseArgs( ".si|I:Font",(const char*[]){"font","size","color"}, &filename, &size, &color)) { return NONE_VAL(); } NO_REINIT(Font); struct TT_Font * fd; if (strstr(filename, "sans-serif") == filename || strstr(filename, "monospace") == filename) { fd = tt_font_from_shm(filename); } else { fd = tt_font_from_file(filename); } if (!fd) { return krk_runtimeError(vm.exceptions->ioError, "failed to load '%s'", filename); } tt_set_size(fd, size); self->fontData = fd; self->fontSize = size; self->fontColor = color; krk_attachNamedValue(&self->inst.fields, "file", OBJECT_VAL(krk_copyString(filename, strlen(filename)))); return NONE_VAL(); } KRK_Method(Font,size) { INIT_CHECK(Font); if (argc > 1) { if (!IS_INTEGER(argv[1])) return krk_runtimeError(vm.exceptions->typeError, "size must be int, not %T", argv[1]); self->fontSize = AS_INTEGER(argv[1]); tt_set_size(self->fontData, self->fontSize); } return self->fontSize; } KRK_Method(Font,draw_string) { struct _yutani_GraphicsContext * ctx; const char * s; int x; int y; if (!krk_parseArgs( ".O!sii", (const char*[]){"ctx","s","x","y"}, GraphicsContext, &ctx, &s, &x, &y)) { return NONE_VAL(); } INIT_CHECK(Font); return INTEGER_VAL(tt_draw_string(ctx->ctx, self->fontData, x, y, s, self->fontColor)); } KRK_Method(Font,draw_string_shadow) { struct _yutani_GraphicsContext * ctx; const char * s; int x; int y; uint32_t shadow; int blur; if (!krk_parseArgs( ".O!siiIi", (const char*[]){"ctx","s","x","y","shadow","blur"}, GraphicsContext, &ctx, &s, &x, &y, &shadow, &blur)) { return NONE_VAL(); } INIT_CHECK(Font); /* This has a weird API for reasons I can't remember */ tt_draw_string_shadow(ctx->ctx, self->fontData, (char*)s, self->fontSize, x, y, self->fontColor, shadow, blur); return NONE_VAL(); } KRK_Method(Font,width) { const char * s; if (!krk_parseArgs(".s", (const char*[]){"s"}, &s)) return NONE_VAL(); INIT_CHECK(Font); return INTEGER_VAL(tt_string_width(self->fontData, s)); } KRK_Method(Font,measure) { INIT_CHECK(Font); KrkTuple * out = krk_newTuple(3); krk_push(OBJECT_VAL(out)); struct TT_FontMetrics metrics; tt_measure_font(self->fontData, &metrics); out->values.values[out->values.count++] = FLOATING_VAL(metrics.ascender); out->values.values[out->values.count++] = FLOATING_VAL(metrics.descender); out->values.values[out->values.count++] = FLOATING_VAL(metrics.lineGap); return krk_pop(); } KRK_Method(Font,draw_glyph_into) { INIT_CHECK(Font); struct _yutani_TTContour * contour; float x, y; unsigned int glyph; if (!krk_parseArgs(".O!ffI", (const char*[]){"contour","x","y","glyph"}, TTContour, &contour, &x, &y, &glyph)) return NONE_VAL(); if (!contour->contour) return krk_runtimeError(vm.exceptions->typeError, "contour is not initialized"); /* tt_draw_glyph_into returns potentially-realloc'd contour, but we'll return nothing and * just mutate the passed contour object. */ contour->contour = tt_draw_glyph_into(contour->contour, self->fontData, x, y, glyph); return NONE_VAL(); } KRK_Method(Font,prepare_string) { INIT_CHECK(Font); struct _yutani_TTContour * contour = NULL; float x, y; const char * s; if (!krk_parseArgs(".ffs|O!", (const char*[]){"x","y","s","into"}, &x, &y, &s, TTContour, &contour)) return NONE_VAL(); float out_width = 0; KrkTuple * out_tuple = krk_newTuple(2); /* contour, width */ krk_push(OBJECT_VAL(out_tuple)); /* if @c into is unset, make a new one to store result; otherwise, @c into is updated */ if (!contour) { contour = (struct _yutani_TTContour*)krk_newInstance(TTContour); } contour->contour = tt_prepare_string_into(contour->contour, self->fontData, x, y, s, &out_width); out_tuple->values.values[out_tuple->values.count++] = OBJECT_VAL(contour); out_tuple->values.values[out_tuple->values.count++] = FLOATING_VAL(out_width); return krk_pop(); } KRK_Method(Font,ellipsify) { const char * s; int max_width; if (!krk_parseArgs(".si", (const char*[]){"s","w"}, &s, &max_width)) return NONE_VAL(); int out_width = 0; char * out = tt_ellipsify(s, self->fontSize, self->fontData, max_width, &out_width); KrkTuple * out_tuple = krk_newTuple(2); krk_push(OBJECT_VAL(out_tuple)); out_tuple->values.values[out_tuple->values.count++] = OBJECT_VAL(krk_copyString(out, strlen(out))); out_tuple->values.values[out_tuple->values.count++] = INTEGER_VAL(out_width); free(out); return krk_pop(); } #undef CURRENT_CTYPE WRAP_TYPE(MenuBar,struct menu_bar,menuBar); WRAP_TYPE(MenuList,struct MenuList, menuList); WRAP_TYPE(MenuEntry,struct MenuEntry, menuEntry); WRAP_TYPE(MenuEntrySubmenu,struct MenuEntry, menuEntry); WRAP_TYPE(MenuEntrySeparator,struct MenuEntry, menuEntry); WRAP_TYPE(MenuEntryToggle,struct MenuEntry, menuEntry); WRAP_TYPE(MenuEntryCustom,struct MenuEntry, menuEntry); #define IS_MenuBar(o) (krk_isInstanceOf(o,MenuBar)) #define AS_MenuBar(o) ((struct _yutani_MenuBar*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_MenuBar* static void _yutani_MenuBar_gcsweep(KrkInstance * _self) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self; if (self->menuBar->entries) { for (size_t i = 0; self->menuBar->entries[i].title; ++i) { free(self->menuBar->entries[i].title); free(self->menuBar->entries[i].action); } free(self->menuBar->entries); } free(self->menuBar); } static void _menubar_callback(struct menu_bar * _self) { CURRENT_CTYPE self = _self->_private; KrkValue callback; if (krk_tableGet(&self->inst.fields, OBJECT_VAL(S("callback")), &callback)) { krk_push(callback); krk_push(OBJECT_VAL(self)); krk_callStack(1); } } KRK_Method(MenuBar,__init__) { KrkTuple * entries; if (!krk_parseArgs( ".O!:MenuBar", (const char*[]){"entries"}, KRK_BASE_CLASS(tuple), &entries)) { return NONE_VAL(); } NO_REINIT(MenuBar); size_t count = entries->values.count; struct menu_bar * out = calloc(sizeof(struct menu_bar), 1); out->entries = calloc(sizeof(struct menu_bar_entries), count + 1); for (size_t i = 0; i < count; ++i) { KrkValue entry = entries->values.values[i]; if (!IS_TUPLE(entry) || AS_TUPLE(entry)->values.count != 2 || !IS_STRING(AS_TUPLE(entry)->values.values[0]) || !IS_STRING(AS_TUPLE(entry)->values.values[1])) { krk_runtimeError(vm.exceptions->typeError, "entries member should be tuple[str,str], not %T", entry); goto _error; } out->entries[i].title = strdup(AS_CSTRING(AS_TUPLE(entry)->values.values[0])); out->entries[i].action = strdup(AS_CSTRING(AS_TUPLE(entry)->values.values[1])); } out->entries[count].title = NULL; out->entries[count].action = NULL; out->set = menu_set_create(); self->menuBar = out; out->_private = self; out->redraw_callback = _menubar_callback; krk_attachNamedValue(&self->inst.fields, "entries", OBJECT_VAL(entries)); krk_attachNamedValue(&self->inst.fields, "set", krk_dict_of(0,NULL,0)); return NONE_VAL(); _error: for (size_t i = 0; i < count; ++i) { if (out->entries[i].title) free(out->entries[i].title); if (out->entries[i].action) free(out->entries[i].action); } free(out->entries); free(out); return NONE_VAL(); } KRK_Method(MenuBar,place) { int x, y; unsigned int width; struct _yutani_Window * window; if (!krk_parseArgs( ".iiIO!", (const char*[]){"x","y","width","window"}, &x, &y, &width, Window, &window)) { return NONE_VAL(); } INIT_CHECK(MenuBar); self->menuBar->x = x; self->menuBar->y = y; self->menuBar->width = width; self->menuBar->window = window->window; return NONE_VAL(); } KRK_Method(MenuBar,render) { struct _yutani_GraphicsContext * ctx; if (!krk_parseArgs(".O!",(const char*[]){"ctx"}, GraphicsContext, &ctx)) return NONE_VAL(); INIT_CHECK(MenuBar); menu_bar_render(self->menuBar, ctx->ctx); return NONE_VAL(); } KRK_Method(MenuBar,mouse_event) { struct _yutani_Window * window; KrkValue message; if (!krk_parseArgs( ".O!V!", (const char*[]){"window", "message"}, Window, &window, Message_WindowMouseEvent, &message )) { return NONE_VAL(); } INIT_CHECK(MenuBar); return INTEGER_VAL(menu_bar_mouse_event(yctxInstance->yctx, window->window, self->menuBar, AS_Message_WindowMouseEvent(message), AS_Message_WindowMouseEvent(message)->new_x , AS_Message_WindowMouseEvent(message)->new_y)); } KRK_Method(MenuBar,insert) { KrkValue name; struct _yutani_MenuList * menu; if (!krk_parseArgs( ".V!O!", (const char*[]){"name","menu"}, KRK_BASE_CLASS(str), &name, MenuList, &menu )) { return NONE_VAL(); } INIT_CHECK(MenuBar); menu_set_insert(self->menuBar->set, AS_CSTRING(name), menu->menuList); KrkValue dict = NONE_VAL(); if (!krk_tableGet(&self->inst.fields, OBJECT_VAL(S("set")), &dict) || !krk_isInstanceOf(dict,KRK_BASE_CLASS(dict))) { return krk_runtimeError(vm.exceptions->typeError, "corrupt MenuBar"); } krk_tableSet(AS_DICT(dict), name, OBJECT_VAL(menu)); return NONE_VAL(); } KRK_Method(MenuBar,height) { return INTEGER_VAL(MENU_BAR_HEIGHT); } #undef CURRENT_CTYPE #define IS_MenuList(o) (krk_isInstanceOf(o,MenuList)) #define AS_MenuList(o) ((struct _yutani_MenuList*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_MenuList* KRK_Method(MenuList,__init__) { if (!krk_parseArgs(".:MenuList",(const char*[]){}, NULL)) return NONE_VAL(); NO_REINIT(MenuList); struct MenuList * out = menu_create(); self->menuList = out; KrkValue list = krk_list_of(0,NULL,0); krk_attachNamedValue(&self->inst.fields, "entries", list); return NONE_VAL(); } KRK_Method(MenuList,insert) { struct _yutani_MenuEntry * entry; if (!krk_parseArgs(".O!",(const char*[]){"entry"}, MenuEntry, &entry)) return NONE_VAL(); INIT_CHECK(MenuList); menu_insert(self->menuList, entry->menuEntry); KrkValue list = NONE_VAL(); if (!krk_tableGet(&self->inst.fields, OBJECT_VAL(S("entries")), &list) || !IS_list(list)) { return krk_runtimeError(vm.exceptions->typeError, "corrupt MenuList"); } krk_writeValueArray(AS_LIST(list), OBJECT_VAL(entry)); return NONE_VAL(); } #undef CURRENT_CTYPE #define IS_MenuEntry(o) (krk_isInstanceOf(o,MenuEntry)) #define AS_MenuEntry(o) ((struct _yutani_MenuEntry*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_MenuEntry* static void _MenuEntry_callback_internal(struct MenuEntry * _self) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private; KrkValue callback = NONE_VAL(); if (krk_tableGet(&self->inst.fields, OBJECT_VAL(S("callback")), &callback)) { krk_push(callback); krk_push(OBJECT_VAL(self)); krk_callStack(1); } } KRK_Method(MenuEntry,__init__) { const char * title; KrkValue callback; const char * icon = NULL; const char * action = NULL; if (!krk_parseArgs( ".sV|zz:MenuEntry", (const char*[]){"title","callback","icon","action"}, &title, &callback, &icon, &action )) { return NONE_VAL(); } NO_REINIT(MenuEntry); struct MenuEntry * out = menu_create_normal(icon, action, title, _MenuEntry_callback_internal); self->menuEntry = out; out->_private = self; krk_attachNamedValue(&self->inst.fields, "callback", callback); return NONE_VAL(); } #define MENU_ENTRY_INT_PROP(name) \ KRK_Method(MenuEntry,name) { \ int set = 0, to = 0; \ if (!krk_parseArgs(".|i?",(const char*[]){"value"},&set,&to)) return NONE_VAL(); \ if (set) self->menuEntry-> name = to; \ return INTEGER_VAL(self->menuEntry-> name); \ } MENU_ENTRY_INT_PROP(height) MENU_ENTRY_INT_PROP(width) MENU_ENTRY_INT_PROP(rwidth) MENU_ENTRY_INT_PROP(hilight) MENU_ENTRY_INT_PROP(offset) KRK_Method(MenuEntry,update_icon) { char * icon; if (!krk_parseArgs(".z", (const char*[]){"icon"}, &icon)) return NONE_VAL(); INIT_CHECK(MenuEntry); menu_update_icon(self->menuEntry, icon); return NONE_VAL(); } #undef CURRENT_CTYPE #define IS_MenuEntrySubmenu(o) (krk_isInstanceOf(o,MenuEntrySubmenu)) #define AS_MenuEntrySubmenu(o) ((struct _yutani_MenuEntrySubmenu*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_MenuEntrySubmenu* KRK_Method(MenuEntrySubmenu,__init__) { const char * title; const char * action; const char * icon = NULL; if (!krk_parseArgs( ".ss|z:MenuEntrySubmenu", (const char*[]){"title","action","icon"}, &title, &action, &icon )) { return NONE_VAL(); } NO_REINIT(MenuEntrySubmenu); struct MenuEntry * out = menu_create_submenu(icon, action, title); self->menuEntry = out; out->_private = self; return NONE_VAL(); } #undef CURRENT_CTYPE #define IS_MenuEntryToggle(o) (krk_isInstanceOf(o,MenuEntryToggle)) #define AS_MenuEntryToggle(o) ((struct _yutani_MenuEntryToggle*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_MenuEntryToggle* KRK_Method(MenuEntryToggle,__init__) { const char * title; KrkValue callback; int state = 0; const char * action = NULL; if (!krk_parseArgs( ".sV|ps:MenuEntryToggle", (const char*[]){"title","callback","state","action"}, &title, &callback, &state, &action )) { return NONE_VAL(); } NO_REINIT(MenuEntryToggle); struct MenuEntry * out = menu_create_toggle(action, title, state, _MenuEntry_callback_internal); self->menuEntry = out; out->_private = self; krk_attachNamedValue(&self->inst.fields, "callback", callback); return NONE_VAL(); } KRK_Method(MenuEntryToggle,state) { int had_state = 0; int state = 0; if (!krk_parseArgs(".|p?", (const char*[]){"state"}, &had_state, &state)) return NONE_VAL(); INIT_CHECK(MenuEntryToggle); if (had_state) { menu_update_toggle_state(self->menuEntry, state); } return BOOLEAN_VAL(((struct MenuEntry_Toggle*)self)->set); } #undef CURRENT_CTYPE #define IS_MenuEntrySeparator(o) (krk_isInstanceOf(o,MenuEntrySeparator)) #define AS_MenuEntrySeparator(o) ((struct _yutani_MenuEntrySeparator*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_MenuEntrySeparator* KRK_Method(MenuEntrySeparator,__init__) { if (!krk_parseArgs(".:MenuEntrySeparator", (const char*[]){}, NULL)) return NONE_VAL(); NO_REINIT(MenuEntrySeparator); struct MenuEntry * out = menu_create_separator(); self->menuEntry = out; out->_private = self; return NONE_VAL(); } #undef CURRENT_CTYPE #define IS_MenuEntryCustom(o) (krk_isInstanceOf(o,MenuEntryCustom)) #define AS_MenuEntryCustom(o) ((struct _yutani_MenuEntryCustom*)AS_OBJECT(o)) #define CURRENT_CTYPE struct _yutani_MenuEntryCustom* static void _custom_menu_render(gfx_context_t * ctx, struct MenuEntry * _self, int offset) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private; KrkClass * myClass = self->inst._class; KrkValue method; if (!krk_tableGet_fast(&myClass->methods, S("render"), &method)) return; krk_push(method); krk_push(OBJECT_VAL(self)); struct _yutani_GraphicsContext * gctx = (struct _yutani_GraphicsContext*)krk_newInstance(GraphicsContext); gctx->ctx = ctx; krk_push(OBJECT_VAL(gctx)); krk_push(INTEGER_VAL(offset)); krk_callStack(3); } static void _custom_menu_focus_change(struct MenuEntry * _self, int focused) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private; KrkClass * myClass = self->inst._class; KrkValue method; if (!krk_tableGet_fast(&myClass->methods, S("focus_change"), &method)) return; krk_push(method); krk_push(OBJECT_VAL(self)); krk_push(BOOLEAN_VAL(focused)); krk_callStack(2); } static void _custom_menu_activate(struct MenuEntry * _self, int focused) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private; KrkClass * myClass = self->inst._class; KrkValue method; if (!krk_tableGet_fast(&myClass->methods, S("activate"), &method)) return; krk_push(method); krk_push(OBJECT_VAL(self)); krk_push(BOOLEAN_VAL(focused)); krk_callStack(2); } static int _custom_menu_mouse_event(struct MenuEntry * _self, struct yutani_msg_window_mouse_event * event) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self->_private; KrkClass * myClass = self->inst._class; KrkValue method; if (!krk_tableGet_fast(&myClass->methods, S("mouse_event"), &method)) return 0; krk_push(method); krk_push(OBJECT_VAL(self)); size_t size = sizeof(yutani_msg_t) + sizeof(struct yutani_msg_window_mouse_event); yutani_msg_t * tmp = malloc(size); tmp->type = YUTANI_MSG_WINDOW_MOUSE_EVENT; tmp->size = size; memcpy(tmp->data, event, sizeof(struct yutani_msg_window_mouse_event)); krk_push(makeMessage(tmp)); KrkValue result = krk_callStack(2); if (IS_INTEGER(result)) return AS_INTEGER(result); return 0; } static struct MenuEntryVTable _custom_menu_vtable = { .methods = 4, .renderer = _custom_menu_render, .focus_change = _custom_menu_focus_change, .activate = _custom_menu_activate, .mouse_event = _custom_menu_mouse_event, }; KRK_Method(MenuEntryCustom,__init__) { if (!krk_parseArgs(".:MenuEntryCustom", (const char*[]){}, NULL)) return NONE_VAL(); NO_REINIT(MenuEntryCustom); struct MenuEntry * out = menu_create_separator(); /* Steal some defaults */ out->_type = -1; /* Special */ out->vtable = &_custom_menu_vtable; self->menuEntry = out; out->_private = self; return NONE_VAL(); } #undef CURRENT_CTYPE #define CURRENT_CTYPE struct _yutani_TTContour* void _TTContour_ongcsweep(KrkInstance * _self) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self; if (self->contour) { free(self->contour); } self->contour = NULL; } KRK_Method(TTContour,__init__) { float x, y; if (!krk_parseArgs(".ff:TTContour", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); NO_REINIT(TTContour); self->contour = tt_contour_start(x,y); return NONE_VAL(); } KRK_Method(TTContour,line_to) { float x, y; if (!krk_parseArgs(".ff", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); INIT_CHECK(TTContour); self->contour = tt_contour_line_to(self->contour, x, y); return NONE_VAL(); } KRK_Method(TTContour,move_to) { float x, y; if (!krk_parseArgs(".ff", (const char*[]){"x","y"}, &x, &y)) return NONE_VAL(); INIT_CHECK(TTContour); self->contour = tt_contour_move_to(self->contour, x, y); return NONE_VAL(); } KRK_Method(TTContour,finish) { INIT_CHECK(TTContour); struct _yutani_TTShape * newShape = (struct _yutani_TTShape*)krk_newInstance(TTShape); newShape->shape = tt_contour_finish(self->contour); return OBJECT_VAL(newShape); } KRK_Method(TTContour,stroke) { float width; if (!krk_parseArgs(".f", (const char*[]){"width"}, &width)) return NONE_VAL(); INIT_CHECK(TTContour); struct _yutani_TTShape * newShape = (struct _yutani_TTShape*)krk_newInstance(TTShape); newShape->shape = tt_contour_stroke_shape(self->contour, width); return OBJECT_VAL(newShape); } KRK_Method(TTContour,stroke_path) { float width; if (!krk_parseArgs(".f", (const char*[]){"width"}, &width)) return NONE_VAL(); INIT_CHECK(TTContour); struct _yutani_TTContour * newContour = (struct _yutani_TTContour*)krk_newInstance(TTContour); newContour->contour = tt_contour_stroke_contour(self->contour, width); return OBJECT_VAL(newContour); } KRK_Method(TTContour,free) { INIT_CHECK(TTContour); free(self->contour); self->contour = NULL; return NONE_VAL(); } KRK_Method(TTContour,transform) { struct _yutani_TransformMatrix * matrix; if (!krk_parseArgs(".O!", (const char*[]){"matrix"}, TransformMatrix, &matrix)) return NONE_VAL(); INIT_CHECK(TTContour); tt_contour_transform(self->contour, matrix->matrix); return NONE_VAL(); } #undef CURRENT_CTYPE #define CURRENT_CTYPE struct _yutani_TTShape* void _TTShape_ongcsweep(KrkInstance * _self) { CURRENT_CTYPE self = (CURRENT_CTYPE)_self; if (self->shape) { fprintf(stderr, "free shape\n"); free(self->shape); } self->shape = NULL; } KRK_Method(TTShape,__init__) { return krk_runtimeError(vm.exceptions->typeError, "Can not initialize empty shape; use TTContour.finish instead"); } KRK_Method(TTShape,paint) { struct _yutani_GraphicsContext * ctx; uint32_t color; if (!krk_parseArgs( ".O!I", (const char*[]){"ctx","color"}, GraphicsContext, &ctx, &color)) { return NONE_VAL(); } INIT_CHECK(TTShape); tt_path_paint(ctx->ctx, self->shape, color); return NONE_VAL(); } extern void tt_path_paint_sprite(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix); extern void tt_path_paint_sprite_options(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix, int, int); KRK_Method(TTShape,paint_sprite) { struct _yutani_GraphicsContext * ctx; struct _yutani_Sprite * sprite; struct _yutani_TransformMatrix * matrix; int filter = 0; int wrap = 0; if (!krk_parseArgs( ".O!O!O!|ii", (const char*[]){"ctx","sprite","matrix","filter","wrap"}, GraphicsContext, &ctx, Sprite, &sprite, TransformMatrix, &matrix, &filter, &wrap)) { return NONE_VAL(); } INIT_CHECK(TTShape); if (!sprite->sprite) return krk_runtimeError(vm.exceptions->valueError, "sprite go brrr"); if (filter == 0 && wrap == 0) { tt_path_paint_sprite(ctx->ctx, self->shape, sprite->sprite, matrix->matrix); } else { tt_path_paint_sprite_options(ctx->ctx, self->shape, sprite->sprite, matrix->matrix, filter, wrap); } return NONE_VAL(); } KRK_Method(TTShape,free) { INIT_CHECK(TTShape); free(self->shape); self->shape = NULL; return NONE_VAL(); } #undef CURRENT_CTYPE KRK_Function(decor_get_bounds) { struct _yutani_Window * window = NULL; if (!krk_parseArgs("|O!",(const char *[]){"window"}, Window, &window)) return NONE_VAL(); if (window && !window->window) return krk_runtimeError(vm.exceptions->valueError, "Window is closed"); struct decor_bounds bounds; decor_get_bounds(window ? window->window : NULL, &bounds); KrkValue result = krk_dict_of(0, NULL, 0); krk_push(result); #define SET(val) krk_attachNamedValue(AS_DICT(result), #val, INTEGER_VAL(bounds. val)); SET(top_height); SET(bottom_height); SET(left_width); SET(right_width); SET(width); SET(height); return krk_pop(); } KRK_Function(decor_render) { struct _yutani_Window * window; const char * title = NULL; if (!krk_parseArgs("O!|z",(const char *[]){"window","title"}, Window, &window, &title)) return NONE_VAL(); if (!window->window) return krk_runtimeError(vm.exceptions->valueError, "Window is closed"); if (!title) title = IS_NONE(window->title) ? "" : AS_CSTRING(window->title); render_decorations(window->window, window->ctx, (char*)title); return NONE_VAL(); } KRK_Function(decor_handle_event) { struct _yutani_Message * message = NULL; if (!krk_parseArgs("O!",(const char *[]){"message"}, Message, &message)) return NONE_VAL(); return INTEGER_VAL(decor_handle_event(yctxInstance->yctx, message->msg)); } KRK_Function(decor_show_default_menu) { struct _yutani_Window * window; int x, y; if (!krk_parseArgs("O!ii",(const char *[]){"window","x","y"}, Window, &window, &x, &y)) return NONE_VAL(); if (!window->window) return krk_runtimeError(vm.exceptions->valueError, "Window is closed"); decor_show_default_menu(window->window, x, y); return NONE_VAL(); } KRK_Function(rgb) { int r, g, b; KrkValue a = NONE_VAL(); if (!krk_parseArgs("bbb|V",(const char*[]){"r","g","b","a"}, &r, &g, &b, &a)) return NONE_VAL(); if (IS_NONE(a)) { return INTEGER_VAL(rgb(r,g,b)); } else { if (IS_FLOATING(a)) a = INTEGER_VAL(AS_FLOATING(a) * 255); if (!IS_INTEGER(a)) return TYPE_ERROR(int or float,a); return INTEGER_VAL(rgba(r,g,b,AS_INTEGER(a))); } } KRK_Function(draw_button) { struct _yutani_GraphicsContext * ctx; int x, y, width, height, hilight; const char * title; if (!krk_parseArgs("O!iiIIsi", (const char*[]){"ctx","x","y","width","height","title","hilight"}, GraphicsContext, &ctx, &x, &y, &width, &height, &title, &hilight)) { return NONE_VAL(); } struct TTKButton button = {x,y,width,height,(char*)title,hilight}; ttk_button_draw(ctx->ctx, &button); return NONE_VAL(); } KRK_Function(fswait) { KrkTuple * fds; int timeout = -1; if (!krk_parseArgs("O!|i",(const char*[]){"fds","timeout"}, KRK_BASE_CLASS(tuple), &fds, &timeout)) { return NONE_VAL(); } size_t count = fds->values.count; if (!count) { return krk_runtimeError(vm.exceptions->typeError, "can not wait on nothing?"); } /* Spot check first */ for (size_t i = 0; i < count; ++i) { KrkValue val = fds->values.values[i]; if (!IS_INTEGER(val)) return krk_runtimeError(vm.exceptions->typeError, "fds must be tuple of int, not %T", val); } int * _fds = malloc(sizeof(int) * count); int * _results = malloc(sizeof(int) * count); for (size_t i = 0; i < count; ++i) { KrkValue val = fds->values.values[i]; _fds[i] = AS_INTEGER(val); _results[i] = 0; } errno = 0; int status = fswait3(count, _fds, timeout, _results); free(_fds); if (status < 0) { int _errno = errno; free(_results); /* check if we were already raising a keyboard interrupt */ if (krk_currentThread.flags & (KRK_THREAD_HAS_EXCEPTION | KRK_THREAD_SIGNALLED)) return NONE_VAL(); return krk_runtimeError(vm.exceptions->OSError, "%s", strerror(_errno)); } KrkTuple * output = krk_newTuple(count); krk_push(OBJECT_VAL(output)); for (size_t i = 0; i < count; ++i) { output->values.values[output->values.count++] = INTEGER_VAL(_results[i]); } free(_results); return krk_pop(); } #undef CURRENT_CTYPE KRK_Module(_yutani2) { _module = module; /** * Base message type */ krk_makeClass(module, &Message, "Message", KRK_BASE_CLASS(object)); Message->allocSize = sizeof(struct _yutani_Message); Message->_ongcsweep = _Message_gcsweep; BIND_STATICMETHOD(Message,__new__); BIND_METHOD(Message,__repr__); BIND_PROP(Message,msg_magic); BIND_PROP(Message,msg_type); BIND_PROP(Message,msg_size); #define TYPE(type) krk_attachNamedValue(&Message->methods, "MSG_" #type, INTEGER_VAL(YUTANI_MSG_ ## type)) TYPE(HELLO); TYPE(WINDOW_NEW); TYPE(FLIP); TYPE(KEY_EVENT); TYPE(MOUSE_EVENT); TYPE(WINDOW_MOVE); TYPE(WINDOW_CLOSE); TYPE(WINDOW_SHOW); TYPE(WINDOW_HIDE); TYPE(WINDOW_STACK); TYPE(WINDOW_FOCUS_CHANGE); TYPE(WINDOW_MOUSE_EVENT); TYPE(FLIP_REGION); TYPE(WINDOW_NEW_FLAGS); TYPE(RESIZE_REQUEST); TYPE(RESIZE_OFFER); TYPE(RESIZE_ACCEPT); TYPE(RESIZE_BUFID); TYPE(RESIZE_DONE); TYPE(WINDOW_ADVERTISE); TYPE(SUBSCRIBE); TYPE(UNSUBSCRIBE); TYPE(NOTIFY); TYPE(QUERY_WINDOWS); TYPE(WINDOW_FOCUS); TYPE(WINDOW_DRAG_START); TYPE(WINDOW_WARP_MOUSE); TYPE(WINDOW_SHOW_MOUSE); TYPE(WINDOW_RESIZE_START); TYPE(SESSION_END); TYPE(KEY_BIND); TYPE(WINDOW_UPDATE_SHAPE); TYPE(CLIPBOARD); TYPE(GOODBYE); TYPE(SPECIAL_REQUEST); TYPE(WELCOME); TYPE(WINDOW_INIT); #undef TYPE krk_finalizeClass(Message); #define MAKE_MSG(type) \ krk_makeClass(module, &Message_ ## type, "Message_" # type, Message) MAKE_MSG(Welcome); BIND_PROP(Message_Welcome,display_width); BIND_PROP(Message_Welcome,display_height); krk_finalizeClass(Message_Welcome); MAKE_MSG(WindowMouseEvent); BIND_PROP(Message_WindowMouseEvent,wid); BIND_PROP(Message_WindowMouseEvent,new_x); BIND_PROP(Message_WindowMouseEvent,new_y); BIND_PROP(Message_WindowMouseEvent,old_x); BIND_PROP(Message_WindowMouseEvent,old_y); BIND_PROP(Message_WindowMouseEvent,buttons); BIND_PROP(Message_WindowMouseEvent,command); BIND_PROP(Message_WindowMouseEvent,modifiers); krk_finalizeClass(Message_WindowMouseEvent); MAKE_MSG(WindowFocusChange); BIND_PROP(Message_WindowFocusChange,wid); BIND_PROP(Message_WindowFocusChange,focused); krk_finalizeClass(Message_WindowFocusChange); MAKE_MSG(ResizeOffer); BIND_PROP(Message_ResizeOffer,wid); BIND_PROP(Message_ResizeOffer,width); BIND_PROP(Message_ResizeOffer,height); BIND_PROP(Message_ResizeOffer,bufid); krk_finalizeClass(Message_ResizeOffer); MAKE_MSG(WindowAdvertise); BIND_PROP(Message_WindowAdvertise,wid); BIND_PROP(Message_WindowAdvertise,flags); BIND_PROP(Message_WindowAdvertise,size); BIND_PROP(Message_WindowAdvertise,width); BIND_PROP(Message_WindowAdvertise,height); BIND_PROP(Message_WindowAdvertise,bufid); BIND_PROP(Message_WindowAdvertise,name); BIND_PROP(Message_WindowAdvertise,icon); krk_finalizeClass(Message_WindowAdvertise); MAKE_MSG(WindowMove); BIND_PROP(Message_WindowMove,wid); BIND_PROP(Message_WindowMove,x); BIND_PROP(Message_WindowMove,y); krk_finalizeClass(Message_WindowMove); MAKE_MSG(KeyEvent); BIND_PROP(Message_KeyEvent,wid); BIND_PROP(Message_KeyEvent,keycode); BIND_PROP(Message_KeyEvent,modifiers); BIND_PROP(Message_KeyEvent,action); BIND_PROP(Message_KeyEvent,key); BIND_PROP(Message_KeyEvent,kbd_state); BIND_PROP(Message_KeyEvent,kbd_s_state); BIND_PROP(Message_KeyEvent,k_ctrl); BIND_PROP(Message_KeyEvent,k_shift); BIND_PROP(Message_KeyEvent,k_alt); BIND_PROP(Message_KeyEvent,k_super); BIND_PROP(Message_KeyEvent,kl_ctrl); BIND_PROP(Message_KeyEvent,kl_shift); BIND_PROP(Message_KeyEvent,kl_alt); BIND_PROP(Message_KeyEvent,kl_super); BIND_PROP(Message_KeyEvent,kr_ctrl); BIND_PROP(Message_KeyEvent,kr_shift); BIND_PROP(Message_KeyEvent,kr_alt); BIND_PROP(Message_KeyEvent,kr_super); BIND_PROP(Message_KeyEvent,kbd_esc_buf); krk_finalizeClass(Message_KeyEvent); MAKE_MSG(WindowClose); BIND_PROP(Message_WindowClose,wid); krk_finalizeClass(Message_WindowClose); /** * Core connection type; singleton * * class YutaniCtx: * display_width: int * display_height: int * def __new__(cls) * def poll(self, sync=True) -> Message * def wait_for(self, msgtype: int) -> Message * def subscribe(self) * def unsubscribe(self) * def query_windows(self) * def fileno(self) -> int * def query(self) -> int * def menu_process_event(self, message: Message) -> int */ krk_makeClass(module, &YutaniCtx, "YutaniCtx", KRK_BASE_CLASS(object)); YutaniCtx->allocSize = sizeof(struct _yutani_YutaniCtx); YutaniCtx->obj.flags |= KRK_OBJ_FLAGS_NO_INHERIT; BIND_STATICMETHOD(YutaniCtx,__new__); BIND_METHOD(YutaniCtx,poll); BIND_METHOD(YutaniCtx,wait_for); BIND_METHOD(YutaniCtx,subscribe); BIND_METHOD(YutaniCtx,unsubscribe); BIND_METHOD(YutaniCtx,query_windows); BIND_METHOD(YutaniCtx,fileno); BIND_METHOD(YutaniCtx,query); BIND_METHOD(YutaniCtx,menu_process_event); BIND_PROP(YutaniCtx,display_width); BIND_PROP(YutaniCtx,display_height); krk_finalizeClass(YutaniCtx); /* * Generic graphics context. * Subclassed by Window and Sprite. * * class GraphicsContext: * width: int * height: int * isDoubleBuffered: bool * def fill(self, color: int) * def flip(self) * def blur(self, radius: int = 2) * def line(self, x0: int, x1: int, y0: int, y1: int, color: int, thickness=None) * def rect(self, x: int, y: int, width: int, height: int, color: int, solid: bool = False, radius: int = 0) * 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) * * To allocate a new graphics context with a fresh backing store, use Sprite. */ krk_makeClass(module, &GraphicsContext, "GraphicsContext", KRK_BASE_CLASS(object)); GraphicsContext->allocSize = sizeof(struct _yutani_GraphicsContext); GraphicsContext->obj.flags |= KRK_OBJ_FLAGS_NO_INHERIT; BIND_STATICMETHOD(GraphicsContext,__new__); BIND_PROP(GraphicsContext,width); BIND_PROP(GraphicsContext,height); BIND_PROP(GraphicsContext,isDoubleBuffered); BIND_METHOD(GraphicsContext,fill); BIND_METHOD(GraphicsContext,flip); BIND_METHOD(GraphicsContext,blur); BIND_METHOD(GraphicsContext,line); BIND_METHOD(GraphicsContext,rect); BIND_METHOD(GraphicsContext,draw_sprite); BIND_METHOD(GraphicsContext,__setitem__); BIND_METHOD(GraphicsContext,__getitem__); krk_finalizeClass(GraphicsContext); /* * Graphics object with a bitmap backing store, * typically derived from an image file. * * class Sprite(GraphicsContext): * def __init__(self, file=None, width=0, height=0) */ krk_makeClass(module, &Sprite, "Sprite", GraphicsContext); Sprite->allocSize = sizeof(struct _yutani_Sprite); Sprite->_ongcsweep = _yutani_Sprite_gcsweep; BIND_METHOD(Sprite,__init__); BIND_METHOD(Sprite,__repr__); BIND_METHOD(Sprite,free); krk_finalizeClass(Sprite); /* * A window. * * class Window(GraphicsContext): * title: str * icon: str * wid: int * x: int * y: int * focused: bool * closed: bool * def __init__(self, width: int, height: int, flags: int = 0, title: str = None, icon: str = None, doublebuffer: bool = True) * def flip(self) * def move(self, x: int, y: int) * def close(self) * def set_stack(self, z: int) * def special_request(self: request: int) * def resize(self, width: int, height: int) * def resize_start(self, direction: int) * def resize_done(self) * def resize_offer(self, width: int, height: int) * def resize_accept(self, width: int, height: int) * def update_shape(self, threshold: int) * def show_mouse(self, mouse: int) * def warp_mouse(self, x: int, y: int) * def reinit(self) */ krk_makeClass(module, &Window, "Window", GraphicsContext); Window->allocSize = sizeof(struct _yutani_Window); Window->_ongcscan = _yutani_Window_gcscan; BIND_METHOD(Window,__init__); BIND_METHOD(Window,__repr__); BIND_METHOD(Window,flip); BIND_METHOD(Window,move); BIND_METHOD(Window,close); BIND_METHOD(Window,set_stack); BIND_METHOD(Window,special_request); BIND_METHOD(Window,resize); BIND_METHOD(Window,resize_start); BIND_METHOD(Window,resize_done); BIND_METHOD(Window,resize_offer); BIND_METHOD(Window,resize_accept); BIND_METHOD(Window,update_shape); BIND_METHOD(Window,show_mouse); BIND_METHOD(Window,warp_mouse); BIND_METHOD(Window,reinit); BIND_PROP(Window,title); BIND_PROP(Window,icon); BIND_PROP(Window,wid); BIND_PROP(Window,x); BIND_PROP(Window,y); BIND_PROP(Window,focused); BIND_PROP(Window,closed); krk_finalizeClass(Window); krk_makeClass(module, &Subregion, "Subregion", GraphicsContext); Subregion->allocSize = sizeof(struct _yutani_Subregion); Subregion->_ongcsweep = _yutani_Subregion_gcsweep; BIND_METHOD(Subregion,__init__); BIND_PROP(Subregion,offset_x); BIND_PROP(Subregion,offset_y); krk_finalizeClass(Subregion); /* * Typeface using the 'text' library. * * class Font: * size: int * def __init__(self, font: str, size: int, color: int = rgb(0,0,0)) * def draw_string(self, ctx: GraphicsContext, s: str, x: int, y: int) -> int * def draw_string_shadow(self, ctx: GraphicsContext, s: str, x: int, y: int, shadow: int, blur: int) * def width(self, s: str) -> int */ krk_makeClass(module, &Font, "Font", KRK_BASE_CLASS(object)); Font->allocSize = sizeof(struct _yutani_Font); Font->_ongcsweep = _yutani_Font_gcsweep; BIND_METHOD(Font,__init__); BIND_METHOD(Font,draw_string); BIND_METHOD(Font,draw_string_shadow); BIND_METHOD(Font,width); BIND_METHOD(Font,measure); BIND_METHOD(Font,draw_glyph_into); BIND_METHOD(Font,prepare_string); BIND_METHOD(Font,ellipsify); BIND_PROP(Font,size); krk_finalizeClass(Font); /* * Menu bar widget. * * This should really be in a higher-level GUI toolkit, but for now we have what we have... * * class MenuBar: * def __init__(self, entries: tuple[tuple[str,str]]) * def place(self, x: int, y: int, width: int, window: Window) * def render(self, ctx: GraphicsContext) * def mouse_event(self, window: Window, message: Message_WindowMouseEvent) * def insert(self, name: str, menu: MenuList) */ krk_makeClass(module, &MenuBar, "MenuBar", KRK_BASE_CLASS(object)); MenuBar->allocSize = sizeof(struct _yutani_MenuBar); MenuBar->_ongcsweep = _yutani_MenuBar_gcsweep; BIND_METHOD(MenuBar,__init__); BIND_METHOD(MenuBar,place); BIND_METHOD(MenuBar,render); BIND_METHOD(MenuBar,mouse_event); BIND_METHOD(MenuBar,insert); BIND_PROP(MenuBar,height); krk_finalizeClass(MenuBar); /* * MenuList wrapper * * class MenuList: * def __init__(self) * def insert(self, entry: MenuEntry) */ krk_makeClass(module, &MenuList, "MenuList", KRK_BASE_CLASS(object)); MenuList->allocSize = sizeof(struct _yutani_MenuList); /* XXX where is the cleanup function for this? */ BIND_METHOD(MenuList,__init__); BIND_METHOD(MenuList,insert); krk_finalizeClass(MenuList); /* * Menu entry wrapper. * * class MenuEntry: * def __init__(self, title: str, callback: function, icon: str = None, action: str = None) */ krk_makeClass(module, &MenuEntry, "MenuEntry", KRK_BASE_CLASS(object)); MenuEntry->allocSize = sizeof(struct _yutani_MenuEntry); BIND_METHOD(MenuEntry,__init__); BIND_PROP(MenuEntry,height); BIND_PROP(MenuEntry,width); BIND_PROP(MenuEntry,rwidth); BIND_PROP(MenuEntry,hilight); BIND_PROP(MenuEntry,offset); BIND_METHOD(MenuEntry,update_icon); krk_finalizeClass(MenuEntry); /* * Toggle subtype * * class MenuEntryToggle(MenuEntry): * def __init__(self, title: str, callback: function, state: bool = False, action: str = None) */ krk_makeClass(module, &MenuEntryToggle, "MenuEntryToggle", MenuEntry); MenuEntryToggle->allocSize = sizeof(struct _yutani_MenuEntryToggle); BIND_METHOD(MenuEntryToggle,__init__); BIND_PROP(MenuEntryToggle,state); krk_finalizeClass(MenuEntryToggle); /* * Submenu subtype * * class MenuEntrySubmenu(MenuEntry): * def __init__(self, title: str, action: str, icon: str = None) */ krk_makeClass(module, &MenuEntrySubmenu, "MenuEntrySubmenu", MenuEntry); MenuEntrySubmenu->allocSize = sizeof(struct _yutani_MenuEntrySubmenu); BIND_METHOD(MenuEntrySubmenu,__init__); krk_finalizeClass(MenuEntrySubmenu); /** * Separator subtype * * class MenuEntrySeparator(MenuEntry): * def __init__(self) */ krk_makeClass(module, &MenuEntrySeparator, "MenuEntrySeparator", MenuEntry); MenuEntrySeparator->allocSize = sizeof(struct _yutani_MenuEntrySeparator); BIND_METHOD(MenuEntrySeparator,__init__); krk_finalizeClass(MenuEntrySeparator); krk_makeClass(module, &MenuEntryCustom, "MenuEntryCustom", MenuEntry); MenuEntryCustom->allocSize = sizeof(struct _yutani_MenuEntryCustom); BIND_METHOD(MenuEntryCustom,__init__); krk_finalizeClass(MenuEntryCustom); krk_makeClass(module, &TTContour, "TTContour", KRK_BASE_CLASS(object)); TTContour->allocSize = sizeof(struct _yutani_TTContour); TTContour->_ongcsweep = _TTContour_ongcsweep; BIND_METHOD(TTContour,__init__); BIND_METHOD(TTContour,line_to); BIND_METHOD(TTContour,move_to); BIND_METHOD(TTContour,finish); BIND_METHOD(TTContour,free); BIND_METHOD(TTContour,stroke); BIND_METHOD(TTContour,stroke_path); BIND_METHOD(TTContour,transform); krk_finalizeClass(TTContour); krk_makeClass(module, &TTShape, "TTShape", KRK_BASE_CLASS(object)); TTShape->allocSize = sizeof(struct _yutani_TTShape); TTShape->_ongcsweep = _TTShape_ongcsweep; BIND_METHOD(TTShape,__init__); BIND_METHOD(TTShape,paint); BIND_METHOD(TTShape,paint_sprite); BIND_METHOD(TTShape,free); #define CONST(n) krk_attachNamedValue(&TTShape->methods, #n, INTEGER_VAL(n)) CONST(TT_PATH_FILTER_BILINEAR); CONST(TT_PATH_FILTER_NEAREST); CONST(TT_PATH_WRAP_REPEAT); CONST(TT_PATH_WRAP_NONE); CONST(TT_PATH_WRAP_PAD); #undef CONST krk_finalizeClass(TTShape); krk_makeClass(module, &TransformMatrix, "TransformMatrix", KRK_BASE_CLASS(object)); TransformMatrix->allocSize = sizeof(struct _yutani_TransformMatrix); BIND_METHOD(TransformMatrix,__init__); BIND_METHOD(TransformMatrix,__repr__); BIND_METHOD(TransformMatrix,scale); BIND_METHOD(TransformMatrix,translate); BIND_METHOD(TransformMatrix,rotate); BIND_METHOD(TransformMatrix,shear); BIND_METHOD(TransformMatrix,apply); BIND_PROP(TransformMatrix,a); BIND_PROP(TransformMatrix,b); BIND_PROP(TransformMatrix,tx); BIND_PROP(TransformMatrix,c); BIND_PROP(TransformMatrix,d); BIND_PROP(TransformMatrix,ty); krk_finalizeClass(TransformMatrix); BIND_FUNC(module,decor_get_bounds); BIND_FUNC(module,decor_render); BIND_FUNC(module,decor_handle_event); BIND_FUNC(module,decor_show_default_menu); BIND_FUNC(module,rgb); BIND_FUNC(module,draw_button); BIND_FUNC(module,fswait); } ================================================ FILE: lib/kuroko/yutani_mainloop.krk ================================================ ''' Asynchronous event loop for Yutani applications. ''' import time import _yutani2 let _windows = {} let yctx = _yutani2.YutaniCtx() class Window(_yutani2.Window): ''' Base class for eventloop-managed windows, providing stub implementations of the core callback functions. ''' def __init__(self, *args, **kwargs): super().__init__(*args,**kwargs) _windows[self.wid] = self def close(self): if self.wid in _windows: del _windows[self.wid] super().close() def draw(self): pass def keyboard_event(self, msg): pass def focus_changed(self, msg): self.focused = msg.focused self.draw() def finish_resize(self, msg): self.resize_accept(msg.width, msg.height) self.reinit() self.draw() self.resize_done() self.flip() def window_moved(self, msg): pass def mouse_event(self, msg): pass let current_loop class Future(): def __init__(self): self.result = None self.loop = current_loop self.done = False self.callbacks = [] def add_callback(self, func): self.callbacks.append(func) def schedule_callbacks(self): let mycallbacks = self.callbacks[:] self.callbacks = [] for callback in mycallbacks: self.loop.call_soon(callback, self) def set_result(self, result): self.result = result self.done = True self.schedule_callbacks() def __await__(self): if not self.done: yield self return self.result class Task(): def __init__(self, coro): self.coro = coro current_loop.call_soon(self.step) def step(self): let result = self.coro.send(None) if isinstance(result,Future): result.add_callback(self.wakeup) else if result == self.coro: return # This task is done else if result is None: current_loop.call_soon(self.step) else: print("Don't know what to do with",result) def wakeup(self, future): self.step() async def sleep(delay, result=None): let future = Future() current_loop.call_later(delay, future.set_result, result) return await future class Timer(): def __init__(self, time, func, args): self.time = time self.func = func self.args = args def __lt__(self, other): return self.time < other.time def __gt__(self, other): return self.time > other.time def __le__(self, other): return self.time <= other.time def __ge__(self, other): return self.time >= other.time def __eq__(self, other): return self.time == other.time class AsyncMainloop(): def __init__(self): self.should_exit = 0 self.status_code = 0 self.ready = [] self.schedule = [] self.fileno = yctx.fileno() self.menu_closed_callback = None def exit(self, arg=0): self.status_code = arg self.should_exit = 1 def call_soon(self, func, *args): self.ready.append((func,args)) def call_later(self, delay, func, *args): self.call_at(time.time() + delay, func, *args) def call_at(self, time, func, *args): self.schedule.append(Timer(time,func,args)) self.schedule.sort() def maybe_coro(self, result): if isinstance(result, generator): Task(result) def handle_message(self): let msg = yctx.poll() if yctx.menu_process_event(msg): if self.menu_closed_callback: self.maybe_coro(self.menu_closed_callback()) if msg.msg_type == _yutani2.Message.MSG_SESSION_END: self.exit() return False else if msg.msg_type == _yutani2.Message.MSG_KEY_EVENT: if msg.wid in _windows: self.maybe_coro(_windows[msg.wid].keyboard_event(msg)) else if msg.msg_type == _yutani2.Message.MSG_WINDOW_FOCUS_CHANGE: if msg.wid in _windows: self.maybe_coro(_windows[msg.wid].focus_changed(msg)) else if msg.msg_type == _yutani2.Message.MSG_RESIZE_OFFER: if msg.wid in _windows: self.maybe_coro(_windows[msg.wid].finish_resize(msg)) else if msg.msg_type == _yutani2.Message.MSG_WINDOW_MOVE: if msg.wid in _windows: self.maybe_coro(_windows[msg.wid].window_moved(msg)) else if msg.msg_type == _yutani2.Message.MSG_WINDOW_MOUSE_EVENT: if msg.wid in _windows: self.maybe_coro(_windows[msg.wid].mouse_event(msg)) else if msg.msg_type == _yutani2.Message.MSG_WINDOW_CLOSE: if msg.wid in _windows: self.maybe_coro(_windows[msg.wid].close()) return True def run_once(self): # Determine if anything in the schedule list can be run let timeout = -1 let now = time.time() if self.ready: timeout = 0 else if self.schedule: timeout = max(0,self.schedule[0].time - now) # Poll let res = _yutani2.fswait((self.fileno,), int(timeout * 1000)) # TODO probably hooks if these have callbacks if res[0]: self.handle_message() # Schedule future stuff while self.schedule and self.schedule[0].time <= now: self.ready.append((self.schedule[0].func,self.schedule[0].args)) self.schedule.pop(0) let count = len(self.ready) for i in range(count): let func, args = self.ready.pop(0) func(*args) def run(self): current_loop = self while not self.should_exit: self.run_once() def activate(self): current_loop = self ================================================ FILE: lib/list.c ================================================ /** * @brief General-purpose list implementations. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2018 K. Lange */ #ifdef _KERNEL_ # include #else # include # include #endif #include void list_destroy(list_t * list) { /* Free all of the contents of a list */ node_t * n = list->head; while (n) { free(n->value); n = n->next; } } void list_free(list_t * list) { /* Free the actual structure of a list */ node_t * n = list->head; while (n) { node_t * s = n->next; free(n); n = s; } } void list_append(list_t * list, node_t * node) { assert(!(node->next || node->prev) && "Node is already in a list."); node->next = NULL; /* Insert a node onto the end of a list */ node->owner = list; if (!list->length) { list->head = node; list->tail = node; node->prev = NULL; node->next = NULL; list->length++; return; } list->tail->next = node; node->prev = list->tail; list->tail = node; list->length++; } node_t * list_insert(list_t * list, void * item) { /* Insert an item into a list */ node_t * node = malloc(sizeof(node_t)); node->value = item; node->next = NULL; node->prev = NULL; node->owner = NULL; list_append(list, node); return node; } void list_append_after(list_t * list, node_t * before, node_t * node) { assert(!(node->next || node->prev) && "Node is already in a list."); node->owner = list; if (!list->length) { list_append(list, node); return; } if (before == NULL) { node->next = list->head; node->prev = NULL; list->head->prev = node; list->head = node; list->length++; return; } if (before == list->tail) { list->tail = node; } else { before->next->prev = node; node->next = before->next; } node->prev = before; before->next = node; list->length++; } node_t * list_insert_after(list_t * list, node_t * before, void * item) { node_t * node = malloc(sizeof(node_t)); node->value = item; node->next = NULL; node->prev = NULL; node->owner = NULL; list_append_after(list, before, node); return node; } void list_append_before(list_t * list, node_t * after, node_t * node) { assert(!(node->next || node->prev) && "Node is already in a list."); node->owner = list; if (!list->length) { list_append(list, node); return; } if (after == NULL) { node->next = NULL; node->prev = list->tail; list->tail->next = node; list->tail = node; list->length++; return; } if (after == list->head) { list->head = node; } else { after->prev->next = node; node->prev = after->prev; } node->next = after; after->prev = node; list->length++; } node_t * list_insert_before(list_t * list, node_t * after, void * item) { node_t * node = malloc(sizeof(node_t)); node->value = item; node->next = NULL; node->prev = NULL; node->owner = NULL; list_append_before(list, after, node); return node; } list_t * list_create(void) { /* Create a fresh list */ list_t * out = malloc(sizeof(list_t)); out->head = NULL; out->tail = NULL; out->length = 0; return out; } node_t * list_find(list_t * list, void * value) { foreach(item, list) { if (item->value == value) { return item; } } return NULL; } int list_index_of(list_t * list, void * value) { int i = 0; foreach(item, list) { if (item->value == value) { return i; } i++; } return -1; /* not find */ } void * list_index(list_t * list, int index) { int i = 0; foreach(item, list) { if (i == index) return item->value; i++; } return NULL; } void list_remove(list_t * list, size_t index) { /* remove index from the list */ if (index > list->length) return; size_t i = 0; node_t * n = list->head; while (i < index) { n = n->next; i++; } list_delete(list, n); } void list_delete(list_t * list, node_t * node) { /* remove node from the list */ assert(node->owner == list && "Tried to remove a list node from a list it does not belong to."); if (node == list->head) { list->head = node->next; } if (node == list->tail) { list->tail = node->prev; } if (node->prev) { node->prev->next = node->next; } if (node->next) { node->next->prev = node->prev; } node->prev = NULL; node->next = NULL; node->owner = NULL; list->length--; } node_t * list_pop(list_t * list) { /* Remove and return the last value in the list * If you don't need it, you still probably want to free it! * Try free(list_pop(list)); ! * */ if (!list->tail) return NULL; node_t * out = list->tail; list_delete(list, out); return out; } node_t * list_dequeue(list_t * list) { if (!list->head) return NULL; node_t * out = list->head; list_delete(list, out); return out; } list_t * list_copy(list_t * original) { /* Create a new copy of original */ list_t * out = list_create(); node_t * node = original->head; while (node) { list_insert(out, node->value); } return out; } void list_merge(list_t * target, list_t * source) { /* Destructively merges source into target */ foreach(node, source) { node->owner = target; } if (source->head) { source->head->prev = target->tail; } if (target->tail) { target->tail->next = source->head; } else { target->head = source->head; } if (source->tail) { target->tail = source->tail; } target->length += source->length; free(source); } ================================================ FILE: lib/markup.c ================================================ /** * @brief HTML-ish markup parser. * * @copyright * 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 */ #include #include struct markup_state { int state; void * user; markup_callback_tag_open callback_tag_open; markup_callback_tag_close callback_tag_close; markup_callback_data callback_data; /* Private stuff */ struct markup_tag tag; size_t len; char data[64]; char * attr; }; struct markup_state * markup_init(void * user, markup_callback_tag_open open, markup_callback_tag_close close, markup_callback_data data) { struct markup_state * out = malloc(sizeof(struct markup_state)); out->state = 0; out->user = user; out->len = 0; out->callback_tag_open = open; out->callback_tag_close = close; out->callback_data = data; return out; } static void _dump_buffer(struct markup_state * state) { if (state->len) { state->data[state->len] = '\0'; state->callback_data(state, state->user, state->data); state->data[0] = '\0'; state->len = 0; } } static void _finish_name(struct markup_state * state) { state->data[state->len] = '\0'; state->tag.name = strdup(state->data); state->tag.options = hashmap_create(5); state->data[0] = '\0'; state->len = 0; state->state = 2; } static void _finish_close(struct markup_state * state) { state->data[state->len] = '\0'; state->callback_tag_close(state, state->user, state->data); state->data[0] = '\0'; state->len = 0; state->state = 0; } static void _finish_tag(struct markup_state * state) { state->callback_tag_open(state, state->user, &state->tag); state->state = 0; } static void _finish_bare_attr(struct markup_state * state) { state->data[state->len] = '\0'; hashmap_set(state->tag.options, state->data, strdup(state->data)); state->data[0] = '\0'; state->len = 0; } static void _finish_attr(struct markup_state * state) { state->data[state->len] = '\0'; state->attr = strdup(state->data); state->data[0] = '\0'; state->len = 0; state->state = 4; } static void _finish_attr_value(struct markup_state * state) { state->data[state->len] = '\0'; hashmap_set(state->tag.options, state->attr, strdup(state->data)); free(state->attr); state->data[0] = '\0'; state->len = 0; state->state = 2; } int markup_free_tag(struct markup_tag * tag) { free(tag->name); list_t * keys = hashmap_keys(tag->options); if (keys->length) { foreach(node, keys) { free(hashmap_get(tag->options, node->value)); } } list_free(keys); free(keys); hashmap_free(tag->options); free(tag->options); return 0; } int markup_parse(struct markup_state * state, char c) { switch (state->state) { case 0: /* STATE_NORMAL */ if (state->len == 63) { _dump_buffer(state); } switch (c) { case '<': _dump_buffer(state); state->state = 1; return 0; default: state->data[state->len] = c; state->len++; return 0; } break; case 1: /* STATE_TAG_OPEN */ switch (c) { case '/': if (state->len) { fprintf(stderr, "syntax error\n"); return 1; } state->state = 3; /* STATE_TAG_CLOSE */ return 0; case '>': _finish_name(state); _finish_tag(state); return 0; case ' ': _finish_name(state); return 0; default: state->data[state->len] = c; state->len++; return 0; } break; case 2: /* STATE_TAG_ATTRIB */ switch (c) { case ' ': /* attribute has no value, end it and append it with = self */ _finish_bare_attr(state); return 0; case '>': _finish_bare_attr(state); _finish_tag(state); return 0; case '=': /* attribute has a value, go to next mode */ _finish_attr(state); return 0; default: state->data[state->len] = c; state->len++; return 0; } return 0; case 3: /* STATE_TAG_CLOSE */ switch (c) { case '>': _finish_close(state); return 0; default: state->data[state->len] = c; state->len++; return 0; } break; case 4: /* STATE_ATTR_VALUE */ switch (c) { case ' ': _finish_attr_value(state); return 0; case '>': _finish_attr_value(state); _finish_tag(state); return 0; default: state->data[state->len] = c; state->len++; return 0; } break; default: fprintf(stderr, "parser in unknown state\n"); return 1; } return 0; } int markup_finish(struct markup_state * state) { if (state->state != 0) { fprintf(stderr, "unexpected end of data\n"); return 1; } else { _dump_buffer(state); free(state); return 0; } } ================================================ FILE: lib/markup_text.c ================================================ /** * @brief Marked up text label renderer. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include "toaru/markup_text.h" static struct TT_Font * dejaVuSans = NULL; static struct TT_Font * dejaVuSans_Bold = NULL; static struct TT_Font * dejaVuSans_Oblique = NULL; static struct TT_Font * dejaVuSans_BoldOblique = NULL; static struct TT_Font * dejaVuSansMono = NULL; static struct TT_Font * dejaVuSansMono_Bold = NULL; static struct TT_Font * dejaVuSansMono_Oblique = NULL; static struct TT_Font * dejaVuSansMono_BoldOblique = NULL; struct MarkupState { struct markup_state * parser; list_t * state; int current_state; int cursor_x; int cursor_y; int initial_left; uint32_t color; gfx_context_t * ctx; int max_cursor_x; list_t * colors; int sizes[3]; int dryrun; }; static void push_state(struct MarkupState * state, int val) { list_insert(state->state, (void*)(uintptr_t)state->current_state); state->current_state |= val; } static void pop_state(struct MarkupState * state) { node_t * nstate = list_pop(state->state); state->current_state = (int)(uintptr_t)nstate->value; free(nstate); } static uint32_t parseColor(const char * c) { if (*c != '#' || strlen(c) != 7) return rgba(0,0,0,255); char r[3] = {c[1],c[2],'\0'}; char g[3] = {c[3],c[4],'\0'}; char b[3] = {c[5],c[6],'\0'}; return rgba(strtoul(r,NULL,16),strtoul(g,NULL,16),strtoul(b,NULL,16),255); } static int parser_open(struct markup_state * self, void * user, struct markup_tag * tag) { struct MarkupState * state = (struct MarkupState*)user; if (!strcmp(tag->name, "b")) { push_state(state, MARKUP_TEXT_STATE_BOLD); } else if (!strcmp(tag->name, "i")) { push_state(state, MARKUP_TEXT_STATE_OBLIQUE); } else if (!strcmp(tag->name, "h1")) { push_state(state, MARKUP_TEXT_STATE_HEADING); } else if (!strcmp(tag->name, "small")) { push_state(state, MARKUP_TEXT_STATE_SMALL); } else if (!strcmp(tag->name, "mono")) { push_state(state, MARKUP_TEXT_STATE_MONO); } else if (!strcmp(tag->name, "br")) { state->cursor_x = state->initial_left; state->cursor_y += 20; /* state->line_height? */ } else if (!strcmp(tag->name, "color")) { /* get options */ list_t * args = hashmap_keys(tag->options); if (args->length == 1) { list_insert(state->colors, (void*)(uintptr_t)state->color); state->color = parseColor((char*)args->head->value); } free(args); } markup_free_tag(tag); return 0; } static int parser_close(struct markup_state * self, void * user, char * tag_name) { struct MarkupState * state = (struct MarkupState*)user; if (!strcmp(tag_name, "b")) { pop_state(state); } else if (!strcmp(tag_name, "i")) { pop_state(state); } else if (!strcmp(tag_name, "h1")) { pop_state(state); } else if (!strcmp(tag_name, "small")) { pop_state(state); } else if (!strcmp(tag_name, "mono")) { pop_state(state); } else if (!strcmp(tag_name, "color")) { node_t * ncolor = list_pop(state->colors); state->color = (uint32_t)(uintptr_t)ncolor->value; free(ncolor); } return 0; } static struct TT_Font * fontForState(struct MarkupState * state) { int bold = !!(state->current_state & MARKUP_TEXT_STATE_BOLD); int obli = !!(state->current_state & MARKUP_TEXT_STATE_OBLIQUE); int mono = !!(state->current_state & MARKUP_TEXT_STATE_MONO); if (mono) { if (bold && obli) return dejaVuSansMono_BoldOblique; if (bold) return dejaVuSansMono_Bold; if (obli) return dejaVuSansMono_Oblique; return dejaVuSansMono; } else { if (bold && obli) return dejaVuSans_BoldOblique; if (bold) return dejaVuSans_Bold; if (obli) return dejaVuSans_Oblique; return dejaVuSans; } } static int sizeForState(struct MarkupState * state) { if (state->current_state & MARKUP_TEXT_STATE_HEADING) return state->sizes[2]; if (state->current_state & MARKUP_TEXT_STATE_SMALL) return state->sizes[1]; return state->sizes[0]; } struct GlyphCacheEntry { struct TT_Font * font; sprite_t * sprites[3]; int xs[3]; uint32_t size; uint32_t glyph; uint32_t color; int y; }; static struct GlyphCacheEntry glyph_cache[1024]; static 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) { unsigned int hash = (((uintptr_t)_font >> 8) ^ (glyph * size)) & 1023; struct GlyphCacheEntry * entry = &glyph_cache[hash]; if (entry->font != _font || entry->size != size || entry->glyph != glyph) { if (entry->sprites[0]) sprite_free(entry->sprites[0]); if (entry->sprites[1]) sprite_free(entry->sprites[1]); if (entry->sprites[2]) sprite_free(entry->sprites[2]); tt_set_size(_font, size); entry->font = _font; entry->size = size; entry->glyph = glyph; entry->color = _ALP(fg) == 255 ? fg : rgb(0,0,0); entry->sprites[0] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[0], &entry->y, 0.0); entry->sprites[1] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[1], &entry->y, 0.333); entry->sprites[2] = tt_bake_glyph(entry->font, entry->glyph, entry->color, &entry->xs[2], &entry->y, 0.666); } if (entry->sprites[0]) { int sprite = xadj < 0.166 ? 0 : xadj < 0.5 ? 1 : 2; if (entry->color != fg) { draw_sprite_alpha_paint(ctx, entry->sprites[sprite], x + entry->xs[sprite], y + entry->y, 1.0, fg); } else { draw_sprite(ctx, entry->sprites[sprite], x + entry->xs[sprite], y + entry->y); } } } static int string_draw_internal(gfx_context_t * ctx, struct TT_Font * font, int font_size, int x, int y, char * data, uint32_t color) { float x_offset = x; uint32_t cp = 0; uint32_t istate = 0; for (const unsigned char * c = (const unsigned char*)data; *c; ++c) { if (!decode(&istate, &cp, *c)) { unsigned int glyph = tt_glyph_for_codepoint(font, cp); draw_cached_glyph(ctx, font, font_size, (int)floor(x_offset), y, glyph, color, x_offset-floor(x_offset)); x_offset += tt_glyph_width(font, glyph); } } return x_offset - x; } static int parser_data(struct markup_state * self, void * user, char * data) { struct MarkupState * state = (struct MarkupState*)user; struct TT_Font * font = fontForState(state); int size = sizeForState(state); tt_set_size(font, size); state->cursor_x += string_draw_internal(state->ctx, font, size, state->cursor_x, state->cursor_y, data, state->color); if (state->cursor_x > state->max_cursor_x) state->max_cursor_x = state->cursor_x; return 0; } static int parser_dryrun(struct markup_state * self, void * user, char * data) { struct MarkupState * state = (struct MarkupState*)user; struct TT_Font * font = fontForState(state); tt_set_size(font, sizeForState(state)); state->cursor_x += tt_string_width(font, data); if (state->cursor_x > state->max_cursor_x) state->max_cursor_x = state->cursor_x; return 0; } struct MarkupState * markup_setup_renderer(gfx_context_t * ctx, int x, int y, uint32_t color, int dryrun) { struct MarkupState * state = malloc(sizeof(struct MarkupState)); state->parser = markup_init(state, parser_open, parser_close, dryrun ? parser_dryrun : parser_data); state->state = list_create(); state->current_state = 0; state->cursor_x = x; state->cursor_y = y; state->initial_left = x; state->color = color; state->ctx = ctx; state->max_cursor_x = x; state->colors = list_create(); state->sizes[0] = 13; state->sizes[1] = 10; state->sizes[2] = 18; state->dryrun = dryrun; return state; } void markup_set_base_font_size(struct MarkupState * state, int size) { state->sizes[0] = size; state->sizes[1] = 10 * size / 13; state->sizes[2] = 18 * size / 13; } void markup_set_base_state(struct MarkupState * state, int mode) { state->current_state = mode; } int markup_push_string(struct MarkupState * state, const char * str) { while (*str) { if (markup_parse(state->parser, *str++)) { break; } } return state->max_cursor_x - state->initial_left; } int markup_push_raw_string(struct MarkupState * state, const char * str) { if (state->dryrun) { return parser_dryrun(state->parser, state, (char*)str); } else { return parser_data(state->parser, state, (char*)str); } } int markup_finish_renderer(struct MarkupState * state) { markup_finish(state->parser); list_free(state->state); list_free(state->colors); free(state->state); free(state->colors); int total = state->max_cursor_x - state->initial_left; free(state); return total; } int markup_string_width(const char * str) { struct MarkupState * state = markup_setup_renderer(NULL,0,0,0,1); while (*str) { if (markup_parse(state->parser, *str++)) { break; } } return markup_finish_renderer(state); } int markup_string_height(const char * str) { struct MarkupState * state = markup_setup_renderer(NULL,0,0,0,1); while (*str) { if (markup_parse(state->parser, *str++)) { break; } } int out = state->cursor_y; markup_finish_renderer(state); return out; } int markup_draw_string(gfx_context_t * ctx, int x, int y, const char * str, uint32_t color) { struct MarkupState * state = markup_setup_renderer(ctx,x,y,color,0); while (*str) { if (markup_parse(state->parser, *str++)) { break; } } return markup_finish_renderer(state); } void markup_text_init(void) { if (!dejaVuSans) { dejaVuSans = tt_font_from_shm("sans-serif"); dejaVuSans_Bold = tt_font_from_shm("sans-serif.bold"); dejaVuSans_Oblique = tt_font_from_shm("sans-serif.italic"); dejaVuSans_BoldOblique = tt_font_from_shm("sans-serif.bolditalic"); dejaVuSansMono = tt_font_from_shm("monospace"); dejaVuSansMono_Bold = tt_font_from_shm("monospace.bold"); dejaVuSansMono_Oblique = tt_font_from_shm("monospace.italic"); dejaVuSansMono_BoldOblique = tt_font_from_shm("monospace.bolditalic"); } } ================================================ FILE: lib/menu.c ================================================ /** * @brief Cascading graphical menu library. * * C reimplementation of the original Python menu library. * Used to provide menu bars and the applications menu. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MENU_ENTRY_HEIGHT 20 #define MENU_BACKGROUND rgb(239,238,232) #define MENU_ICON_SIZE 16 #define HILIGHT_BORDER_TOP rgb(54,128,205) #define HILIGHT_GRADIENT_TOP rgb(93,163,236) #define HILIGHT_GRADIENT_BOTTOM rgb(56,137,220) #define HILIGHT_BORDER_BOTTOM rgb(47,106,167) #define MENU_ENTRY_FLAGS_DISABLED 0x0001 static hashmap_t * menu_windows = NULL; static yutani_t * my_yctx = NULL; static struct MenuList * hovered_menu = NULL; int menu_definitely_close(struct MenuList * menu); __attribute__((constructor)) static void _init_menus(void) { menu_windows = hashmap_create_int(10); markup_text_init(); } hashmap_t * menu_get_windows_hash(void) { return menu_windows; } static int string_width(const char * s) { return markup_string_width(s); } static int draw_string(gfx_context_t * ctx, int x, int y, uint32_t color, const char * s) { return markup_draw_string(ctx,x,y+13,s,color); } static int _menu_is_disabled(struct MenuEntry * entry); void _menu_draw_MenuEntry_Normal(gfx_context_t * ctx, struct MenuEntry * self, int offset) { struct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self; _self->offset = offset; /* Background gradient */ if (!_menu_is_disabled(self) && _self->hilight) { draw_line(ctx, 1, _self->width-2, offset, offset, HILIGHT_BORDER_TOP); draw_line(ctx, 1, _self->width-2, offset + _self->height - 1, offset + _self->height - 1, HILIGHT_BORDER_BOTTOM); for (int i = 1; i < self->height-1; ++i) { int thing = ((i - 1) * 256) / (_self->height - 2); if (thing > 255) thing = 255; if (thing < 0) thing = 0; uint32_t c = interp_colors(HILIGHT_GRADIENT_TOP, HILIGHT_GRADIENT_BOTTOM, thing); draw_line(ctx, 1, self->width-2, offset + i, offset + i, c); } } /* Icon */ if (_self->icon) { sprite_t * icon = icon_get_16(_self->icon); if (icon->width == MENU_ICON_SIZE) { if (_menu_is_disabled(self)) { draw_sprite_alpha(ctx, icon, 4, offset + 2, 0.5); } else { draw_sprite(ctx, icon, 4, offset + 2); } } else { if (_menu_is_disabled(self)) { draw_sprite_scaled_alpha(ctx, icon, 4, offset + 2, MENU_ICON_SIZE, MENU_ICON_SIZE, 0.5); } else { draw_sprite_scaled(ctx, icon, 4, offset + 2, MENU_ICON_SIZE, MENU_ICON_SIZE); } } } /* Foreground text color */ uint32_t color = _menu_is_disabled(self) ? rgba(0,0,0,127) : (_self->hilight ? rgb(255,255,255) : rgb(0,0,0)); /* Draw title */ draw_string(ctx, 22, offset + 1, color, _self->title); } void _menu_focus_MenuEntry_Normal(struct MenuEntry * self, int focused) { if (focused) { if (self->_owner && self->_owner->child) { menu_definitely_close(self->_owner->child); self->_owner->child = NULL; } } } void _menu_activate_MenuEntry_Normal(struct MenuEntry * self, int flags) { struct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self; if (_menu_is_disabled(self)) return; /* Do nothing */ list_t * menu_keys = hashmap_keys(menu_windows); hovered_menu = NULL; foreach(_key, menu_keys) { yutani_window_t * window = hashmap_get(menu_windows, (void*)(uintptr_t)_key->value); if (window) { struct MenuList * menu = window->user_data; menu_definitely_close(menu); if (menu->parent && menu->parent->child == menu) { menu->parent->child = NULL; } } } list_free(menu_keys); free(menu_keys); if (_self->callback) { _self->callback(_self); } } static struct MenuEntryVTable _menu_vtable_MenuEntry_Normal = { .methods = 3, .renderer = _menu_draw_MenuEntry_Normal, .focus_change = _menu_focus_MenuEntry_Normal, .activate = _menu_activate_MenuEntry_Normal, }; struct MenuEntry * menu_create_normal(const char * icon, const char * action, const char * title, void (*callback)(struct MenuEntry *)) { struct MenuEntry_Normal * out = calloc(1,sizeof(struct MenuEntry_Normal)); out->_type = MenuEntry_Normal; out->height = MENU_ENTRY_HEIGHT; out->hilight = 0; out->vtable = &_menu_vtable_MenuEntry_Normal; out->icon = icon ? strdup(icon) : NULL; out->title = strdup(title); out->action = action ? strdup(action) : NULL; out->callback = callback; out->rwidth = 50 + string_width(out->title); return (struct MenuEntry *)out; } void _menu_draw_MenuEntry_Toggle(gfx_context_t * ctx, struct MenuEntry * self, int offset) { _menu_draw_MenuEntry_Normal(ctx,self,offset); struct MenuEntry_Toggle * _self = (struct MenuEntry_Toggle *)self; if (_self->set) { sprite_t * check_box = icon_get_16("check"); uint32_t color = _menu_is_disabled(self) ? rgba(0,0,0,127) : (_self->hilight ? rgb(255,255,255) : rgb(0,0,0)); draw_sprite_alpha_paint(ctx, check_box, 4, offset + 2, 1.0, color); } } static struct MenuEntryVTable _menu_vtable_MenuEntry_Toggle = { .methods = 3, .renderer = _menu_draw_MenuEntry_Toggle, /* Only the renderer differs; users must manage toggle state */ .focus_change = _menu_focus_MenuEntry_Normal, .activate = _menu_activate_MenuEntry_Normal, }; struct MenuEntry * menu_create_toggle(const char * action, const char * title, int set, void (*callback)(struct MenuEntry *)) { /* Reuse the initializer for the normal one, assuming with no icon. */ struct MenuEntry_Toggle * out = (struct MenuEntry_Toggle*)menu_create_normal(NULL, action, title, callback); if (!out) return out; out = realloc(out, sizeof(struct MenuEntry_Toggle)); out->_type = MenuEntry_Toggle; out->vtable = &_menu_vtable_MenuEntry_Toggle; /* And just set our status */ out->set = set; return out; } void _menu_draw_MenuEntry_Submenu(gfx_context_t * ctx, struct MenuEntry * self, int offset) { struct MenuEntry_Submenu * _self = (struct MenuEntry_Submenu *)self; int h = _self->hilight; if (_self->_owner && _self->_my_child && _self->_owner->child == _self->_my_child && !_self->_my_child->closed) { _self->hilight = 1; } _menu_draw_MenuEntry_Normal(ctx,self,offset); /* Draw the tick on the right side to indicate this is a submenu */ uint32_t color = _menu_is_disabled(self) ? rgba(0,0,0,127) : (_self->hilight ? rgb(255,255,255) : rgb(0,0,0)); sprite_t * tick = icon_get_16("menu-tick"); draw_sprite_alpha_paint(ctx, tick, _self->width - 16, offset + 2, 1.0, color); _self->hilight = h; } void _menu_focus_MenuEntry_Submenu(struct MenuEntry * self, int focused) { if (focused) { self->vtable->activate(self, focused); } } void _menu_activate_MenuEntry_Submenu(struct MenuEntry * self, int focused) { struct MenuEntry_Submenu * _self = (struct MenuEntry_Submenu *)self; if (_menu_is_disabled(self)) return; /* Do nothing */ if (_self->_owner && _self->_owner->set) { /* Show a menu */ struct MenuList * new_menu = menu_set_get_menu(_self->_owner->set, (char *)_self->action); if (_self->_owner->child && _self->_owner->child != new_menu) { menu_definitely_close(_self->_owner->child); _self->_owner->child = NULL; } new_menu->parent = _self->_owner; new_menu->parent->child = new_menu; _self->_my_child = new_menu; if (new_menu->closed) { new_menu->main_window = new_menu->parent->main_window; menu_prepare(new_menu, _self->_owner->window->ctx); int offset_x = _self->_owner->window->width - 2; if (_self->_owner->window->width + _self->_owner->window->x - 2 + new_menu->window->width > _self->_owner->window->ctx->display_width) { offset_x = 2 - new_menu->window->width; } yutani_window_move_relative(_self->_owner->window->ctx, new_menu->window, _self->_owner->window, offset_x, _self->offset - 4); yutani_flip(_self->_owner->window->ctx, new_menu->window); } } } static struct MenuEntryVTable _menu_vtable_MenuEntry_Submenu = { .methods = 3, .renderer = _menu_draw_MenuEntry_Submenu, .focus_change = _menu_focus_MenuEntry_Submenu, .activate = _menu_activate_MenuEntry_Submenu, }; struct MenuEntry * menu_create_submenu(const char * icon, const char * action, const char * title) { struct MenuEntry_Submenu * out = calloc(1,sizeof(struct MenuEntry_Submenu)); out->_type = MenuEntry_Submenu; out->height = MENU_ENTRY_HEIGHT; out->hilight = 0; out->vtable = &_menu_vtable_MenuEntry_Submenu; out->icon = icon ? strdup(icon) : NULL; out->title = strdup(title); out->action = action ? strdup(action) : NULL; out->rwidth = 50 + string_width(out->title); return (struct MenuEntry *)out; } void _menu_draw_MenuEntry_Separator(gfx_context_t * ctx, struct MenuEntry * self, int offset) { self->offset = offset; draw_line(ctx, 2, self->width-4, offset+3, offset+3, rgb(178,178,178)); draw_line(ctx, 2, self->width-5, offset+4, offset+4, rgb(250,250,250)); } void _menu_focus_MenuEntry_Separator(struct MenuEntry * self, int focused) { if (focused) { if (self->_owner && self->_owner->child) { menu_definitely_close(self->_owner->child); self->_owner->child = NULL; } } } void _menu_activate_MenuEntry_Separator(struct MenuEntry * self, int focused) { } static struct MenuEntryVTable _menu_vtable_MenuEntry_Separator = { .methods = 3, .renderer = _menu_draw_MenuEntry_Separator, .focus_change = _menu_focus_MenuEntry_Separator, .activate = _menu_activate_MenuEntry_Separator, }; struct MenuEntry * menu_create_separator(void) { struct MenuEntry_Separator * out = calloc(1,sizeof(struct MenuEntry_Separator)); out->_type = MenuEntry_Separator; out->height = 6; out->hilight = 0; out->rwidth = 10; /* at least a bit please */ out->vtable = &_menu_vtable_MenuEntry_Separator; return (struct MenuEntry *)out; } void menu_update_title(struct MenuEntry * self, char * new_title) { if (self->_type == MenuEntry_Normal) { struct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self; if (_self->title) { free((void*)_self->title); } _self->title = strdup(new_title); _self->rwidth = 50 + string_width(_self->title); } else if (self->_type == MenuEntry_Submenu) { struct MenuEntry_Submenu * _self = (struct MenuEntry_Submenu *)self; if (_self->title) { free((void*)_self->title); } _self->title = strdup(new_title); _self->rwidth = 50 + string_width(_self->title); } } void menu_update_icon(struct MenuEntry * self, char * newIcon) { switch (self->_type) { case MenuEntry_Submenu: case MenuEntry_Normal: { struct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self; if (_self->icon) free(_self->icon); _self->icon = newIcon ? strdup(newIcon) : NULL; break; } case MenuEntry_Toggle: /* Toggle should not be able to set icon */ default: break; } } void menu_update_toggle_state(struct MenuEntry * self, int state) { switch (self->_type) { case MenuEntry_Toggle: { struct MenuEntry_Toggle * _self = (struct MenuEntry_Toggle *)self; _self->set = state; break; } default: break; } } void menu_update_enabled(struct MenuEntry * self, int state) { switch (self->_type) { case MenuEntry_Toggle: case MenuEntry_Submenu: case MenuEntry_Normal: { unsigned long flags = ((struct MenuEntry_Normal*)self)->flags & ~MENU_ENTRY_FLAGS_DISABLED; if (!state) flags |= MENU_ENTRY_FLAGS_DISABLED; ((struct MenuEntry_Normal*)self)->flags = flags; break; } default: break; } } void menu_free_entry(struct MenuEntry * self) { switch (self->_type) { case MenuEntry_Toggle: case MenuEntry_Submenu: case MenuEntry_Normal: { struct MenuEntry_Normal * _self = (struct MenuEntry_Normal *)self; if (_self->icon) free(_self->icon); if (_self->title) free(_self->title); if (_self->action) free(_self->action); break; } default: break; } free(self); } static int _close_enough(struct yutani_msg_window_mouse_event * me) { if (me->command == YUTANI_MOUSE_EVENT_RAISE && sqrt(pow(me->new_x - me->old_x, 2) + pow(me->new_y - me->old_y, 2)) < 10) { return 1; } return 0; } static char read_buf[1024]; static size_t available = 0; static size_t offset = 0; static size_t read_from = 0; static char * read_line(FILE * f, char * out, ssize_t len) { while (len > 0) { if (available == 0) { if (offset == 1024) { offset = 0; } size_t r = read(fileno(f), &read_buf[offset], 1024 - offset); read_from = offset; available = r; offset += available; } if (available == 0) { *out = '\0'; return out; } while (read_from < offset && len > 0) { *out = read_buf[read_from]; len--; read_from++; available--; if (*out == '\n') { return out; } out++; } } return out; } void menu_calculate_dimensions(struct MenuList * menu, int * height, int * width) { list_t * list = menu->entries; *width = 0; *height = (menu->flags & MENU_FLAG_BUBBLE) ? 16 : 8; /* TODO top and height */ foreach(node, list) { struct MenuEntry * entry = node->value; *height += entry->height; if (*width < entry->rwidth) { *width = entry->rwidth; } } /* Go back through and update actual widths */ foreach(node, list) { struct MenuEntry * entry = node->value; entry->width = *width; } } struct MenuList * menu_set_get_root(struct MenuSet * menu) { return (void*)hashmap_get(menu->_menus,"_"); } struct MenuList * menu_set_get_menu(struct MenuSet * menu, char * submenu) { return (void*)hashmap_get(menu->_menus, submenu); } void menu_insert(struct MenuList * menu, struct MenuEntry * entry) { list_insert(menu->entries, entry); entry->_owner = menu; } struct MenuList * menu_create(void) { struct MenuList * p = malloc(sizeof(struct MenuList)); p->entries = list_create(); p->ctx = NULL; p->window = NULL; p->set = NULL; p->child = NULL; p->_bar = NULL; p->parent = NULL; p->closed = 1; p->flags = 0; p->tail_offset = 0; p->main_window = 0; return p; } struct MenuSet * menu_set_create(void) { struct MenuSet * _out = malloc(sizeof(struct MenuSet)); _out->_menus = hashmap_create(10); return _out; } void menu_set_insert(struct MenuSet * set, char * action, struct MenuList * menu) { hashmap_set(set->_menus, action, menu); menu->set = set; } struct MenuSet * menu_set_from_description(const char * path, void (*callback)(struct MenuEntry *)) { FILE * f; if (!strcmp(path,"-")) { f = stdin; } else { f = fopen(path,"r"); } if (!f) { return NULL; } struct MenuSet * _out = malloc(sizeof(struct MenuSet)); hashmap_t * out = hashmap_create(10); _out->_menus = out; struct MenuList * current_menu = NULL; /* Read through the file */ char line[256]; while (1) { memset(line, 0, 256); read_line(f, line, 256); if (!*line) break; if (line[strlen(line)-1] == '\n') { line[strlen(line)-1] = '\0'; } if (!*line) continue; /* skip blank */ if (*line == ':') { /* New menu */ struct MenuList * p = menu_create(); p->set = _out; hashmap_set(out, line+1, p); current_menu = p; } else if (*line == '#') { /* Comment */ continue; } else if (*line == '-') { if (!current_menu) { fprintf(stderr, "Tried to add separator with no active menu.\n"); goto failure; } menu_insert(current_menu, menu_create_separator()); } else if (*line == '&') { if (!current_menu) { fprintf(stderr, "Tried to add submenu with no active menu.\n"); goto failure; } char * action = line+1; char * icon = strstr(action,","); if (!icon) { fprintf(stderr, "Malformed line in submenu: no icon\n"); goto failure; } *icon = '\0'; icon++; char * title = strstr(icon,","); if (!title) { fprintf(stderr, "Malformed line in submenu: no title\n"); goto failure; } *title = '\0'; title++; menu_insert(current_menu, menu_create_submenu(icon,action,title)); } else { if (!current_menu) { fprintf(stderr, "Tried to add item with no active menu.\n"); goto failure; } char * action = line; char * icon = strstr(action,","); if (!icon) { fprintf(stderr, "Malformed line in action: no icon\n"); goto failure; } *icon = '\0'; icon++; char * title = strstr(icon,","); if (!title) { fprintf(stderr, "Malformed line in action: no title\n"); goto failure; } *title = '\0'; title++; menu_insert(current_menu, menu_create_normal(icon,action,title,callback)); } } return _out; failure: fprintf(stderr, "malformed description file\n"); if (f != stdin) { fclose(f); } free(out); return NULL; } static void _menu_redraw(yutani_window_t * menu_window, yutani_t * yctx, struct MenuList * menu, int expose) { gfx_context_t * ctx = menu->ctx; list_t * entries = menu->entries; /* Window background */ if (menu->flags & MENU_FLAG_BUBBLE) { draw_fill(ctx, rgba(0,0,0,0)); draw_rounded_rectangle(ctx, 0, 6, ctx->width, ctx->height - 6, 6, rgb(109,111,112)); draw_rounded_rectangle(ctx, 1, 7, ctx->width-2, ctx->height - 8, 5, MENU_BACKGROUND); /* Figure out where to draw the tail */ int tail_left = 0; /* Do we have a set tail? */ #define TAIL_BOUND 8 if (menu->flags & MENU_FLAG_TAIL_POSITION) { tail_left = (menu->tail_offset < TAIL_BOUND) ? TAIL_BOUND : (menu->tail_offset > ctx->width - TAIL_BOUND) ? (ctx->width - TAIL_BOUND) : menu->tail_offset; } else if (menu->flags & MENU_FLAG_BUBBLE_LEFT) { tail_left = 16; } else if (menu->flags & MENU_FLAG_BUBBLE_RIGHT) { tail_left = ctx->width - 16; } else if (menu->flags & MENU_FLAG_BUBBLE_CENTER) { tail_left = ctx->width / 2; } for (int i = 1; i < 7; ++i) { draw_line(ctx, tail_left - i, tail_left + i, i, i, MENU_BACKGROUND); } draw_line_aa(ctx, tail_left - 6, tail_left, 6, 0, rgb(109,111,112), 0.5); draw_line_aa(ctx, tail_left + 6, tail_left, 6, 0, rgb(109,111,112), 0.5); } else { draw_fill(ctx, MENU_BACKGROUND); /* Window border */ draw_line(ctx, 0, ctx->width-1, 0, 0, rgb(109,111,112)); draw_line(ctx, 0, 0, 0, ctx->height-1, rgb(109,111,112)); draw_line(ctx, ctx->width-1, ctx->width-1, 0, ctx->height-1, rgb(109,111,112)); draw_line(ctx, 0, ctx->width-1, ctx->height-1, ctx->height-1, rgb(109,111,112)); } /* Draw menu entries */ int offset = (menu->flags & MENU_FLAG_BUBBLE) ? 12 : 4; foreach(node, entries) { struct MenuEntry * entry = node->value; if (entry->vtable->methods >= 1 && entry->vtable->renderer) { entry->vtable->renderer(ctx, entry, offset); } offset += entry->height; } flip(ctx); if (expose) { yutani_flip(yctx, menu_window); } } void menu_prepare(struct MenuList * menu, yutani_t * yctx) { /* Calculate window dimensions */ int height, width; menu_calculate_dimensions(menu,&height, &width); my_yctx = yctx; menu->closed = 0; /* Create window */ yutani_window_t * menu_window = yutani_window_create_flags(yctx, width, height, ((menu->flags & MENU_FLAG_BUBBLE) ? YUTANI_WINDOW_FLAG_ALT_ANIMATION : YUTANI_WINDOW_FLAG_NO_ANIMATION) | YUTANI_WINDOW_FLAG_DISALLOW_DRAG | YUTANI_WINDOW_FLAG_DISALLOW_RESIZE | YUTANI_WINDOW_FLAG_PARENT_WID, menu->main_window); yutani_set_stack(yctx, menu_window, YUTANI_ZORDER_MENU); if (menu->ctx) { reinit_graphics_yutani(menu->ctx, menu_window); } else { menu->ctx = init_graphics_yutani_double_buffer(menu_window); } menu_window->user_data = menu; menu->window = menu_window; _menu_redraw(menu_window, yctx, menu, 0); hashmap_set(menu_windows, (void*)(uintptr_t)menu_window->wid, menu_window); } void menu_show_at(struct MenuList * menu, yutani_window_t * parent, int x, int y) { menu->main_window = parent; menu_prepare(menu, parent->ctx); if (parent->x + x + menu->window->width > parent->ctx->display_width) x -= menu->window->width; if (parent->y + y + menu->window->height > parent->ctx->display_height) y -= menu->window->height; yutani_window_move_relative(parent->ctx, menu->window, parent, x, y); yutani_flip(parent->ctx, menu->window); } int menu_has_eventual_child(struct MenuList * root, struct MenuList * child) { if (!child) return 0; if (root == child) return 1; struct MenuList * candidate = root->child; while (candidate && candidate != child) { if (candidate == root->child) { return 1; } candidate = root->child; } return (candidate == child); } int menu_definitely_close(struct MenuList * menu) { if (menu->child) { menu_definitely_close(menu->child); menu->child = NULL; } if (menu->closed) { return 0; } /* if focused_widget, leave focus on widget */ foreach(node, menu->entries) { struct MenuEntry * entry = node->value; entry->hilight = 0; } menu->closed = 1; yutani_wid_t wid = menu->window->wid; yutani_close(menu->window->ctx, menu->window); menu->window = NULL; menu->main_window = NULL; hashmap_remove(menu_windows, (void*)(uintptr_t)wid); return 0; } int menu_leave(struct MenuList * menu) { if (!hovered_menu) { while (menu->parent) { menu = menu->parent; } menu_definitely_close(menu); return 0; } if (!menu_has_eventual_child(menu, hovered_menu)) { /* Get all menus */ list_t * menu_keys = hashmap_keys(menu_windows); foreach(_key, menu_keys) { yutani_window_t * window = hashmap_get(menu_windows, (void *)(uintptr_t)_key->value); if (window) { struct MenuList * menu = window->user_data; if (!hovered_menu || (menu != hovered_menu->child && !menu_has_eventual_child(menu, hovered_menu))) { menu_definitely_close(menu); if (menu->parent && menu->parent->child == menu) { menu->parent->child = NULL; } } } } list_free(menu_keys); free(menu_keys); } return 0; } static int _menu_is_disabled(struct MenuEntry * entry) { switch (entry->_type) { case MenuEntry_Toggle: case MenuEntry_Submenu: case MenuEntry_Normal: return ((struct MenuEntry_Normal*)entry)->flags & MENU_ENTRY_FLAGS_DISABLED; case MenuEntry_Separator: return 1; default: return 0; } } void menu_key_action(struct MenuList * menu, struct yutani_msg_key_event * me) { if (me->event.action != KEY_ACTION_DOWN) return; yutani_window_t * window = menu->window; yutani_t * yctx = window->ctx; hovered_menu = menu; /* Find hilighted entry */ struct MenuEntry * hilighted = NULL; struct MenuEntry * previous = NULL; struct MenuEntry * next = NULL; struct MenuEntry * first = NULL; struct MenuEntry * last = NULL; foreach(node, menu->entries) { struct MenuEntry * entry = node->value; if (!first && !_menu_is_disabled(entry)) { first = entry; } if (!hilighted && entry->hilight) { hilighted = entry; continue; } if (!_menu_is_disabled(entry)) { if (!next && hilighted) next = entry; if (!hilighted) previous = entry; last = entry; } } if (me->event.keycode == KEY_ARROW_DOWN) { if (hilighted) { hilighted->hilight = 0; } hilighted = next ? next : first; if (hilighted) hilighted->hilight = 1; _menu_redraw(window,yctx,menu,1); } else if (me->event.keycode == KEY_ARROW_UP) { if (hilighted) { hilighted->hilight = 0; } hilighted = previous ? previous : last; if (hilighted) hilighted->hilight = 1; _menu_redraw(window,yctx,menu,1); } else if (me->event.keycode == KEY_ARROW_RIGHT) { if (!hilighted) { hilighted = menu->entries->head->value; } if (hilighted) { hilighted->hilight = 1; if (hilighted->_type == MenuEntry_Submenu) { if (hilighted->vtable->methods >= 3 && hilighted->vtable->activate) { hilighted->vtable->activate(hilighted, 0); } _menu_redraw(window,yctx,menu,1); } else { struct menu_bar * bar = NULL; struct MenuList * p = menu; do { if (p->_bar) { bar = p->_bar; break; } } while ((p = p->parent)); if (bar) { menu_definitely_close(p); int active = (bar->active_entry_idx + 1 + bar->num_entries) % (bar->num_entries); bar->active_entry = &bar->entries[active]; if (bar->redraw_callback) { bar->redraw_callback(bar); } menu_bar_show_menu(yctx, bar->window, bar, -1, bar->active_entry); } else { _menu_redraw(window,yctx,menu,1); } } } } else if (me->event.key == '\n') { if (!hilighted) { hilighted = menu->entries->head->value; } if (hilighted) { hilighted->hilight = 1; if (hilighted->vtable->methods >= 3 && hilighted->vtable->activate) { hilighted->vtable->activate(hilighted, 0); } } } else if (me->event.keycode == KEY_ARROW_LEFT) { if (menu->parent) { hovered_menu = menu->parent; } /* else previous from menu bar? */ menu_definitely_close(menu); if (menu->_bar) { int active = (menu->_bar->active_entry_idx - 1 + menu->_bar->num_entries) % (menu->_bar->num_entries); menu->_bar->active_entry = &menu->_bar->entries[active]; if (menu->_bar->redraw_callback) { menu->_bar->redraw_callback(menu->_bar); } menu_bar_show_menu(yctx, menu->_bar->window, menu->_bar, -1, menu->_bar->active_entry); } else if (menu->parent && menu->parent->window) { yutani_focus_window(yctx, menu->parent->window->wid); } } else if (me->event.keycode == KEY_ESCAPE) { hovered_menu = NULL; menu_leave(menu); } } void menu_mouse_action(struct MenuList * menu, struct yutani_msg_window_mouse_event * me) { yutani_window_t * window = menu->window; yutani_t * yctx = window->ctx; int offset = (menu->flags & MENU_FLAG_BUBBLE) ? 12 : 4; int changed = 0; foreach(node, menu->entries) { struct MenuEntry * entry = node->value; if (me->new_y >= offset && me->new_y < offset + entry->height && me->new_x >= 0 && me->new_x < entry->width) { if (!entry->hilight) { changed = 1; entry->hilight = 1; if (entry->vtable->methods >= 2 && entry->vtable->focus_change) { entry->vtable->focus_change(entry, 1); } } if (entry->vtable->methods >= 4 && entry->vtable->mouse_event) { if (entry->vtable->mouse_event(entry, me)) { _menu_redraw(window,yctx,menu,1); } } else if (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) { if (entry->vtable->methods >= 3 && entry->vtable->activate) { entry->vtable->activate(entry, 0); } } } else { if (entry->hilight) { changed = 1; entry->hilight = 0; if (entry->vtable->methods >= 2 && entry->vtable->focus_change) { entry->vtable->focus_change(entry, 0); } } } offset += entry->height; } if (changed) { _menu_redraw(window,yctx,menu,1); } } void menu_force_redraw(struct MenuList * menu) { yutani_window_t * window = menu->window; yutani_t * yctx = window->ctx; _menu_redraw(window,yctx,menu,1); } struct MenuList * menu_any_contains(int x, int y) { struct MenuList * out = NULL; list_t * menu_keys = hashmap_keys(menu_windows); foreach(_key, menu_keys) { yutani_window_t * window = hashmap_get(menu_windows, (void*)_key->value); if (window) { if (x >= (int)window->x && x < (int)window->x + (int)window->width && y >= (int)window->y && y < (int)window->y + (int)window->height) { out = window->user_data; break; } } } list_free(menu_keys); free(menu_keys); return out; } int menu_process_event(yutani_t * yctx, yutani_msg_t * m) { if (m) { switch (m->type) { case YUTANI_MSG_KEY_EVENT: { struct yutani_msg_key_event * me = (void*)m->data; if (hashmap_has(menu_windows, (void*)(uintptr_t)me->wid)) { yutani_window_t * window = hashmap_get(menu_windows, (void *)(uintptr_t)me->wid); struct MenuList * menu = window->user_data; menu_key_action(menu, me); } } break; case YUTANI_MSG_WINDOW_MOUSE_EVENT: { struct yutani_msg_window_mouse_event * me = (void*)m->data; if (hashmap_has(menu_windows, (void*)(uintptr_t)me->wid)) { yutani_window_t * window = hashmap_get(menu_windows, (void *)(uintptr_t)me->wid); struct MenuList * menu = window->user_data; if (me->new_x >= 0 && me->new_x < (int)window->width && me->new_y >= 0 && me->new_y < (int)window->height) { if (hovered_menu != menu) { hovered_menu = menu; } } else { if (hovered_menu) { struct MenuList * t = menu_any_contains(me->new_x + window->x, me->new_y + window->y); if (t) { hovered_menu = t; } else { hovered_menu = NULL; } } } menu_mouse_action(menu, me); } } break; case YUTANI_MSG_WINDOW_FOCUS_CHANGE: { struct yutani_msg_window_focus_change * me = (void*)m->data; if (hashmap_has(menu_windows, (void*)(uintptr_t)me->wid)) { yutani_window_t * window = hashmap_get(menu_windows, (void *)(uintptr_t)me->wid); struct MenuList * menu = window->user_data; if (!me->focused) { /* XXX leave menu */ menu_leave(menu); /* if root and not window.root.menus and window.root.focused */ return 1; } else { window->focused = me->focused; /* Redraw? */ } } } break; default: break; } } return 0; } void menu_bar_render(struct menu_bar * self, gfx_context_t * ctx) { int _x = self->x; int _y = self->y; int width = self->width; int height = MENU_BAR_HEIGHT; if (_x < 0) _x = 0; if (_y < 0) _y = 0; if (_x + width > ctx->width) width = ctx->width - _x; if (_y + height > ctx->height) height = ctx->height - _y; gfx_context_t * subctx = init_graphics_subregion(ctx, _x, _y, width, height); uint32_t menu_bar_color = rgb(59,59,59); draw_rectangle(subctx, 0, 0, width, height, menu_bar_color); /* for each menu entry */ int offset = 0; struct menu_bar_entries * _entries = self->entries; if (!self->num_entries) { while (_entries->title) { _entries++; self->num_entries++; } _entries = self->entries; } while (_entries->title) { int w = string_width(_entries->title) + 11; if ((self->active_menu && hashmap_has(menu_get_windows_hash(), (void*)(uintptr_t)self->active_menu_wid)) && _entries == self->active_entry) { draw_rectangle(subctx, offset + 2, 0, w, height, rgb(93,163,236)); } draw_string(subctx, offset + 7, 2, 0xFFFFFFFF, _entries->title); offset += w; _entries++; } free(subctx); } void menu_bar_show_menu(yutani_t * yctx, yutani_window_t * window, struct menu_bar * self, int offset, struct menu_bar_entries * _entries) { struct MenuList * new_menu = menu_set_get_menu(self->set, _entries->action); int i = 0; if (offset == -1) { /* Must calculate */ offset = self->x; struct menu_bar_entries * e = self->entries; while (e->title) { if (e == _entries) break; offset += string_width(e->title) + 10; e++; i++; } } else { struct menu_bar_entries * e = self->entries; while (e->title) { if (e == _entries) break; e++; i++; } } new_menu->main_window = window; menu_prepare(new_menu, yctx); yutani_window_move_relative(yctx, new_menu->window, window, offset, self->y + MENU_BAR_HEIGHT); yutani_flip(yctx, new_menu->window); self->active_menu = new_menu; self->active_menu->_bar = self; self->active_menu_wid = new_menu->window->wid; self->active_entry = _entries; self->active_entry_idx = i; if (self->redraw_callback) { self->redraw_callback(self); } } 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) { if (x < self->x || x >= self->x + self->width || y < self->y || y >= self->y + 24 /* base height */) { return 0; } int offset = self->x; struct menu_bar_entries * _entries = self->entries; while (_entries->title) { int w = string_width(_entries->title) + 11; if (x >= offset && x < offset + w) { if (me->command == YUTANI_MOUSE_EVENT_CLICK || _close_enough(me)) { menu_bar_show_menu(yctx, window, self,offset,_entries); } else if (self->active_menu && hashmap_has(menu_get_windows_hash(), (void*)(uintptr_t)self->active_menu_wid) && _entries != self->active_entry) { menu_definitely_close(self->active_menu); menu_bar_show_menu(yctx, window, self,offset,_entries); } } offset += w; _entries++; } if (x >= offset && me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { yutani_window_drag_start(yctx, window); } return 0; } ================================================ FILE: lib/panel_appmenu.c ================================================ /** * @brief Panel "Applications" menu widget */ #include #include #include #include #include #include #include static struct MenuList * appmenu; static int widget_draw_appmenu(struct PanelWidget * this, gfx_context_t * ctx) { panel_highlight_widget(this,ctx, !!appmenu->window); tt_set_size(this->pctx->font, 16); int w = tt_string_width(this->pctx->font, "Applications"); tt_draw_string(ctx, this->pctx->font, (ctx->width - w) / 2, 20, "Applications", appmenu->window ? this->pctx->color_text_hilighted : this->pctx->color_text_normal); return 0; } static int widget_click_appmenu(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { if (!appmenu->window) { panel_menu_show(this,appmenu); return 1; } return 0; } static int widget_onkey_appmenu(struct PanelWidget * this, struct yutani_msg_key_event * ke) { if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == KEY_F1) && (ke->event.action == KEY_ACTION_DOWN)) { panel_menu_show(this,appmenu); } return 0; } struct PanelWidget * widget_init_appmenu(void) { appmenu = menu_set_get_root(menu_set_from_description("/etc/panel.menu", launch_application_menu)); appmenu->flags = MENU_FLAG_BUBBLE_CENTER; /* Bind Alt+F1 */ yutani_key_bind(yctx, KEY_F1, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); struct PanelWidget * widget = widget_new(); widget->width = 130; widget->draw = widget_draw_appmenu; widget->click = widget_click_appmenu; widget->onkey = widget_onkey_appmenu; list_insert(widgets_enabled, widget); return widget; } ================================================ FILE: lib/panel_clock.c ================================================ /** * @brief Panel clock widget */ #include #include #include #include #include #include #include #include static struct MenuList * clockmenu; static void watch_draw_line(gfx_context_t * ctx, int offset, double r, double ir, double a, double b, uint32_t color, float thickness) { double theta = (a / b) * 2.0 * M_PI; struct gfx_point v = {74.0 + sin(theta) * ir, 70.0 + offset - cos(theta) * ir}; struct gfx_point w = {74.0 + sin(theta) * r, 70.0 + offset - cos(theta) * r}; draw_line_aa_points(ctx,&v,&w,color,thickness); } static double tick(double t) { double ts = t*t; double tc = ts*t; return (0.5*tc*ts + -8.0*ts*ts + 20.0*tc + -19.0*ts + 7.5*t); } void _menu_draw_MenuEntry_Clock(gfx_context_t * ctx, struct MenuEntry * self, int offset) { self->offset = offset; /* Draw the background */ draw_rounded_rectangle(ctx, 4, offset, 140, 140, 70, rgb(0,0,0)); draw_rounded_rectangle(ctx, 6, offset + 2, 136, 136, 68, rgb(255,255,255)); for (int i = 0; i < 60; ++i) { watch_draw_line(ctx, offset, 68, i % 5 ? 65 : 60, i, 60, rgb(0,0,0), i % 5 ? 0.3 : 1.0); } static const char * digits[] = {"12","1","2","3","4","5","6","7","8","9","10","11"}; struct TT_Font * font = ((struct PanelWidget*)self->_private)->pctx->font; tt_set_size(font, 12); for (int i = 0; i < 12; ++i) { int w = tt_string_width(font, digits[i]); double theta = (i / 12.0) * 2.0 * M_PI; double x = 74.0 + sin(theta) * 50.0; double y = 70.0 + offset - cos(theta) * 50.0; int _x = x - w / 2; int _y = y + 6; tt_draw_string(ctx, font, _x, _y, digits[i], rgb(0,0,0)); } struct timeval now; struct tm * timeinfo; gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); double sec = timeinfo->tm_sec + tick((double)now.tv_usec / 1000000.0) - 1.0; double min = timeinfo->tm_min + sec / 60.0; double hour = (timeinfo->tm_hour % 12) + min / 60.0; watch_draw_line(ctx, offset, 40, 0, hour, 12, rgb(0,0,0), 2.0); watch_draw_line(ctx, offset, 60, 0, min, 60, rgb(0,0,0), 1.5); watch_draw_line(ctx, offset, 65, -12, sec, 60, rgb(240,0,0), 0.5); watch_draw_line(ctx, offset, -4, -8, sec, 60, rgb(240,0,0), 2.0); } static struct MenuEntryVTable clock_vtable = { .methods = 3, .renderer = _menu_draw_MenuEntry_Clock, }; struct MenuEntry * menu_create_clock(struct PanelWidget * this) { struct MenuEntry * out = menu_create_separator(); /* Steal some defaults */ out->_type = -1; /* Special */ out->height = 140; out->rwidth = 148; out->vtable = &clock_vtable; out->_private = this; return out; } static int widget_draw_clock(struct PanelWidget * this, gfx_context_t * ctx) { struct timeval now; struct tm * timeinfo; panel_highlight_widget(this,ctx,!!clockmenu->window); struct TT_Font * font = this->pctx->font; /* Get the current time for the clock */ gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); /* Hours : Minutes : Seconds */ char time[80]; strftime(time, 80, "%H:%M:%S", timeinfo); tt_set_size(font, 16); int w = tt_string_width(font, time); tt_draw_string(ctx, font, (ctx->width - w) / 2, 20, time, clockmenu->window ? this->pctx->color_text_hilighted : this->pctx->color_text_normal); return 0; } static int widget_click_clock(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { if (!clockmenu->window) { panel_menu_show(this,clockmenu); return 1; } return 0; } static int widget_update_clock(struct PanelWidget * this, int * force_updates) { if (clockmenu && clockmenu->window) { menu_force_redraw(clockmenu); *force_updates = 1; } return 0; } struct PanelWidget * widget_init_clock(void) { struct PanelWidget * widget = widget_new(); clockmenu = menu_create(); clockmenu->flags |= MENU_FLAG_BUBBLE_RIGHT; menu_insert(clockmenu, menu_create_clock(widget)); widget->width = 90; /* TODO what */ widget->draw = widget_draw_clock; widget->click = widget_click_clock; widget->update = widget_update_clock; list_insert(widgets_enabled, widget); return widget; } ================================================ FILE: lib/panel_date.c ================================================ /** * @brief Panel date widget */ #include #include #include #include #include #include #include static struct MenuList * calmenu; static int date_widget_width = 48; #define CALENDAR_LINE_HEIGHT 22 #define CALENDAR_BASE_HEIGHT 45 #define CALENDAR_PAD_HEIGHT 2 static int days_in_month(struct tm * timeinfo) { static int days_in_months[] = { 31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, }; if (timeinfo->tm_mon != 1) return days_in_months[timeinfo->tm_mon]; /* How many days in February? */ struct tm tmp; memcpy(&tmp, timeinfo, sizeof(struct tm)); tmp.tm_mday = 29; tmp.tm_hour = 12; time_t tmp3 = mktime(&tmp); struct tm * tmp2 = localtime(&tmp3); return tmp2->tm_mday == 29 ? 29 : 28; } static int weeks_in_month(struct tm * timeinfo) { int line = 0; int wday = (36 + timeinfo->tm_wday - timeinfo->tm_mday) % 7; for (int day = 1; day <= days_in_month(timeinfo); day++, (wday = (wday + 1) % 7)) { if (wday == 6) { line++; } } return (wday ? line + 1 : line); } void _menu_draw_MenuEntry_Calendar(gfx_context_t * ctx, struct MenuEntry * self, int offset) { self->offset = offset; struct timeval now; gettimeofday(&now, NULL); struct tm actual; struct tm * timeinfo; timeinfo = localtime((time_t *)&now.tv_sec); memcpy(&actual, timeinfo, sizeof(struct tm)); timeinfo = &actual; struct TT_Font * font = ((struct PanelWidget*)self->_private)->pctx->font; struct TT_Font * font_bold = ((struct PanelWidget*)self->_private)->pctx->font_bold; /* Render heading with Month Year */ { char month[20]; strftime(month, 20, "%B %Y", timeinfo); tt_set_size(font_bold, 16); tt_draw_string(ctx, font_bold, (self->width - tt_string_width(font_bold, month)) / 2, self->offset + 16, month, rgb(0,0,0)); } /* Get ready to draw a table... */ int cell_size = self->width / 7; int base_left = (self->width - cell_size * 7) / 2; /* Render weekday abbreviations */ const char * weekdays[] = {"Su","Mo","Tu","We","Th","Fr","Sa",NULL}; int left = base_left; tt_set_size(font, 11); for (const char ** w = weekdays; *w; w++) { tt_draw_string(ctx, font, left + (cell_size - tt_string_width(font,*w)) / 2, self->offset + 22 + 13, *w, rgb(0,0,0)); left += cell_size; } int weeks = weeks_in_month(timeinfo); self->height = CALENDAR_LINE_HEIGHT * weeks + CALENDAR_BASE_HEIGHT + CALENDAR_PAD_HEIGHT; /* The 1st was a... */ int wday = (36 + timeinfo->tm_wday - timeinfo->tm_mday) % 7; int line = 0; left = base_left + cell_size * wday; tt_set_size(font, 13); for (int day = 1; day <= days_in_month(timeinfo); day++, (wday = (wday + 1) % 7)) { char date[12]; snprintf(date, 11, "%d", day); /* Is this the cell for today? */ if (day == timeinfo->tm_mday) { draw_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); tt_draw_string(ctx, font, left + (cell_size - tt_string_width(font, date)) / 2, self->offset + CALENDAR_BASE_HEIGHT + 13 + line * CALENDAR_LINE_HEIGHT, date, rgb(255,255,255)); } else { tt_draw_string(ctx, font, left + (cell_size - tt_string_width(font, date)) / 2, self->offset + CALENDAR_BASE_HEIGHT + 13 + line * CALENDAR_LINE_HEIGHT, date, (wday == 0 || wday == 6) ? rgba(0,0,0,120) : rgb(0,0,0)); } if (wday == 6) { left = base_left; line++; } else { left += cell_size; } } } static struct MenuEntryVTable calendar_vtable = { .methods = 3, .renderer = _menu_draw_MenuEntry_Calendar, }; /* * Special menu entry to display a calendar */ struct MenuEntry * menu_create_calendar(struct PanelWidget * this) { struct MenuEntry * out = menu_create_separator(); /* Steal some defaults */ out->_type = -1; /* Special */ struct timeval now; gettimeofday(&now, NULL); out->height = CALENDAR_LINE_HEIGHT * weeks_in_month(localtime((time_t *)&now.tv_sec)) + CALENDAR_BASE_HEIGHT + CALENDAR_PAD_HEIGHT; out->rwidth = 200; out->vtable = &calendar_vtable; out->_private = this; return out; } static int weekday_width, date_width; static char weekday[80], date[80]; static void update_date_widget(struct PanelWidget * this) { struct timeval now; struct tm * timeinfo; struct TT_Font * font = this->pctx->font; struct TT_Font * font_bold = this->pctx->font_bold; /* Get the current time for the clock */ gettimeofday(&now, NULL); timeinfo = localtime((time_t *)&now.tv_sec); strftime(weekday, 80, "%A", timeinfo); strftime(date, 80, "%B %e", timeinfo); tt_set_size(font, 11); tt_set_size(font_bold, 11); /* Update date_widget_width */ weekday_width = tt_string_width(font, weekday); date_width = tt_string_width(font_bold, date); /* Uh, we need to calculate this elsewhere */ date_widget_width = (weekday_width > date_width ? weekday_width : date_width) + 24; /* A bit of padding... */ } static int widget_draw_date(struct PanelWidget * this, gfx_context_t * ctx) { update_date_widget(this); panel_highlight_widget(this,ctx,!!calmenu->window); struct TT_Font * font = this->pctx->font; struct TT_Font * font_bold = this->pctx->font_bold; /* Day-of-week */ int t = (this->width - weekday_width) / 2; tt_draw_string(ctx, font, t, 13, weekday, calmenu->window ? this->pctx->color_text_hilighted : this->pctx->color_text_normal); /* Month Day */ t = (this->width - date_width) / 2; tt_draw_string(ctx, font_bold, t, 23, date, calmenu->window ? this->pctx->color_text_hilighted : this->pctx->color_text_normal); return 0; } static int widget_click_date(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { if (!calmenu->window) { panel_menu_show(this,calmenu); return 1; } return 0; } static int widget_update_date(struct PanelWidget * this, int * force_updates) { int width_before = date_widget_width; update_date_widget(this); this->width = date_widget_width; return width_before != date_widget_width; } struct PanelWidget * widget_init_date(void) { struct PanelWidget * widget = widget_new(); calmenu = menu_create(); calmenu->flags |= MENU_FLAG_BUBBLE_CENTER; menu_insert(calmenu, menu_create_calendar(widget)); widget->width = 92; /* TODO calculate correct width */ widget->draw = widget_draw_date; widget->click = widget_click_date; widget->update = widget_update_date; list_insert(widgets_enabled, widget); return widget; } ================================================ FILE: lib/panel_logout.c ================================================ /** * @brief Panel Logout Widget * * Shows a button that presents a menu with the option to log out. */ #include #include #include #include #include static struct MenuList * logout_menu; static sprite_t * sprite_logout; static int widget_draw_logout(struct PanelWidget * this, gfx_context_t * ctx) { panel_highlight_widget(this,ctx,!!logout_menu->window); draw_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 */ return 0; } static int widget_click_logout(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { if (!logout_menu->window) { panel_menu_show(this,logout_menu); return 1; } return 0; } struct PanelWidget * widget_init_logout(void) { sprite_logout = malloc(sizeof(sprite_t)); load_sprite(sprite_logout, "/usr/share/icons/panel-shutdown.png"); logout_menu = menu_create(); logout_menu->flags |= MENU_FLAG_BUBBLE_RIGHT; menu_insert(logout_menu, menu_create_normal("exit", "log-out", "Log Out", launch_application_menu)); struct PanelWidget * widget = widget_new(); widget->width = sprite_logout->width + widget->pctx->extra_widget_spacing; widget->draw = widget_draw_logout; widget->click = widget_click_logout; list_insert(widgets_enabled, widget); return widget; } ================================================ FILE: lib/panel_network.c ================================================ /** * @brief Panel Network Status Widget */ #include #include #include #include #include #include #include #include #include #include #include #include #include static int netstat_count = 0; static int netstat_prev[32] = {0}; static char netstat_data[32][1024]; static struct MenuList * netstat; static int network_status = 0; static sprite_t * sprite_net_active; static sprite_t * sprite_net_disabled; static void ip_ntoa(const uint32_t src_addr, char * out) { snprintf(out, 16, "%d.%d.%d.%d", (src_addr & 0xFF000000) >> 24, (src_addr & 0xFF0000) >> 16, (src_addr & 0xFF00) >> 8, (src_addr & 0xFF)); } static void netif_show_toast(const char * str) { int toastDaemon = open("/dev/pex/toast", O_WRONLY); if (toastDaemon >= 0) { write(toastDaemon, str, strlen(str)); close(toastDaemon); } } static void netif_disconnected(const char * if_name) { network_status |= 1; if (netstat_prev[netstat_count] && netstat_prev[netstat_count] != 1) { char toastMsg[1024]; sprintf(toastMsg, "{\"icon\":\"/usr/share/icons/48/network-jack-disconnected.png\",\"body\":\"%s
Network disconnected.\"}", if_name); netif_show_toast(toastMsg); } netstat_prev[netstat_count] = 1; } static void netif_connected(const char * if_name) { network_status |= 2; if (netstat_prev[netstat_count] && netstat_prev[netstat_count] != 2) { char toastMsg[1024]; sprintf(toastMsg, "{\"icon\":\"/usr/share/icons/48/network-jack.png\",\"body\":\"%s
Connection established.\"}", if_name); netif_show_toast(toastMsg); } netstat_prev[netstat_count] = 2; } static void check_network(const char * if_name) { if (netstat_count >= 32) return; char if_path[512]; snprintf(if_path, 511, "/dev/net/%s", if_name); int netdev = open(if_path, O_RDONLY); if (netdev < 0) return; uint32_t flags; if (!ioctl(netdev, SIOCGIFFLAGS, &flags)) { if (!(flags & IFF_UP)) { snprintf(netstat_data[netstat_count], 1023, "%s: disconnected", if_name); netif_disconnected(if_name); goto _netif_next; } } /* Get IPv4 address */ uint32_t ip_addr; if (!ioctl(netdev, SIOCGIFADDR, &ip_addr)) { char ip_str[16]; ip_ntoa(ntohl(ip_addr), ip_str); snprintf(netstat_data[netstat_count], 1023, "%s: %s", if_name, ip_str); netif_connected(if_name); } else { snprintf(netstat_data[netstat_count], 1023, "%s: No address", if_name); netif_disconnected(if_name); } _netif_next: close(netdev); netstat_count++; } static int widget_update_network(struct PanelWidget * this, int * force_updates) { network_status = 0; netstat_count = 0; DIR * d = opendir("/dev/net"); if (!d) return 1; struct dirent * ent; while ((ent = readdir(d))) { if (ent->d_name[0] == '.') continue; if (!strcmp(ent->d_name, "lo")) continue; /* Ignore loopback */ check_network(ent->d_name); } closedir(d); return 0; } static int widget_click_network(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { if (!netstat) { netstat = menu_create(); netstat->flags |= MENU_FLAG_BUBBLE_LEFT; menu_insert(netstat, menu_create_normal(NULL, NULL, "Network Status", NULL)); menu_insert(netstat, menu_create_separator()); } while (netstat->entries->length > 2) { node_t * node = list_pop(netstat->entries); menu_free_entry((struct MenuEntry *)node->value); free(node); } if (!network_status) { menu_insert(netstat, menu_create_normal(NULL, NULL, "No network.", NULL)); } else { for (int i = 0; i < netstat_count; ++i) { menu_insert(netstat, menu_create_normal(NULL, NULL, netstat_data[i], NULL)); } } if (!netstat->window) { panel_menu_show(this,netstat); } return 1; } static int widget_draw_network(struct PanelWidget * this, gfx_context_t * ctx) { uint32_t color = (netstat && netstat->window) ? this->pctx->color_text_hilighted : this->pctx->color_icon_normal; panel_highlight_widget(this,ctx,(netstat && netstat->window)); if (network_status & 2) { draw_sprite_alpha_paint(ctx, sprite_net_active, (ctx->width - sprite_net_active->width) / 2, 1, 1.0, color); } else { draw_sprite_alpha_paint(ctx, sprite_net_disabled, (ctx->width - sprite_net_disabled->width) / 2, 1, 1.0, color); } return 0; } struct PanelWidget * widget_init_network(void) { sprite_net_active = malloc(sizeof(sprite_t)); load_sprite(sprite_net_active, "/usr/share/icons/24/net-active.png"); sprite_net_disabled = malloc(sizeof(sprite_t)); load_sprite(sprite_net_disabled, "/usr/share/icons/24/net-disconnected.png"); struct PanelWidget * widget = widget_new(); widget->width = sprite_net_active->width + widget->pctx->extra_widget_spacing; widget->draw = widget_draw_network; widget->click = widget_click_network; widget->update = widget_update_network; list_insert(widgets_enabled, widget); return widget; } ================================================ FILE: lib/panel_volume.c ================================================ /** * @brief Panel Volume Widget * * Shows an icon indicating the mixer's master volume, * and shows a menu with a volume slider when clicked. */ #include #include #include #include #include #include #include #include #define VOLUME_DEVICE_ID 0 #define VOLUME_KNOB_ID 0 static sprite_t * sprite_volume_mute; static sprite_t * sprite_volume_low; static sprite_t * sprite_volume_med; static sprite_t * sprite_volume_high; static struct MenuList * volume_menu; static long volume_level = 0; static int mixer = -1; static int widget_update_volume(struct PanelWidget * this, int * force_updates) { if (mixer == -1) { mixer = open("/dev/mixer", O_RDONLY); } snd_knob_value_t value = {0}; value.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */ value.id = VOLUME_KNOB_ID; /* TODO this too */ ioctl(mixer, SND_MIXER_READ_KNOB, &value); volume_level = value.val; return 0; } static void set_volume(void) { snd_knob_value_t value = {0}; value.device = VOLUME_DEVICE_ID; /* TODO configure this somewhere */ value.id = VOLUME_KNOB_ID; /* TODO this too */ value.val = volume_level; ioctl(mixer, SND_MIXER_WRITE_KNOB, &value); redraw(); } static void volume_raise(void) { volume_level += 0x10000000; if (volume_level > 0xF0000000) volume_level = 0xFC000000; set_volume(); } static void volume_lower(void) { volume_level -= 0x10000000; if (volume_level < 0x0) volume_level = 0x0; set_volume(); } #define VOLUME_SLIDER_LEFT_PAD 38 #define VOLUME_SLIDER_RIGHT_PAD 14 #define VOLUME_SLIDER_PAD (VOLUME_SLIDER_LEFT_PAD + VOLUME_SLIDER_RIGHT_PAD) #define VOLUME_SLIDER_VERT_PAD 10 #define VOLUME_SLIDER_BALL_RADIUS 8 struct SliderStuff { int level; uint32_t on; uint32_t off; }; uint32_t volume_pattern(int32_t x, int32_t y, double alpha, void * extra) { struct SliderStuff * stuff = extra; if (alpha > 1.0) alpha = 1.0; if (alpha < 0.0) alpha = 0.0; uint32_t color = stuff->off; if (x < stuff->level + VOLUME_SLIDER_LEFT_PAD) { color = stuff->on; } color |= rgba(0,0,0,alpha*255); return premultiply(color); } void _menu_draw_MenuEntry_Slider(gfx_context_t * ctx, struct MenuEntry * self, int offset) { self->offset = offset; draw_sprite_alpha_paint(ctx, sprite_volume_high, 4, offset, 1.0, rgb(0,0,0)); struct SliderStuff stuff; stuff.level = (ctx->width - VOLUME_SLIDER_PAD) * (float)volume_level / (float)0xFC000000; stuff.on = rgba(0,120,220,0); stuff.off = rgba(140,140,140,0); draw_rounded_rectangle_pattern(ctx, /* x */ VOLUME_SLIDER_LEFT_PAD - 4, /* y */ offset + VOLUME_SLIDER_VERT_PAD - 1, /* w */ ctx->width - VOLUME_SLIDER_PAD + 8, /* h */ self->height - 2 * VOLUME_SLIDER_VERT_PAD + 2, 6, volume_pattern, &stuff); stuff.on = rgba(40,160,255,0); stuff.off = rgba(200,200,200,0); draw_rounded_rectangle_pattern(ctx, /* x */ VOLUME_SLIDER_LEFT_PAD - 3, /* y */ offset + VOLUME_SLIDER_VERT_PAD, /* w */ ctx->width - VOLUME_SLIDER_PAD + 6, /* h */ self->height - 2 * VOLUME_SLIDER_VERT_PAD, 5, volume_pattern, &stuff); draw_rounded_rectangle(ctx, /* x */ stuff.level - VOLUME_SLIDER_BALL_RADIUS + VOLUME_SLIDER_LEFT_PAD, /* y */ offset + 12 - VOLUME_SLIDER_BALL_RADIUS, /* w */ VOLUME_SLIDER_BALL_RADIUS * 2, /* h */ VOLUME_SLIDER_BALL_RADIUS * 2, VOLUME_SLIDER_BALL_RADIUS, rgb(140,140,140)); draw_rounded_rectangle(ctx, /* x */ stuff.level - VOLUME_SLIDER_BALL_RADIUS + 1 + VOLUME_SLIDER_LEFT_PAD, /* y */ offset + 12 - VOLUME_SLIDER_BALL_RADIUS + 1, /* w */ VOLUME_SLIDER_BALL_RADIUS * 2 - 2, /* h */ VOLUME_SLIDER_BALL_RADIUS * 2 - 2, VOLUME_SLIDER_BALL_RADIUS - 1, rgb(220,220,220)); } int _menu_mouse_MenuEntry_Slider(struct MenuEntry * self, struct yutani_msg_window_mouse_event * event) { if (event->buttons & YUTANI_MOUSE_BUTTON_LEFT) { /* Figure out where it is */ float level = (float)(event->new_x - VOLUME_SLIDER_LEFT_PAD) / (float)(self->width - VOLUME_SLIDER_PAD); if (level >= 1.0) level = 1.0; if (level <= 0.0) level = 0.0; if (volume_level != level * 0xFC000000) { volume_level = level * 0xFC000000; set_volume(); return 1; } } return 0; } static struct MenuEntryVTable slider_vtable = { .methods = 4, .renderer = _menu_draw_MenuEntry_Slider, .mouse_event = _menu_mouse_MenuEntry_Slider, }; struct MenuEntry * menu_create_slider(void) { struct MenuEntry * out = menu_create_separator(); /* Steal some defaults */ out->_type = -1; /* Special */ out->height = 24; out->rwidth = 200; out->vtable = &slider_vtable; return out; } static int widget_click_volume(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { if (!volume_menu) { volume_menu = menu_create(); volume_menu->flags |= MENU_FLAG_BUBBLE_LEFT; } /* Clear the menu */ while (volume_menu->entries->length) { node_t * node = list_pop(volume_menu->entries); menu_free_entry((struct MenuEntry *)node->value); free(node); } menu_insert(volume_menu, menu_create_slider()); /* TODO Our mixer supports multiple knobs and we could show all of them. */ /* TODO We could also show a nice slider... if we had one... */ if (!volume_menu->window) { panel_menu_show(this, volume_menu); } return 1; } static int widget_draw_volume(struct PanelWidget * this, gfx_context_t * ctx) { uint32_t color = (volume_menu && volume_menu->window) ? this->pctx->color_text_hilighted : this->pctx->color_icon_normal; panel_highlight_widget(this,ctx,(volume_menu && volume_menu->window)); if (volume_level < 10) { draw_sprite_alpha_paint(ctx, sprite_volume_mute, (ctx->width - sprite_volume_mute->width) / 2, 1, 1.0, color); } else if (volume_level < 0x547ae147) { draw_sprite_alpha_paint(ctx, sprite_volume_low, (ctx->width - sprite_volume_low->width) / 2, 1, 1.0, color); } else if (volume_level < 0xa8f5c28e) { draw_sprite_alpha_paint(ctx, sprite_volume_med, (ctx->width - sprite_volume_med->width) / 2, 1, 1.0, color); } else { draw_sprite_alpha_paint(ctx, sprite_volume_high, (ctx->width - sprite_volume_high->width) / 2, 1, 1.0, color); } return 0; } /* For dumb legacy reasons, scroll wheel movement shows up here... */ static int widget_move_volume(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { int scroll_direction = 0; if (evt->buttons & YUTANI_MOUSE_SCROLL_UP) scroll_direction = -1; else if (evt->buttons & YUTANI_MOUSE_SCROLL_DOWN) scroll_direction = 1; if (scroll_direction == 1) { volume_lower(); return 1; } else if (scroll_direction == -1) { volume_raise(); return 1; } return 0; } struct PanelWidget * widget_init_volume(void) { sprite_volume_mute = malloc(sizeof(sprite_t)); sprite_volume_low = malloc(sizeof(sprite_t)); sprite_volume_med = malloc(sizeof(sprite_t)); sprite_volume_high = malloc(sizeof(sprite_t)); load_sprite(sprite_volume_mute, "/usr/share/icons/24/volume-mute.png"); load_sprite(sprite_volume_low, "/usr/share/icons/24/volume-low.png"); load_sprite(sprite_volume_med, "/usr/share/icons/24/volume-medium.png"); load_sprite(sprite_volume_high, "/usr/share/icons/24/volume-full.png"); struct PanelWidget * widget = widget_new(); widget->width = sprite_volume_high->width + widget->pctx->extra_widget_spacing; widget->draw = widget_draw_volume; widget->click = widget_click_volume; widget->move = widget_move_volume; widget->update = widget_update_volume; list_insert(widgets_enabled, widget); return widget; } ================================================ FILE: lib/panel_weather.c ================================================ /** * @brief Panel Weather Widget */ #include #include #include #include #include #include #include struct MenuList * weather; static struct MenuEntry_Normal * weather_title_entry; static struct MenuEntry_Normal * weather_updated_entry; static struct MenuEntry_Normal * weather_conditions_entry; static struct MenuEntry_Normal * weather_humidity_entry; static struct MenuEntry_Normal * weather_clouds_entry; static struct MenuEntry_Normal * weather_pressure_entry; static char * weather_title_str; static char * weather_updated_str; static char * weather_conditions_str; static char * weather_humidity_str; static char * weather_clouds_str; static char * weather_pressure_str; static char * weather_temp_str; static int weather_status_valid = 0; static hashmap_t * weather_icons = NULL; static sprite_t * weather_icon = NULL; static int widgets_weather_enabled = 0; static int widget_update_weather(struct PanelWidget * this, int * force_updates) { FILE * f = fopen("/tmp/weather-parsed.conf","r"); if (!f) { weather_status_valid = 0; if (widgets_weather_enabled) { widgets_weather_enabled = 0; this->width = 0; return 1; } return 0; } weather_status_valid = 1; if (weather_title_str) free(weather_title_str); if (weather_updated_str) free(weather_updated_str); if (weather_conditions_str) free(weather_conditions_str); if (weather_humidity_str) free(weather_humidity_str); if (weather_clouds_str) free(weather_clouds_str); if (weather_pressure_str) free(weather_pressure_str); if (weather_temp_str) free(weather_temp_str); /* read the entire status file */ fseek(f, 0, SEEK_END); size_t size = ftell(f); fseek(f, 0, SEEK_SET); char * data = malloc(size + 1); fread(data, size, 1, f); data[size] = 0; fclose(f); /* Find relevant pieces */ char * t = data; char * temp = t; t = strstr(t, "\n"); *t = '\0'; t++; char * temp_r = t; t = strstr(t, "\n"); *t = '\0'; t++; char * conditions = t; t = strstr(t, "\n"); *t = '\0'; t++; char * icon = t; t = strstr(t, "\n"); *t = '\0'; t++; char * humidity = t; t = strstr(t, "\n"); *t = '\0'; t++; char * clouds = t; t = strstr(t, "\n"); *t = '\0'; t++; char * city = t; t = strstr(t, "\n"); *t = '\0'; t++; char * updated = t; t = strstr(t, "\n"); *t = '\0'; t++; char * pressure = t; t = strstr(t, "\n"); if (t) { *t = '\0'; t++; } if (!weather_icons) { weather_icons = hashmap_create(10); } if (!hashmap_has(weather_icons, icon)) { sprite_t * tmp = malloc(sizeof(sprite_t)); char path[512]; sprintf(path,"/usr/share/icons/weather/%s.png", icon); load_sprite(tmp, path); hashmap_set(weather_icons, icon, tmp); } weather_icon = hashmap_get(weather_icons, icon); char tmp[300]; sprintf(tmp, "Weather for %s", city); weather_title_str = strdup(tmp); sprintf(tmp, "%s", updated); weather_updated_str = strdup(tmp); sprintf(tmp, "%s° - %s", temp, conditions); weather_conditions_str = strdup(tmp); sprintf(tmp, "Humidity: %s%%", humidity); weather_humidity_str = strdup(tmp); sprintf(tmp, "Clouds: %s%%", clouds); weather_clouds_str = strdup(tmp); sprintf(tmp, "Pressure: %s hPa", pressure); weather_pressure_str = strdup(tmp); sprintf(tmp, "%s°", temp_r); weather_temp_str = strdup(tmp); free(data); if (!widgets_weather_enabled) { widgets_weather_enabled = 1; this->width = 60; return 1; } return 0; } static void weather_refresh(struct MenuEntry * self) { (void)self; system("weather-tool &"); } static void weather_configure(struct MenuEntry * self) { (void)self; system("terminal sh -c \"sudo weather-configurator; weather-tool\" &"); } static int widget_click_weather(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { if (!weather) { weather = menu_create(); weather->flags |= MENU_FLAG_BUBBLE_LEFT; weather_title_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(weather, weather_title_entry); weather_updated_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(weather, weather_updated_entry); menu_insert(weather, menu_create_separator()); weather_conditions_entry = (struct MenuEntry_Normal *)menu_create_normal(NULL, NULL, "", NULL); menu_insert(weather, weather_conditions_entry); weather_humidity_entry = (struct MenuEntry_Normal *)menu_create_normal("weather-humidity", NULL, "", NULL); menu_insert(weather, weather_humidity_entry); weather_clouds_entry = (struct MenuEntry_Normal *)menu_create_normal("weather-clouds", NULL, "", NULL); menu_insert(weather, weather_clouds_entry); weather_pressure_entry = (struct MenuEntry_Normal *)menu_create_normal("weather-pressure", NULL, "", NULL); menu_insert(weather, weather_pressure_entry); menu_insert(weather, menu_create_separator()); menu_insert(weather, menu_create_normal("refresh", NULL, "Refresh...", weather_refresh)); menu_insert(weather, menu_create_normal("config", NULL, "Configure...", weather_configure)); menu_insert(weather, menu_create_separator()); menu_insert(weather, menu_create_normal(NULL, NULL, "Weather data provided by", NULL)); menu_insert(weather, menu_create_normal(NULL, NULL, "OpenWeather™", NULL)); } if (weather_status_valid) { menu_update_title(weather_title_entry, weather_title_str); menu_update_title(weather_updated_entry, weather_updated_str); menu_update_title(weather_conditions_entry, weather_conditions_str); menu_update_title(weather_humidity_entry, weather_humidity_str); menu_update_title(weather_clouds_entry, weather_clouds_str); menu_update_title(weather_pressure_entry, weather_pressure_str); } if (!weather->window) { panel_menu_show(this,weather); } return 1; } static int widget_draw_weather(struct PanelWidget * this, gfx_context_t * ctx) { if (widgets_weather_enabled) { uint32_t color = (weather && weather->window) ? this->pctx->color_text_hilighted : this->pctx->color_icon_normal; panel_highlight_widget(this,ctx, (weather && weather->window)); tt_set_size(this->pctx->font, 12); int t = tt_string_width(this->pctx->font, weather_temp_str); tt_draw_string(ctx, this->pctx->font, 4 + 24 + (24 - t) / 2, 6 + 12, weather_temp_str, color); draw_sprite_alpha_paint(ctx, weather_icon, 4, 1, 1.0, color); } return 0; } struct PanelWidget * widget_init_weather(void) { weather_refresh(NULL); struct PanelWidget * widget = widget_new(); widget->width = 0; widget->draw = widget_draw_weather; widget->click = widget_click_weather; widget->update = widget_update_weather; list_insert(widgets_enabled, widget); return widget; } ================================================ FILE: lib/panel_windowlist.c ================================================ /** * @brief Panel window list widget */ #include #include #include #include #include #include #define GRADIENT_HEIGHT 24 #define GRADIENT_AT(y) premultiply(rgba(72, 167, 255, ((24-(y))*160)/24)) #define MAX_TEXT_WIDTH 180 #define MIN_TEXT_WIDTH 50 #define TOTAL_CELL_WIDTH (title_width) static struct MenuList * window_menu; static int title_width = 0; static yutani_wid_t _window_menu_wid = 0; static int focused_app = -1; static void _window_menu_start_move(struct MenuEntry * self) { if (!_window_menu_wid) return; yutani_focus_window(yctx, _window_menu_wid); yutani_window_drag_start_wid(yctx, _window_menu_wid); } static void _window_menu_start_maximize(struct MenuEntry * self) { if (!_window_menu_wid) return; yutani_special_request_wid(yctx, _window_menu_wid, YUTANI_SPECIAL_REQUEST_MAXIMIZE); yutani_focus_window(yctx, _window_menu_wid); } static void _window_menu_start_minimize(struct MenuEntry * self) { if (!_window_menu_wid) return; yutani_special_request_wid(yctx, _window_menu_wid, YUTANI_SPECIAL_REQUEST_MINIMIZE); } static void _window_menu_close(struct MenuEntry * self) { if (!_window_menu_wid) return; yutani_special_request_wid(yctx, _window_menu_wid, YUTANI_SPECIAL_REQUEST_PLEASE_CLOSE); } static void window_show_menu(yutani_wid_t wid, int x) { if (window_menu->window) return; _window_menu_wid = wid; panel_menu_show_at(window_menu, x); } static int widget_draw_windowlist(struct PanelWidget * this, gfx_context_t * ctx) { if (!window_list || window_list->length == 0) { title_width = 0; } else if (this->width <= 0) { title_width = 28; } else { title_width = this->width / window_list->length; if (title_width > MAX_TEXT_WIDTH) { title_width = MAX_TEXT_WIDTH; } if (title_width < MIN_TEXT_WIDTH) { title_width = 28; } } int i = 0, j = 0; if (window_list) { foreach(node, window_list) { struct window_ad * ad = node->value; int w = title_width; if (i + w > this->width) { break; } /* Hilight the focused window */ if (ad->flags & 1) { /* This is the focused window */ for (int y = 0; y < GRADIENT_HEIGHT; ++y) { for (int x = i; x < i + w; ++x) { GFX(ctx, x, y) = alpha_blend_rgba(GFX(ctx, x, y), GRADIENT_AT(y)); } } } uint32_t text_color = this->pctx->color_text_normal; if (j == focused_app) text_color = this->pctx->color_text_hilighted; else if (ad->flags & 1) text_color = this->pctx->color_text_focused; else 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)); if (title_width >= MIN_TEXT_WIDTH) { /* Ellipsifiy the title */ char * s = tt_ellipsify(ad->name, 14, this->pctx->font, title_width - 4, NULL); sprite_t * icon = icon_get_48(ad->icon); gfx_context_t * subctx = init_graphics_subregion(ctx, i, 0, w, ctx->height-1); draw_sprite_scaled_alpha(subctx, icon, w - 48 - 2, 0, 48, 48, (ad->flags & 1) ? 1.0 : 0.7); tt_draw_string_shadow(subctx, this->pctx->font, s, 14, 2, 6, text_color, rgb(0,0,0), 4); free(subctx); free(s); } else { sprite_t * icon = icon_get_16(ad->icon); gfx_context_t * subctx = init_graphics_subregion(ctx, i, 0, w, ctx->height-1); draw_sprite_scaled(subctx, icon, 6, 6, 16, 16); free(subctx); } ad->left = this->left + i; yutani_window_panel_size(yctx, ad->wid, ad->left + this->pctx->basewindow->x, this->pctx->basewindow->y, w, ctx->height); j++; i += w; } } return 0; } static int widget_click_windowlist(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { foreach(node, window_list) { struct window_ad * ad = node->value; if (evt->new_x >= ad->left && evt->new_x < ad->left + TOTAL_CELL_WIDTH) { yutani_focus_window(yctx, ad->wid); break; } } return 0; } static int widget_rightclick_windowlist(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { foreach(node, window_list) { struct window_ad * ad = node->value; if (evt->new_x >= ad->left && evt->new_x < ad->left + TOTAL_CELL_WIDTH) { window_show_menu(ad->wid, evt->new_x); } } return 0; } /* Update the hover-focus window */ static int set_focused(int i) { if (focused_app != i) { focused_app = i; return 1; } return 0; } static int widget_move_windowlist(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { int found = 0; int i = 0; int should_redraw = 0; foreach(node, window_list) { struct window_ad * ad = node->value; if (evt->new_x >= ad->left && evt->new_x < ad->left + TOTAL_CELL_WIDTH) { found = 1; should_redraw |= set_focused(i); break; } i++; } if (!found) { should_redraw |= set_focused(-1); } int scroll_direction = 0; if (evt->buttons & YUTANI_MOUSE_SCROLL_UP) scroll_direction = -1; else if (evt->buttons & YUTANI_MOUSE_SCROLL_DOWN) scroll_direction = 1; if (scroll_direction != 0) { struct window_ad * last = window_list->tail ? window_list->tail->value : NULL; int focus_next = 0; foreach(node, window_list) { struct window_ad * ad = node->value; if (focus_next) { yutani_focus_window(yctx, ad->wid); return 1; } if (ad->flags & 1) { if (scroll_direction == -1) { yutani_focus_window(yctx, last->wid); return 1; } if (scroll_direction == 1) { focus_next = 1; } } last = ad; } if (focus_next && window_list->head) { struct window_ad * ad = window_list->head->value; yutani_focus_window(yctx, ad->wid); return 1; } } return should_redraw; } static int widget_leave_windowlist(struct PanelWidget * this, struct yutani_msg_window_mouse_event * evt) { this->highlighted = 0; return set_focused(-1); } static int widget_onkey_windowlist(struct PanelWidget * this, struct yutani_msg_key_event * ke) { if ((ke->event.modifiers & KEY_MOD_LEFT_ALT) && (ke->event.keycode == KEY_F3) && (ke->event.action == KEY_ACTION_DOWN)) { foreach(node, window_list) { struct window_ad * ad = node->value; if (ad->flags & 1) { window_show_menu(ad->wid, ad->left + title_width / 2); } } } return 0; } struct PanelWidget * widget_init_windowlist(void) { window_menu = menu_create(); window_menu->flags |= MENU_FLAG_BUBBLE_LEFT; menu_insert(window_menu, menu_create_normal(NULL, NULL, "Maximize", _window_menu_start_maximize)); menu_insert(window_menu, menu_create_normal(NULL, NULL, "Minimize", _window_menu_start_minimize)); menu_insert(window_menu, menu_create_normal(NULL, NULL, "Move", _window_menu_start_move)); menu_insert(window_menu, menu_create_separator()); menu_insert(window_menu, menu_create_normal(NULL, NULL, "Close", _window_menu_close)); /* Alt+F3 = window context menu */ yutani_key_bind(yctx, KEY_F3, KEY_MOD_LEFT_ALT, YUTANI_BIND_STEAL); struct PanelWidget * widget = widget_new(); widget->fill = 1; widget->draw = widget_draw_windowlist; widget->click = widget_click_windowlist; widget->right_click = widget_rightclick_windowlist; widget->move = widget_move_windowlist; widget->leave = widget_leave_windowlist; widget->onkey = widget_onkey_windowlist; list_insert(widgets_enabled, widget); return widget; } ================================================ FILE: lib/pex.c ================================================ /** * @brief pex - Packet EXchange client library * * Provides a friendly interface to the "Packet Exchange" * functionality provided by the packetfs kernel interface. * * @copyright * 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-2018 K. Lange */ #include #include #include #include #include #include #include #include #include #include size_t pex_send(FILE * sock, uintptr_t rcpt, size_t size, char * blob) { assert(size <= MAX_PACKET_SIZE); pex_header_t * broadcast = malloc(sizeof(pex_header_t) + size); broadcast->target = rcpt; memcpy(broadcast->data, blob, size); size_t out = write(fileno(sock), broadcast, sizeof(pex_header_t) + size); free(broadcast); return out; } size_t pex_broadcast(FILE * sock, size_t size, char * blob) { return pex_send(sock, 0, size, blob); } size_t pex_listen(FILE * sock, pex_packet_t * packet) { return read(fileno(sock), packet, PACKET_SIZE); } size_t pex_reply(FILE * sock, size_t size, char * blob) { return write(fileno(sock), blob, size); } size_t pex_recv(FILE * sock, char * blob) { memset(blob, 0, MAX_PACKET_SIZE); return read(fileno(sock), blob, MAX_PACKET_SIZE); } FILE * pex_connect(char * target) { char tmp[100]; if (strlen(target) > 80) return NULL; sprintf(tmp, "/dev/pex/%s", target); FILE * out = fopen(tmp, "r+"); if (out) { setbuf(out, NULL); } return out; } FILE * pex_bind(char * target) { char tmp[100]; if (strlen(target) > 80) return NULL; sprintf(tmp, "/dev/pex/%s", target); int fd = open(tmp, O_CREAT | O_EXCL | O_RDWR | O_APPEND); if (fd < 0) { return NULL; } FILE * out = fdopen(fd, "a+"); if (out) { setbuf(out, NULL); } return out; } size_t pex_query(FILE * sock) { return ioctl(fileno(sock), IOCTL_PACKETFS_QUEUED, NULL); } ================================================ FILE: lib/png.c ================================================ /** * @brief PNG decoder. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2020 K. Lange */ #include #include #include #include /** * Read 32-bit big-endian value from file. */ unsigned int read_32(FILE * f) { unsigned char a = fgetc(f); unsigned char b = fgetc(f); unsigned char c = fgetc(f); unsigned char d = fgetc(f); return (a << 24) | (b << 16) | (c << 8) | d; } /** * Read 16-bit big-endian value from file. */ unsigned int read_16(FILE * f) { unsigned char a = fgetc(f); unsigned char b = fgetc(f); return (a << 8) | b; } /** * (Debug) Return a chunk type as a string. */ __attribute__((unused)) static char* reorder_type(unsigned int type) { static char out[4]; out[0] = (type >> 24) & 0xFF; out[1] = (type >> 16) & 0xFF; out[2] = (type >> 8) & 0xFF; out[3] = (type >> 0) & 0xFF; return out; } /** * Internal PNG decoder state for use with inflate. */ struct png_ctx { FILE * f; /* File being decoded. */ sprite_t * sprite; /* Sprite being generated. */ int y; /* Cursor pointers for writing out bitmap data */ int x; char buffer[4]; /* A buffer to hold a pixel's worth of data until it can be written out to the image with the right filter. */ int buf_off; /* How much data is in the above buffer */ int seen_ihdr; /* Whether the IHDR was seen; for error handling */ unsigned int width; /* Image width (dup from sprite) */ unsigned int height; /* Image height (dup from sprite) */ int bit_depth; /* Bit depth of the image */ int color_type; /* PNG color type */ int compression; /* Compression method (must be 0) */ int filter; /* Filter method (must be 0) */ int interlace; /* Interlace method (we only support 0) */ unsigned int size; /* Remaining IDAT chunk size */ int sf; /* Current scanline filter type */ }; /* PNG chunk types */ #define PNG_IHDR 0x49484452 #define PNG_IDAT 0x49444154 #define PNG_IEND 0x49454e44 /* PNG filter types */ #define PNG_FILTER_NONE 0 #define PNG_FILTER_SUB 1 #define PNG_FILTER_UP 2 #define PNG_FILTER_AVG 3 #define PNG_FILTER_PAETH 4 /** * Read a byte from the IDAT chunk. * Tracks when an IDAT has been read to completion and * can load the next IDAT (or bail of this was the last one) */ static uint8_t _get(struct inflate_context * ctx) { struct png_ctx * c = (ctx->input_priv); if (c->size == 0) { /* Read the CRC32 from the end of this IDAT */ unsigned int check = read_32(c->f); (void)check; /* ... and in theory check it... */ /* Read the next IDAT chunk header */ unsigned int size = read_32(c->f); unsigned int type = read_32(c->f); c->size = size; if (type != PNG_IDAT) { /* This isn't an IDAT? That's wrong! */ fprintf(stderr, "And this is the wrong type (0x%x), I'm just bailing.\n", type); fprintf(stderr, "size read was 0x%x\n", size); exit(0); } } /* Read one byte from the input */ c->size--; int i = fgetc(c->f); /* If this was EOF, we should handle that error case... probably... */ if (i < 0) fprintf(stderr, "This is probably not good.\n"); return i; } /** * Paeth predictor * Described in section 6.6 of the RFC */ #define ABS(a) ((a >= 0) ? (a) : -(a)) static int paeth(int a, int b, int c) { int p = a + b - c; int pa = ABS(p - a); int pb = ABS(p - b); int pc = ABS(p - c); if (pa <= pb && pa <= pc) return a; else if (pb <= pc) return b; return c; } static void write_pixel(struct png_ctx * c, uint32_t color) { SPRITE((c->sprite), (c->x), (c->y)) = color; /* Reset the short buffer */ c->buf_off = 0; /* Advance to next pixel */ c->x++; if (c->x == (int)c->width) { /* Advance to next line; next read is scanline filter type */ c->x = -1; c->y++; } } static void process_pixel_type_6(struct png_ctx * c) { /* * Obtain pixel data from short buffer; * For color type 6, this is always in R G B A order in the * bytestream, so we don't have to worry about subpixel ordering * or weird color masks. */ unsigned int r = c->buffer[0]; unsigned int g = c->buffer[1]; unsigned int b = c->buffer[2]; unsigned int a = c->buffer[3]; /* Apply filters */ if (c->sf == PNG_FILTER_SUB) { /* Add raw value to the pixel on the left */ if (c->x > 0) { uint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y)); r += _RED(left); g += _GRE(left); b += _BLU(left); a += _ALP(left); } } else if (c->sf == PNG_FILTER_UP) { /* Add raw value to the pixel above */ if (c->y > 0) { uint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1)); r += _RED(up); g += _GRE(up); b += _BLU(up); a += _ALP(up); } } else if (c->sf == PNG_FILTER_AVG) { /* Add raw value to the average of the pixel above and left */ uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; r += ((int)_RED(left) + (int)_RED(up)) / 2; g += ((int)_GRE(left) + (int)_GRE(up)) / 2; b += ((int)_BLU(left) + (int)_BLU(up)) / 2; a += ((int)_ALP(left) + (int)_ALP(up)) / 2; } else if (c->sf == PNG_FILTER_PAETH) { /* Use the Paeth predictor */ uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; uint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0; r = ((int)r + paeth((int)_RED(left),(int)_RED(up),(int)_RED(upleft))) % 256; g = ((int)g + paeth((int)_GRE(left),(int)_GRE(up),(int)_GRE(upleft))) % 256; b = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256; a = ((int)a + paeth((int)_ALP(left),(int)_ALP(up),(int)_ALP(upleft))) % 256; } /* Write new pixel to the image */ write_pixel(c, rgba(r,g,b,a)); } static void process_pixel_type_2(struct png_ctx * c) { /* * Obtain pixel data from short buffer; * For color type 6, this is always in R G B A order in the * bytestream, so we don't have to worry about subpixel ordering * or weird color masks. */ unsigned int r = c->buffer[0]; unsigned int g = c->buffer[1]; unsigned int b = c->buffer[2]; /* Apply filters */ if (c->sf == PNG_FILTER_SUB) { /* Add raw value to the pixel on the left */ if (c->x > 0) { uint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y)); r += _RED(left); g += _GRE(left); b += _BLU(left); } } else if (c->sf == PNG_FILTER_UP) { /* Add raw value to the pixel above */ if (c->y > 0) { uint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1)); r += _RED(up); g += _GRE(up); b += _BLU(up); } } else if (c->sf == PNG_FILTER_AVG) { /* Add raw value to the average of the pixel above and left */ uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; r += ((int)_RED(left) + (int)_RED(up)) / 2; g += ((int)_GRE(left) + (int)_GRE(up)) / 2; b += ((int)_BLU(left) + (int)_BLU(up)) / 2; } else if (c->sf == PNG_FILTER_PAETH) { /* Use the Paeth predictor */ uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; uint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0; r = ((int)r + paeth((int)_RED(left),(int)_RED(up),(int)_RED(upleft))) % 256; g = ((int)g + paeth((int)_GRE(left),(int)_GRE(up),(int)_GRE(upleft))) % 256; b = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256; } /* Write new pixel to the image */ write_pixel(c, rgb(r,g,b)); } static void process_pixel_type_4(struct png_ctx * c) { /* * Obtain pixel data from short buffer; * For color type 6, this is always in R G B A order in the * bytestream, so we don't have to worry about subpixel ordering * or weird color masks. */ unsigned int b = c->buffer[0]; unsigned int a = c->buffer[1]; /* Apply filters */ if (c->sf == PNG_FILTER_SUB) { /* Add raw value to the pixel on the left */ if (c->x > 0) { uint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y)); b += _BLU(left); a += _ALP(left); } } else if (c->sf == PNG_FILTER_UP) { /* Add raw value to the pixel above */ if (c->y > 0) { uint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1)); b += _BLU(up); a += _ALP(up); } } else if (c->sf == PNG_FILTER_AVG) { /* Add raw value to the average of the pixel above and left */ uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; b += ((int)_BLU(left) + (int)_BLU(up)) / 2; a += ((int)_ALP(left) + (int)_ALP(up)) / 2; } else if (c->sf == PNG_FILTER_PAETH) { /* Use the Paeth predictor */ uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; uint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0; b = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256; a = ((int)a + paeth((int)_ALP(left),(int)_ALP(up),(int)_ALP(upleft))) % 256; } /* Write new pixel to the image */ write_pixel(c, rgba(b,b,b,a)); } static void process_pixel_type_0(struct png_ctx * c) { unsigned int b = c->buffer[0]; if (c->sf == PNG_FILTER_SUB) { if (c->x > 0) { uint32_t left = SPRITE((c->sprite), (c->x - 1), (c->y)); b += _BLU(left); } } else if (c->sf == PNG_FILTER_UP) { if (c->y > 0) { uint32_t up = SPRITE((c->sprite), (c->x), (c->y - 1)); b += _BLU(up); } } else if (c->sf == PNG_FILTER_AVG) { uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; b += ((int)_BLU(left) + (int)_BLU(up)) / 2; } else if (c->sf == PNG_FILTER_PAETH) { uint32_t left = (c->x > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y)) : 0; uint32_t up = (c->y > 0) ? SPRITE((c->sprite), (c->x), (c->y - 1)) : 0; uint32_t upleft = (c->x > 0 && c->y > 0) ? SPRITE((c->sprite), (c->x - 1), (c->y - 1)) : 0; b = ((int)b + paeth((int)_BLU(left),(int)_BLU(up),(int)_BLU(upleft))) % 256; } /* Write new pixel to the image */ write_pixel(c, rgb(b,b,b)); } /** * Handle decompressed output from the inflater * * Writes pixel data to the image, and applies relevant filters. */ static void _write(struct inflate_context * ctx, unsigned int sym) { struct png_ctx * c = (ctx->input_priv); /* Put this byte into the short buffer */ c->buffer[c->buf_off] = sym; c->buf_off++; /* If this is the beginning of a scanline... */ if (c->x == -1 && c->buf_off == 1) { /* Then this is the scanline filter type */ c->sf = sym; /* Reset the buffer, advance to the beginning of the actual scanline */ c->x = 0; c->buf_off = 0; } else if (c->buf_off == 1 && c->color_type == 0) { process_pixel_type_0(c); } else if (c->buf_off == 2 && c->color_type == 4) { process_pixel_type_4(c); } else if (c->buf_off == 3 && c->color_type == 2) { process_pixel_type_2(c); } else if (c->buf_off == 4 && c->color_type == 6) { process_pixel_type_6(c); } } static int color_type_has_alpha(int c) { switch (c) { case 4: case 6: return ALPHA_EMBEDDED; default: return 0; } } int load_sprite_png(sprite_t * sprite, char * filename) { FILE * f = fopen(filename,"r"); if (!f) { fprintf(stderr, "Failed to open file %s\n", filename); return 1; } /* Read the PNG signature */ unsigned char sig[] = {137, 80, 78, 71, 13, 10, 26, 10}; for (int i = 0; i < 8; ++i) { unsigned char c = fgetc(f); if (c != sig[i]) { fprintf(stderr, "byte %d (%d) does not match expected (%d)\n", i, c, sig[i]); goto _error; } } /* Set up context for future calls to inflate */ struct png_ctx c; c.sprite = sprite; c.x = -1; c.y = 0; c.f = f; c.buf_off = 0; c.seen_ihdr = 0; while (1) { /* read chunks */ unsigned int size = read_32(f); unsigned int type = read_32(f); if (feof(f)) break; switch (type) { case PNG_IHDR: { /* Image should only have one IHDR */ if (c.seen_ihdr) return 1; c.seen_ihdr = 1; c.width = read_32(f); /* 4 */ c.height = read_32(f); /* 8 */ c.bit_depth = fgetc(f); /* 9 */ c.color_type = fgetc(f); /* 10 */ c.compression = fgetc(f); /* 11 */ c.filter = fgetc(f); /* 12 */ c.interlace = fgetc(f); /* 13 */ /* Invalid / non-standard compression and filter types */ if (c.compression != 0) return 1; if (c.filter != 0) return 1; /* 0 for none, 1 for Adam7 */ if (c.interlace != 0 && c.interlace != 1) return 1; if (c.bit_depth != 8) return 1; /* Sorry */ if (c.color_type < 0 || c.color_type > 6 || (c.color_type & 1)) return 1; /* Sorry, no indexed support */ /* Allocate space */ sprite->width = c.width; sprite->height = c.height; sprite->bitmap = malloc(sizeof(uint32_t) * sprite->width * sprite->height); sprite->masks = NULL; sprite->alpha = color_type_has_alpha(c.color_type); sprite->blank = 0; /* Skip */ for (unsigned int i = 13; i < size; ++i) fgetc(f); } break; case PNG_IDAT: { /* First two bytes of IDAT data are ZLIB header */ unsigned int cflags = fgetc(f); if ((cflags & 0xF) != 8) { /* Compression type must be 8 */ fprintf(stderr, "Expected flags to be 8 but it's 0x%x\n", cflags); return 1; } unsigned int aflags = fgetc(f); if (aflags & (1 << 5)) { fprintf(stderr, "There are preset bytes and I don't know what to do.\n"); return 1; } struct inflate_context ctx; ctx.input_priv = &c; ctx.output_priv = &c; ctx.get_input = _get; ctx.write_output = _write; ctx.ring = NULL; /* use builtin */ c.size = size - 2; /* 2 for the bytes we already read */ deflate_decompress(&ctx); /* The IDATs contain a ZLIB stream, so they end with an * adler32 checksum. Skip that. */ unsigned int adler = read_32(f); (void)adler; } break; case PNG_IEND: /* We don't actually have anything to do here. */ break; default: /* IHDR must be first */ if (!c.seen_ihdr) return 1; //fprintf(stderr, "I don't know what this is! %4s 0x%x\n", reorder_type(type), type); /* Skip */ for (unsigned int i = 0; i < size; ++i) fgetc(f); break; } unsigned int crc32 = read_32(f); (void)crc32; } /* * Data in PNGs is unpremultiplied, but our sprites expect * premultiplied alpha, so convert the image data */ for (int y = 0; y < sprite->height; ++y) { for (int x = 0; x < sprite->width; ++x) { SPRITE(sprite,x,y) = premultiply(SPRITE(sprite,x,y)); } } fclose(f); return 0; _error: fclose(f); return 1; } ================================================ FILE: lib/rline.c ================================================ /** * @brief Line editor * * Interactive line input editor with syntax highlighting for * a handful of languages. Based on an old version of Bim. * Used by the shell and Kuroko. * * This library is generally usable on Linux and even Windows. * * @copyright * 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-2022 K. Lange */ #define _XOPEN_SOURCE #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #include #else #include #include #include "wcwidth._h" #endif #ifdef __toaru__ #include #else #include "rline.h" #endif static __attribute__((used)) int _isdigit(int c) { if (c > 128) return 0; return isdigit(c); } static __attribute__((used)) int _isxdigit(int c) { if (c > 128) return 0; return isxdigit(c); } #undef isdigit #undef isxdigit #define isdigit(c) _isdigit(c) #define isxdigit(c) _isxdigit(c) char * rline_history[RLINE_HISTORY_ENTRIES]; int rline_history_count = 0; int rline_history_offset = 0; int rline_scroll = 0; char * rline_exit_string = "exit\n"; int rline_terminal_width = 0; char * rline_preload = NULL; void rline_history_insert(char * str) { if (str[strlen(str)-1] == '\n') { str[strlen(str)-1] = '\0'; } if (rline_history_count) { if (!strcmp(str, rline_history_prev(1))) { free(str); return; } } if (rline_history_count == RLINE_HISTORY_ENTRIES) { free(rline_history[rline_history_offset]); rline_history[rline_history_offset] = str; rline_history_offset = (rline_history_offset + 1) % RLINE_HISTORY_ENTRIES; } else { rline_history[rline_history_count] = str; rline_history_count++; } } void rline_history_append_line(char * str) { if (rline_history_count) { char ** s = &rline_history[(rline_history_count - 1 + rline_history_offset) % RLINE_HISTORY_ENTRIES]; size_t len = strlen(*s) + strlen(str) + 2; char * c = malloc(len); snprintf(c, len, "%s\n%s", *s, str); if (c[strlen(c)-1] == '\n') { c[strlen(c)-1] = '\0'; } free(*s); *s = c; } else { /* wat */ } } char * rline_history_get(int item) { return rline_history[(item + rline_history_offset) % RLINE_HISTORY_ENTRIES]; } char * rline_history_prev(int item) { return rline_history_get(rline_history_count - item); } #define UTF8_ACCEPT 0 #define UTF8_REJECT 1 /** * Conceptually similar to its predecessor, this implementation is much * less cool, as it uses three separate state tables and more shifts. */ static inline uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) { static int state_table[32] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xxxxxxx */ 1,1,1,1,1,1,1,1, /* 10xxxxxx */ 2,2,2,2, /* 110xxxxx */ 3,3, /* 1110xxxx */ 4, /* 11110xxx */ 1 /* 11111xxx */ }; static int mask_bytes[32] = { 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, 0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x1F,0x1F,0x1F,0x1F, 0x0F,0x0F, 0x07, 0x00 }; static int next[5] = { 0, 1, 0, 2, 3 }; if (*state == UTF8_ACCEPT) { *codep = byte & mask_bytes[byte >> 3]; *state = state_table[byte >> 3]; } else if (*state > 0) { *codep = (byte & 0x3F) | (*codep << 6); *state = next[*state]; } return *state; } #define ENTER_KEY '\n' #define BACKSPACE_KEY 0x08 #define DELETE_KEY 0x7F #define MINIMUM_SIZE 10 /** * Same structures as in bim. * A single character has: * - A codepoint (Unicode) of up to 21 bits. * - Flags for syntax highlighting. * - A display width for rendering. */ typedef struct { uint32_t display_width:4; uint32_t flags:7; uint32_t codepoint:21; } __attribute__((packed)) char_t; /** * We generally only have the one line, * but this matches bim for compatibility reasons. */ typedef struct { int available; int actual; int istate; char_t text[]; } line_t; /** * We operate on a single line of text. * Maybe we can expand this in the future * for continuations of edits such as when * a quote is unclosed? */ static line_t * the_line = NULL; /** * Line editor state */ static int loading = 0; static int column = 0; static int offset = 0; static int width = 0; static int show_right_side = 0; static int show_left_side = 0; static int prompt_width_calc = 0; static int buf_size_max = 0; /** * Prompt strings. * Defaults to just a "> " prompt with no right side. * Support for right side prompts is important * for the ToaruOS shell. */ static int prompt_width = 2; static char * prompt = "> "; static int prompt_right_width = 0; static char * prompt_right = ""; int rline_exp_set_prompts(char * left, char * right, int left_width, int right_width) { prompt = left; prompt_right = right; prompt_width = left_width; prompt_right_width = right_width; return 0; } /** * Extra shell commands to highlight as keywords. * These are basically just copied from the * shell's tab completion database on startup. */ static char ** shell_commands = {0}; static int shell_commands_len = 0; int rline_exp_set_shell_commands(char ** cmds, int len) { shell_commands = cmds; shell_commands_len = len; return 0; } /** * Tab completion callback. * Compatible with the original rline version. */ static rline_callback_t tab_complete_func = NULL; int rline_exp_set_tab_complete_func(rline_callback_t func) { tab_complete_func = func; return 0; } static int have_unget = -1; static int getch(int timeout) { if (have_unget >= 0) { int out = have_unget; have_unget = -1; return out; } #ifndef _WIN32 return fgetc(stdin); #else static int bytesRead = 0; static char buf8[8]; static uint8_t * b; if (bytesRead) { bytesRead--; return *(b++); } DWORD dwRead; uint16_t buf16[8] = {0}; if (ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE),buf16,2,&dwRead,NULL)) { int r = WideCharToMultiByte(CP_UTF8, 0, buf16, -1, buf8, 8, 0, 0); if (r > 1 && buf8[r-1] == '\0') r--; b = (uint8_t*)buf8; bytesRead = r - 1; return *(b++); } else { fprintf(stderr, "error on console read\n"); return -1; } #endif } /** * Convert from Unicode string to utf-8. */ static int to_eight(uint32_t codepoint, char * out) { memset(out, 0x00, 7); if (codepoint < 0x0080) { out[0] = (char)codepoint; } else if (codepoint < 0x0800) { out[0] = 0xC0 | (codepoint >> 6); out[1] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x10000) { out[0] = 0xE0 | (codepoint >> 12); out[1] = 0x80 | ((codepoint >> 6) & 0x3F); out[2] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x200000) { out[0] = 0xF0 | (codepoint >> 18); out[1] = 0x80 | ((codepoint >> 12) & 0x3F); out[2] = 0x80 | ((codepoint >> 6) & 0x3F); out[3] = 0x80 | ((codepoint) & 0x3F); } else if (codepoint < 0x4000000) { out[0] = 0xF8 | (codepoint >> 24); out[1] = 0x80 | (codepoint >> 18); out[2] = 0x80 | ((codepoint >> 12) & 0x3F); out[3] = 0x80 | ((codepoint >> 6) & 0x3F); out[4] = 0x80 | ((codepoint) & 0x3F); } else { out[0] = 0xF8 | (codepoint >> 30); out[1] = 0x80 | ((codepoint >> 24) & 0x3F); out[2] = 0x80 | ((codepoint >> 18) & 0x3F); out[3] = 0x80 | ((codepoint >> 12) & 0x3F); out[4] = 0x80 | ((codepoint >> 6) & 0x3F); out[5] = 0x80 | ((codepoint) & 0x3F); } return strlen(out); } /** * Obtain codepoint display width. * * This is copied from bim. Supports a few useful * things like rendering escapes as codepoints. */ static int codepoint_width(int codepoint) { if (codepoint == '\t') { return 1; /* Recalculate later */ } if (codepoint < 32) { /* We render these as ^@ */ return 2; } if (codepoint == 0x7F) { /* Renders as ^? */ return 2; } if (codepoint > 0x7f && codepoint < 0xa0) { /* Upper control bytes */ return 4; } if (codepoint == 0xa0) { /* Non-breaking space _ */ return 1; } /* Skip wcwidth for anything under 256 */ if (codepoint > 256) { /* Higher codepoints may be wider (eg. Japanese) */ int out = wcwidth(codepoint); if (out >= 1) return out; /* Invalid character, render as [U+ABCD] or [U+ABCDEF] */ return (codepoint < 0x10000) ? 8 : 10; } return 1; } static void recalculate_tabs(line_t * line) { int j = 0; for (int i = 0; i < line->actual; ++i) { if (line->text[i].codepoint == '\t') { line->text[i].display_width = 4 - (j % 4); } j += line->text[i].display_width; } } /** * Color themes have also been copied from bim. * * Slimmed down to only the ones we use for syntax * highlighting; the UI colors have been removed. */ static const char * COLOR_FG = "@9"; static const char * COLOR_BG = "@9"; static const char * COLOR_ALT_FG = "@5"; static const char * COLOR_ALT_BG = "@9"; static const char * COLOR_KEYWORD = "@4"; static const char * COLOR_STRING = "@2"; static const char * COLOR_COMMENT = "@5"; static const char * COLOR_TYPE = "@3"; static const char * COLOR_PRAGMA = "@1"; static const char * COLOR_NUMERAL = "@1"; static const char * COLOR_RED = "@1"; static const char * COLOR_GREEN = "@2"; static const char * COLOR_ESCAPE = "@2"; static const char * COLOR_SEARCH_FG = "@0"; static const char * COLOR_SEARCH_BG = "@3"; static const char * COLOR_ERROR_FG = "@9"; static const char * COLOR_ERROR_BG = "@9"; static const char * COLOR_BOLD = "@9"; static const char * COLOR_LINK = "@9"; /** * Themes are selected from the $RLINE_THEME * environment variable. */ static void rline_exp_load_colorscheme_default(void) { COLOR_FG = "@9"; COLOR_BG = "@9"; COLOR_ALT_FG = "@10"; COLOR_ALT_BG = "@9"; COLOR_KEYWORD = "@14"; COLOR_STRING = "@2"; COLOR_COMMENT = "@10"; COLOR_TYPE = "@3"; COLOR_PRAGMA = "@1"; COLOR_NUMERAL = "@1"; COLOR_RED = "@1"; COLOR_GREEN = "@2"; COLOR_ESCAPE = "@12"; COLOR_SEARCH_FG = "@0"; COLOR_SEARCH_BG = "@13"; COLOR_ERROR_FG = "@17"; COLOR_ERROR_BG = "@1"; COLOR_BOLD = "@9"; COLOR_LINK = "@14"; } static void rline_exp_load_colorscheme_sunsmoke(void) { COLOR_FG = "2;230;230;230"; COLOR_BG = "@9"; COLOR_ALT_FG = "2;122;122;122"; COLOR_ALT_BG = "2;46;43;46"; COLOR_KEYWORD = "2;51;162;230"; COLOR_STRING = "2;72;176;72"; COLOR_COMMENT = "2;158;153;129;3"; COLOR_TYPE = "2;230;206;110"; COLOR_PRAGMA = "2;194;70;54"; COLOR_NUMERAL = "2;230;43;127"; COLOR_RED = "2;222;53;53"; COLOR_GREEN = "2;55;167;0"; COLOR_ESCAPE = "2;113;203;173"; COLOR_SEARCH_FG = "5;234"; COLOR_SEARCH_BG = "5;226"; COLOR_ERROR_FG = "5;15"; COLOR_ERROR_BG = "5;196"; COLOR_BOLD = "2;230;230;230;1"; COLOR_LINK = "2;51;162;230;4"; } /** * Syntax highlighting flags. */ #define FLAG_NONE 0 #define FLAG_KEYWORD 1 #define FLAG_STRING 2 #define FLAG_COMMENT 3 #define FLAG_TYPE 4 #define FLAG_PRAGMA 5 #define FLAG_NUMERAL 6 #define FLAG_ERROR 7 #define FLAG_DIFFPLUS 8 #define FLAG_DIFFMINUS 9 #define FLAG_NOTICE 10 #define FLAG_BOLD 11 #define FLAG_LINK 12 #define FLAG_ESCAPE 13 #define FLAG_SELECT (1 << 5) struct syntax_state { line_t * line; int line_no; int state; int i; }; #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) #define charat() (state->i < state->line->actual ? state->line->text[(state->i)].codepoint : -1) #define nextchar() (state->i + 1 < state->line->actual ? state->line->text[(state->i+1)].codepoint : -1) #define lastchar() (state->i - 1 >= 0 ? state->line->text[(state->i-1)].codepoint : -1) #define skip() (state->i++) #define charrel(x) (state->i + (x) < state->line->actual ? state->line->text[(state->i+(x))].codepoint : -1) /** * Match and paint a single keyword. Returns 1 if the keyword was matched and 0 otherwise, * so it can be used for prefix checking for things that need further special handling. */ static int match_and_paint(struct syntax_state * state, const char * keyword, int flag, int (*keyword_qualifier)(int c)) { if (keyword_qualifier(lastchar())) return 0; if (!keyword_qualifier(charat())) return 0; int i = state->i; int slen = 0; while (i < state->line->actual || *keyword == '\0') { if (*keyword == '\0' && (i >= state->line->actual || !keyword_qualifier(state->line->text[i].codepoint))) { for (int j = 0; j < slen; ++j) { paint(1, flag); } return 1; } if (*keyword != state->line->text[i].codepoint) return 0; i++; keyword++; slen++; } return 0; } /** * Find keywords from a list and paint them, assuming they aren't in the middle of other words. * Returns 1 if a keyword from the last was found, otherwise 0. */ static int find_keywords(struct syntax_state * state, char ** keywords, int flag, int (*keyword_qualifier)(int c)) { if (keyword_qualifier(lastchar())) return 0; if (!keyword_qualifier(charat())) return 0; for (char ** keyword = keywords; *keyword; ++keyword) { int d = 0; while (state->i + d < state->line->actual && state->line->text[state->i+d].codepoint == (*keyword)[d]) d++; if ((*keyword)[d] == '\0' && (state->i + d >= state->line->actual || !keyword_qualifier(state->line->text[state->i+d].codepoint))) { paint((int)strlen(*keyword), flag); return 1; } } return 0; } /** * This is a basic character matcher for "keyword" characters. */ static int simple_keyword_qualifier(int c) { return isalnum(c) || (c == '_'); } static int common_comment_buzzwords(struct syntax_state * state) { if (match_and_paint(state, "TODO", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } else if (match_and_paint(state, "XXX", FLAG_NOTICE, simple_keyword_qualifier)) { return 1; } else if (match_and_paint(state, "FIXME", FLAG_ERROR, simple_keyword_qualifier)) { return 1; } return 0; } /** * Paint a comment until end of line, assumes this comment can not continue. * (Some languages have comments that can continue with a \ - don't use this!) * Assumes you've already painted your comment start characters. */ static int paint_comment(struct syntax_state * state) { while (charat() != -1) { if (common_comment_buzzwords(state)) continue; else { paint(1, FLAG_COMMENT); } } return -1; } static int c_keyword_qualifier(int c) { return isalnum(c) || (c == '_'); } static void paintNHex(struct syntax_state * state, int n) { paint(2, FLAG_ESCAPE); /* Why is my FLAG_ERROR not valid in rline? */ for (int i = 0; i < n; ++i) { paint(1, isxdigit(charat()) ? FLAG_ESCAPE : FLAG_DIFFMINUS); } } static char * syn_krk_keywords[] = { "and","class","def","else","for","if","in","import","del", "let","not","or","return","while","try","except","raise", "continue","break","as","from","elif","lambda","with","is", "pass","assert","yield","finally","async","await", NULL }; static char * syn_krk_types[] = { /* built-in functions */ "self", "super", /* implicit in a class method */ "len", "str", "int", "float", "dir", "repr", /* global functions from __builtins__ */ "list","dict","range", /* builtin classes */ "object","isinstance","type","tuple","reversed", "print","set","any","all","bool","ord","chr","hex","oct","filter", "sorted","bytes","getattr","sum","min","max","id","hash","map","bin", "enumerate","zip","setattr","property","staticmethod","classmethod", "issubclass","hasattr","delattr","NotImplemented","abs","slice","long", NULL }; static char * syn_krk_special[] = { "True","False","None", /* Exception names */ NULL }; static char * syn_krk_exception[] = { "Exception", "TypeError", "ArgumentError", "IndexError", "KeyError", "AttributeError", "NameError", "ImportError", "IOError", "ValueError", "KeyboardInterrupt", "ZeroDivisionError", "NotImplementedError", "SyntaxError", "AssertionError", "BaseException", "OSError", "SystemError", NULL }; static void paint_krk_string_shared(struct syntax_state * state, int type, int isFormat, int isTriple) { if (charat() == '\\') { if (nextchar() == 'x') { paintNHex(state, 2); } else if (nextchar() == 'u') { paintNHex(state, 4); } else if (nextchar() == 'U') { paintNHex(state, 8); } else if (nextchar() >= '0' && nextchar() <= '7') { paint(2, FLAG_ESCAPE); if (charat() >= '0' && charat() <= '7') { paint(1, FLAG_ESCAPE); if (charat() >= '0' && charat() <= '7') { paint(1, FLAG_ESCAPE); } } } else { paint(2, FLAG_ESCAPE); } } else if (isFormat && charat() == '{') { if (nextchar() == '{') { paint(2, FLAG_STRING); return; } paint(1, FLAG_ESCAPE); if (charat() == '}') { state->i--; paint(2, FLAG_ERROR); /* Can't do that. */ } else { int x = 0; while (charat() != -1) { if (charat() == '{') { x++; } else if (charat() == '}') { if (x == 0) { paint(1, FLAG_ESCAPE); break; } x--; } else if (charat() == type && !isTriple) { while (charat() != -1) { paint(1, FLAG_ERROR); } return; } else if (find_keywords(state, syn_krk_keywords, FLAG_ESCAPE, c_keyword_qualifier)) { continue; } else if (lastchar() != '.' && find_keywords(state, syn_krk_types, FLAG_TYPE, c_keyword_qualifier)) { continue; } else if (find_keywords(state, syn_krk_exception, FLAG_PRAGMA, c_keyword_qualifier)) { continue; } paint(1, FLAG_NUMERAL); } } } else { paint(1, FLAG_STRING); } } static void paint_krk_string(struct syntax_state * state, int type, int isFormat) { /* Assumes you came in from a check of charat() == '"' */ paint(1, FLAG_STRING); while (charat() != -1) { if (charat() == '\\' && nextchar() == type) { paint(2, FLAG_ESCAPE); } else if (charat() == type) { paint(1, FLAG_STRING); return; } else { paint_krk_string_shared(state,type,isFormat,0); } } } static int paint_krk_numeral(struct syntax_state * state) { if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { paint(2, FLAG_NUMERAL); while (isxdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); } else if (charat() == '0' && (nextchar() == 'o' || nextchar() == 'O')) { paint(2, FLAG_NUMERAL); while ((charat() >= '0' && charat() <= '7') || charat() == '_') paint(1, FLAG_NUMERAL); } else if (charat() == '0' && (nextchar() == 'b' || nextchar() == 'B')) { paint(2, FLAG_NUMERAL); while (charat() == '0' || charat() == '1' || charat() == '_') paint(1, FLAG_NUMERAL); } else { while (isdigit(charat()) || charat() == '_') paint(1, FLAG_NUMERAL); if (charat() == '.' && isdigit(nextchar())) { paint(1, FLAG_NUMERAL); while (isdigit(charat())) paint(1, FLAG_NUMERAL); } if (charat() == 'e' || charat() == 'E') { paint(1, FLAG_NUMERAL); if (charat() == '-' || charat() == '+') paint(1, FLAG_NUMERAL); while (isdigit(charat())) paint(1, FLAG_NUMERAL); } } return 0; } static int paint_krk_triple_string(struct syntax_state * state, int type, int isFormat) { while (charat() != -1) { if (charat() == '\\' && nextchar() == type) { paint(2, FLAG_ESCAPE); } else if (charat() == type) { paint(1, FLAG_STRING); if (charat() == type && nextchar() == type) { paint(2, FLAG_STRING); return 0; } } else { paint_krk_string_shared(state,type,isFormat,1); } } return (type == '"') ? 1 : 2; /* continues */ } static int syn_krk_calculate(struct syntax_state * state) { switch (state->state) { case -1: case 0: if (charat() == '#') { paint_comment(state); } else if (charat() == '@') { paint(1, FLAG_TYPE); while (c_keyword_qualifier(charat())) paint(1, FLAG_TYPE); return 0; } else if (charat() == '"' || charat() == '\'') { int isFormat = (lastchar() == 'f'); if (nextchar() == charat() && charrel(2) == charat()) { int type = charat(); paint(3, FLAG_STRING); return paint_krk_triple_string(state, type, isFormat); } else { paint_krk_string(state, charat(), isFormat); } return 0; } else if (find_keywords(state, syn_krk_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { return 0; } else if (lastchar() != '.' && find_keywords(state, syn_krk_types, FLAG_TYPE, c_keyword_qualifier)) { return 0; } else if (find_keywords(state, syn_krk_special, FLAG_NUMERAL, c_keyword_qualifier)) { return 0; } else if (find_keywords(state, syn_krk_exception, FLAG_PRAGMA, c_keyword_qualifier)) { return 0; } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { paint_krk_numeral(state); return 0; } else if (charat() != -1) { skip(); return 0; } break; /* rline doesn't support multiline editing anyway */ case 1: return paint_krk_triple_string(state, '"', 0); case 2: return paint_krk_triple_string(state, '\'', 0); } return -1; } static char * syn_krk_dbg_commands[] = { "s", "skip", "c", "continue", "q", "quit", "e", "enable", "d", "disable", "r", "remove", "bt", "backtrace", "break", "abort", "help", NULL, }; static char * syn_krk_dbg_info_types[] = { "breakpoints", NULL, }; static int syn_krk_dbg_calculate(struct syntax_state * state) { if (state->state < 1) { if (state->i == 0) { if (match_and_paint(state, "p", FLAG_KEYWORD, c_keyword_qualifier) || match_and_paint(state, "print", FLAG_KEYWORD, c_keyword_qualifier)) { while (1) { int result = syn_krk_calculate(state); if (result == 0) continue; if (result == -1) return -1; return result + 1; } } else if (match_and_paint(state,"info", FLAG_KEYWORD, c_keyword_qualifier) || match_and_paint(state,"i", FLAG_KEYWORD, c_keyword_qualifier)) { skip(); find_keywords(state,syn_krk_dbg_info_types, FLAG_TYPE, c_keyword_qualifier); return -1; } else if (find_keywords(state, syn_krk_dbg_commands, FLAG_KEYWORD, c_keyword_qualifier)) { return 0; } } return -1; } else { state->state -= 1; return syn_krk_calculate(state) + 1; } } #ifdef __toaru__ static int esh_variable_qualifier(int c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '_'); } static int paint_esh_variable(struct syntax_state * state) { if (charat() == '{') { paint(1, FLAG_TYPE); while (charat() != '}' && charat() != -1) paint(1, FLAG_TYPE); if (charat() == '}') paint(1, FLAG_TYPE); } else { if (charat() == '?' || charat() == '$' || charat() == '#') { paint(1, FLAG_TYPE); } else { while (esh_variable_qualifier(charat())) paint(1, FLAG_TYPE); } } return 0; } static int paint_esh_string(struct syntax_state * state) { int last = -1; while (charat() != -1) { if (last != '\\' && charat() == '"') { paint(1, FLAG_STRING); return 0; } else if (charat() == '$') { paint(1, FLAG_TYPE); paint_esh_variable(state); last = -1; } else if (charat() != -1) { last = charat(); paint(1, FLAG_STRING); } } return 2; } static int paint_esh_single_string(struct syntax_state * state) { int last = -1; while (charat() != -1) { if (last != '\\' && charat() == '\'') { paint(1, FLAG_STRING); return 0; } else if (charat() != -1) { last = charat(); paint(1, FLAG_STRING); } } return 1; } static int esh_keyword_qualifier(int c) { return (isalnum(c) || c == '?' || c == '_' || c == '-'); /* technically anything that isn't a space should qualify... */ } static char * esh_keywords[] = { "cd","exit","export","help","history","if","empty?", "equals?","return","export-cmd","source","exec","not","while", "then","else","echo", NULL }; static int syn_esh_calculate(struct syntax_state * state) { if (state->state == 1) { return paint_esh_single_string(state); } else if (state->state == 2) { return paint_esh_string(state); } if (charat() == '#') { while (charat() != -1) { if (common_comment_buzzwords(state)) continue; else paint(1, FLAG_COMMENT); } return -1; } else if (charat() == '$') { paint(1, FLAG_TYPE); paint_esh_variable(state); return 0; } else if (charat() == '\'') { paint(1, FLAG_STRING); return paint_esh_single_string(state); } else if (charat() == '"') { paint(1, FLAG_STRING); return paint_esh_string(state); } else if (match_and_paint(state, "export", FLAG_KEYWORD, esh_keyword_qualifier)) { while (charat() == ' ') skip(); while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); return 0; } else if (match_and_paint(state, "export-cmd", FLAG_KEYWORD, esh_keyword_qualifier)) { while (charat() == ' ') skip(); while (esh_keyword_qualifier(charat())) paint(1, FLAG_TYPE); return 0; } else if (find_keywords(state, esh_keywords, FLAG_KEYWORD, esh_keyword_qualifier)) { return 0; } else if (find_keywords(state, shell_commands, FLAG_KEYWORD, esh_keyword_qualifier)) { return 0; } else if (isdigit(charat())) { while (isdigit(charat())) paint(1, FLAG_NUMERAL); return 0; } else if (charat() != -1) { skip(); return 0; } return -1; } static char * syn_py_keywords[] = { "class","def","return","del","if","else","elif","for","while","continue", "break","assert","as","and","or","except","finally","from","global", "import","in","is","lambda","with","nonlocal","not","pass","raise","try","yield", NULL }; static char * syn_py_types[] = { /* built-in functions */ "abs","all","any","ascii","bin","bool","breakpoint","bytes", "bytearray","callable","compile","complex","delattr","chr", "dict","dir","divmod","enumerate","eval","exec","filter","float", "format","frozenset","getattr","globals","hasattr","hash","help", "hex","id","input","int","isinstance","issubclass","iter","len", "list","locals","map","max","memoryview","min","next","object", "oct","open","ord","pow","print","property","range","repr","reverse", "round","set","setattr","slice","sorted","staticmethod","str","sum", "super","tuple","type","vars","zip", NULL }; static char * syn_py_special[] = { "True","False","None", NULL }; static int paint_py_triple_double(struct syntax_state * state) { while (charat() != -1) { if (charat() == '"') { paint(1, FLAG_STRING); if (charat() == '"' && nextchar() == '"') { paint(2, FLAG_STRING); return 0; } } else { paint(1, FLAG_STRING); } } return 1; /* continues */ } static int paint_py_triple_single(struct syntax_state * state) { while (charat() != -1) { if (charat() == '\'') { paint(1, FLAG_STRING); if (charat() == '\'' && nextchar() == '\'') { paint(2, FLAG_STRING); return 0; } } else { paint(1, FLAG_STRING); } } return 2; /* continues */ } static int paint_py_single_string(struct syntax_state * state) { paint(1, FLAG_STRING); while (charat() != -1) { if (charat() == '\\' && nextchar() == '\'') { paint(2, FLAG_ESCAPE); } else if (charat() == '\'') { paint(1, FLAG_STRING); return 0; } else if (charat() == '\\') { paint(2, FLAG_ESCAPE); } else { paint(1, FLAG_STRING); } } return 0; } static int paint_py_numeral(struct syntax_state * state) { if (charat() == '0' && (nextchar() == 'x' || nextchar() == 'X')) { paint(2, FLAG_NUMERAL); while (isxdigit(charat())) paint(1, FLAG_NUMERAL); } else if (charat() == '0' && nextchar() == '.') { paint(2, FLAG_NUMERAL); while (isdigit(charat())) paint(1, FLAG_NUMERAL); if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { paint(2, FLAG_NUMERAL); while (isdigit(charat())) paint(1, FLAG_NUMERAL); } else if (charat() == 'e' || charat() == 'E') { paint(1, FLAG_NUMERAL); while (isdigit(charat())) paint(1, FLAG_NUMERAL); } if (charat() == 'j') paint(1, FLAG_NUMERAL); return 0; } else { while (isdigit(charat())) paint(1, FLAG_NUMERAL); if (charat() == '.') { paint(1, FLAG_NUMERAL); while (isdigit(charat())) paint(1, FLAG_NUMERAL); if ((charat() == '+' || charat() == '-') && (nextchar() == 'e' || nextchar() == 'E')) { paint(2, FLAG_NUMERAL); while (isdigit(charat())) paint(1, FLAG_NUMERAL); } else if (charat() == 'e' || charat() == 'E') { paint(1, FLAG_NUMERAL); while (isdigit(charat())) paint(1, FLAG_NUMERAL); } if (charat() == 'j') paint(1, FLAG_NUMERAL); return 0; } if (charat() == 'j') paint(1, FLAG_NUMERAL); } while (charat() == 'l' || charat() == 'L') paint(1, FLAG_NUMERAL); return 0; } static void paint_py_format_string(struct syntax_state * state, char type) { paint(1, FLAG_STRING); while (charat() != -1) { if (charat() == '\\' && nextchar() == type) { paint(2, FLAG_ESCAPE); } else if (charat() == type) { paint(1, FLAG_STRING); return; } else if (charat() == '\\') { paint(2, FLAG_ESCAPE); } else if (charat() == '{') { paint(1, FLAG_NUMERAL); if (charat() == '}') { state->i--; paint(2, FLAG_ERROR); /* Can't do that. */ } else { while (charat() != -1 && charat() != '}') { paint(1, FLAG_NUMERAL); } paint(1, FLAG_NUMERAL); } } else { paint(1, FLAG_STRING); } } } static void paint_simple_string(struct syntax_state * state) { /* Assumes you came in from a check of charat() == '"' */ paint(1, FLAG_STRING); while (charat() != -1) { if (charat() == '\\' && nextchar() == '"') { paint(2, FLAG_ESCAPE); } else if (charat() == '"') { paint(1, FLAG_STRING); return; } else if (charat() == '\\') { paint(2, FLAG_ESCAPE); } else { paint(1, FLAG_STRING); } } } static int syn_py_calculate(struct syntax_state * state) { switch (state->state) { case -1: case 0: if (charat() == '#') { paint_comment(state); } else if (state->i == 0 && match_and_paint(state, "import", FLAG_PRAGMA, c_keyword_qualifier)) { return 0; } else if (charat() == '@') { paint(1, FLAG_PRAGMA); while (c_keyword_qualifier(charat())) paint(1, FLAG_PRAGMA); return 0; } else if (charat() == '"') { if (nextchar() == '"' && charrel(2) == '"') { paint(3, FLAG_STRING); return paint_py_triple_double(state); } else if (lastchar() == 'f') { /* I don't like backtracking like this, but it makes this parse easier */ state->i--; paint(1,FLAG_TYPE); paint_py_format_string(state,'"'); return 0; } else { paint_simple_string(state); return 0; } } else if (find_keywords(state, syn_py_keywords, FLAG_KEYWORD, c_keyword_qualifier)) { return 0; } else if (lastchar() != '.' && find_keywords(state, syn_py_types, FLAG_TYPE, c_keyword_qualifier)) { return 0; } else if (find_keywords(state, syn_py_special, FLAG_NUMERAL, c_keyword_qualifier)) { return 0; } else if (charat() == '\'') { if (nextchar() == '\'' && charrel(2) == '\'') { paint(3, FLAG_STRING); return paint_py_triple_single(state); } else if (lastchar() == 'f') { /* I don't like backtracking like this, but it makes this parse easier */ state->i--; paint(1,FLAG_TYPE); paint_py_format_string(state,'\''); return 0; } else { return paint_py_single_string(state); } } else if (!c_keyword_qualifier(lastchar()) && isdigit(charat())) { paint_py_numeral(state); return 0; } else if (charat() != -1) { skip(); return 0; } break; case 1: /* multiline """ string */ return paint_py_triple_double(state); case 2: /* multiline ''' string */ return paint_py_triple_single(state); } return -1; } void * rline_exp_for_python(void * _stdin, void * _stdout, char * prompt) { rline_exp_set_prompts(prompt, "", strlen(prompt), 0); char * buf = malloc(1024); memset(buf, 0, 1024); rline_exp_set_syntax("python"); rline_exit_string = ""; rline(buf, 1024); rline_history_insert(strdup(buf)); rline_scroll = 0; return buf; } #endif void rline_redraw(rline_context_t * context) { if (context->quiet) return; printf("\033[u%s\033[K", context->buffer); for (int i = context->offset; i < context->collected; ++i) { printf("\033[D"); } fflush(stdout); } /** * Convert syntax hilighting flag to color code */ static const char * flag_to_color(int _flag) { int flag = _flag & 0xF; switch (flag) { case FLAG_KEYWORD: return COLOR_KEYWORD; case FLAG_STRING: return COLOR_STRING; case FLAG_COMMENT: return COLOR_COMMENT; case FLAG_TYPE: return COLOR_TYPE; case FLAG_NUMERAL: return COLOR_NUMERAL; case FLAG_PRAGMA: return COLOR_PRAGMA; case FLAG_DIFFPLUS: return COLOR_GREEN; case FLAG_DIFFMINUS: return COLOR_RED; case FLAG_BOLD: return COLOR_BOLD; case FLAG_LINK: return COLOR_LINK; case FLAG_ESCAPE: return COLOR_ESCAPE; default: return COLOR_FG; } } static struct syntax_definition { char * name; int (*calculate)(struct syntax_state *); int tabIndents; } syntaxes[] = { {"krk",syn_krk_calculate, 1}, {"krk-dbg",syn_krk_dbg_calculate, 1}, #ifdef __toaru__ {"python",syn_py_calculate, 1}, {"esh",syn_esh_calculate, 0}, #endif {NULL, NULL, 0}, }; static struct syntax_definition * syntax; int rline_exp_set_syntax(char * name) { if (!name) { syntax = NULL; return 0; } for (struct syntax_definition * s = syntaxes; s->name; ++s) { if (!strcmp(name,s->name)) { syntax = s; return 0; } } return 1; } /** * Syntax highlighting * Slimmed down from the bim implementation a bit, * but generally compatible with the same definitions. * * Type highlighting has been removed as the sh highlighter * didn't use it. This should be made pluggable again, and * the bim syntax highlighters should probably be broken * out into dynamically-loaded libraries? */ static void recalculate_syntax(line_t * line) { /* Clear syntax for this line first */ int line_no = 0; //int is_original = 1; while (1) { for (int i = 0; i < line->actual; ++i) { line->text[i].flags = 0; } if (!syntax) { return; } /* Start from the line's stored in initial state */ struct syntax_state state; state.line = line; state.line_no = line_no; state.state = line->istate; state.i = 0; while (1) { state.state = syntax->calculate(&state); if (state.state != 0) { /* TODO: Figure out a way to make this work for multiline input */ #if 0 if (line_no == -1) return; if (!is_original) { redraw_line(line_no); } if (line_no + 1 < env->line_count && env->lines[line_no+1]->istate != state.state) { line_no++; line = env->lines[line_no]; line->istate = state.state; if (env->loading) return; is_original = 0; goto _next; } #endif return; } } //_next: // (void)0; } } /** * Set colors */ static void set_colors(const char * fg, const char * bg) { printf("\033[22;23;"); if (*bg == '@') { int _bg = atoi(bg+1); if (_bg < 10) { printf("4%d;", _bg); } else { printf("10%d;", _bg-10); } } else { printf("48;%s;", bg); } if (*fg == '@') { int _fg = atoi(fg+1); if (_fg < 10) { printf("3%dm", _fg); } else { printf("9%dm", _fg-10); } } else { printf("38;%sm", fg); } fflush(stdout); } /** * Set just the foreground color * * (See set_colors above) */ static void set_fg_color(const char * fg) { printf("\033[22;23;"); if (*fg == '@') { int _fg = atoi(fg+1); if (_fg < 10) { printf("3%dm", _fg); } else { printf("9%dm", _fg-10); } } else { printf("38;%sm", fg); } fflush(stdout); } void rline_set_colors(rline_style_t style) { switch (style) { case RLINE_STYLE_MAIN: set_colors(COLOR_FG, COLOR_BG); break; case RLINE_STYLE_ALT: set_colors(COLOR_ALT_FG, COLOR_ALT_BG); break; case RLINE_STYLE_KEYWORD: set_fg_color(COLOR_KEYWORD); break; case RLINE_STYLE_STRING: set_fg_color(COLOR_STRING); break; case RLINE_STYLE_COMMENT: set_fg_color(COLOR_COMMENT); break; case RLINE_STYLE_TYPE: set_fg_color(COLOR_TYPE); break; case RLINE_STYLE_PRAGMA: set_fg_color(COLOR_PRAGMA); break; case RLINE_STYLE_NUMERAL: set_fg_color(COLOR_NUMERAL); break; } } /** * Mostly copied from bim, but with some minor * alterations and removal of selection support. */ static void render_line(void) { printf("\033[?25l"); if (show_left_side) { printf("\033[0m\r%s", prompt); } else { printf("\033[0m\r$"); } if (offset && prompt_width_calc) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("\b<"); } int i = 0; /* Offset in char_t line data entries */ int j = 0; /* Offset in terminal cells */ const char * last_color = NULL; int was_searching = 0; /* Set default text colors */ set_colors(COLOR_FG, COLOR_BG); /* * When we are rendering in the middle of a wide character, * we render -'s to fill the remaining amount of the * charater's width */ int remainder = 0; int is_spaces = 1; line_t * line = the_line; /* For each character in the line ... */ while (i < line->actual) { /* If there is remaining text... */ if (remainder) { /* If we should be drawing by now... */ if (j >= offset) { /* Fill remainder with -'s */ set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("-"); set_colors(COLOR_FG, COLOR_BG); } /* One less remaining width cell to fill */ remainder--; /* Terminal offset moves forward */ j++; /* * If this was the last remaining character, move to * the next codepoint in the line */ if (remainder == 0) { i++; } continue; } /* Get the next character to draw */ char_t c = line->text[i]; if (c.codepoint != ' ') is_spaces = 0; /* If we should be drawing by now... */ if (j >= offset) { /* If this character is going to fall off the edge of the screen... */ if (j - offset + c.display_width >= width - prompt_width_calc) { /* We draw this with special colors so it isn't ambiguous */ set_colors(COLOR_ALT_FG, COLOR_ALT_BG); /* If it's wide, draw ---> as needed */ while (j - offset < width - prompt_width_calc - 1) { printf("-"); j++; } /* End the line with a > to show it overflows */ printf(">"); set_colors(COLOR_FG, COLOR_BG); j++; break; } /* Syntax hilighting */ const char * color = flag_to_color(c.flags); if (c.flags & FLAG_SELECT) { set_colors(color, COLOR_BG); fprintf(stdout,"\033[7m"); was_searching = 1; } else if (c.flags == FLAG_NOTICE) { set_colors(COLOR_SEARCH_FG, COLOR_SEARCH_BG); was_searching = 1; } else if (c.flags == FLAG_ERROR) { set_colors(COLOR_ERROR_FG, COLOR_ERROR_BG); was_searching = 1; /* co-opting this should work... */ } else if (was_searching) { fprintf(stdout,"\033[0m"); set_colors(color, COLOR_BG); last_color = color; } else if (!last_color || strcmp(color, last_color)) { set_fg_color(color); last_color = color; } /* Render special characters */ if (c.codepoint == '\t') { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("»"); for (int i = 1; i < c.display_width; ++i) { printf("·"); } set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint < 32) { /* Codepoints under 32 to get converted to ^@ escapes */ set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("^%c", '@' + c.codepoint); set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint == 0x7f) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("^?"); set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint > 0x7f && c.codepoint < 0xa0) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("<%2x>", c.codepoint); set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.codepoint == 0xa0) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("_"); set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.display_width == 8) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("[U+%04x]", c.codepoint); set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else if (c.display_width == 10) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("[U+%06x]", c.codepoint); set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); #if 0 } else if (c.codepoint == ' ' && i == line->actual - 1) { /* Special case: space at end of line */ set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("·"); set_colors(COLOR_FG, COLOR_BG); #endif } else if (i > 0 && is_spaces && c.codepoint == ' ' && !(i % 4)) { set_colors(COLOR_ALT_FG, COLOR_BG); /* Normal background so this is more subtle */ printf("▏"); set_colors(last_color ? last_color : COLOR_FG, COLOR_BG); } else { /* Normal characters get output */ char tmp[7]; /* Max six bytes, use 7 to ensure last is always nil */ to_eight(c.codepoint, tmp); printf("%s", tmp); } /* Advance the terminal cell offset by the render width of this character */ j += c.display_width; /* Advance to the next character */ i++; } else if (c.display_width > 1) { /* * If this is a wide character but we aren't ready to render yet, * we may need to draw some filler text for the remainder of its * width to ensure we don't jump around when horizontally scrolling * past wide characters. */ remainder = c.display_width - 1; j++; } else { /* Regular character, not ready to draw, advance without doing anything */ j++; i++; } } printf("\033[0m"); set_colors(COLOR_FG, COLOR_BG); if (show_right_side && prompt_right_width) { /* Fill to end right hand side */ for (; j < width + offset - prompt_width_calc; ++j) { printf(" "); } /* Print right hand side */ printf("\033[0m%s", prompt_right); } else { printf("\033[0K"); } fflush(stdout); } /** * Create a line_t */ static line_t * line_create(void) { line_t * line = malloc(sizeof(line_t) + sizeof(char_t) * 32); line->available = 32; line->actual = 0; line->istate = 0; return line; } /** * Insert a character into a line */ static line_t * line_insert(line_t * line, char_t c, int offset) { /* If there is not enough space... */ if (line->actual == line->available) { /* Expand the line buffer */ line->available *= 2; line = realloc(line, sizeof(line_t) + sizeof(char_t) * line->available); } /* If this was not the last character, then shift remaining characters forward. */ if (offset < line->actual) { memmove(&line->text[offset+1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); } /* Insert the new character */ line->text[offset] = c; /* There is one new character in the line */ line->actual += 1; if (!loading) { recalculate_tabs(line); recalculate_syntax(line); } return line; } /** * Update terminal size * * We don't listen for sigwinch for various reasons... */ static void get_size(void) { #ifndef _WIN32 struct winsize w; ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); rline_terminal_width = w.ws_col; #else CONSOLE_SCREEN_BUFFER_INFO csbi; GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); rline_terminal_width = csbi.srWindow.Right - csbi.srWindow.Left + 1; #endif if (rline_terminal_width - prompt_right_width - prompt_width > MINIMUM_SIZE) { show_right_side = 1; show_left_side = 1; prompt_width_calc = prompt_width; width = rline_terminal_width - prompt_right_width; } else { show_right_side = 0; if (rline_terminal_width - prompt_width > MINIMUM_SIZE) { show_left_side = 1; prompt_width_calc = prompt_width; } else { show_left_side = 0; prompt_width_calc = 1; } width = rline_terminal_width; } } /** * Place the cursor within the line */ void rline_place_cursor(void) { int x = prompt_width_calc + 1 - offset; for (int i = 0; i < column; ++i) { char_t * c = &the_line->text[i]; x += c->display_width; } if (x > width - 1) { /* Adjust the offset appropriately to scroll horizontally */ int diff = x - (width - 1); offset += diff; x -= diff; render_line(); } /* Same for scrolling horizontally to the left */ if (x < prompt_width_calc + 1) { int diff = (prompt_width_calc + 1) - x; offset -= diff; x += diff; render_line(); } printf("\033[?25h\033[%dG", x); fflush(stdout); } /** * Delete a character */ static void line_delete(line_t * line, int offset) { /* Can't delete character before start of line. */ if (offset == 0) return; /* If this isn't the last character, we need to move all subsequent characters backwards */ if (offset < line->actual) { memmove(&line->text[offset-1], &line->text[offset], sizeof(char_t) * (line->actual - offset)); } /* The line is one character shorter */ line->actual -= 1; if (!loading) { recalculate_tabs(line); recalculate_syntax(line); } } /** * Backspace from the cursor position */ static void delete_at_cursor(void) { if (column > 0) { line_delete(the_line, column); column--; if (offset > 0) offset--; } } static void smart_backspace(void) { if (column > 0) { int i; for (i = 0; i < column; ++i) { if (the_line->text[i].codepoint != ' ') break; } if (i == column) { delete_at_cursor(); while (column > 0 && (column % 4)) delete_at_cursor(); return; } } delete_at_cursor(); } /** * Delete whole word */ static void delete_word(void) { if (!the_line->actual) return; if (!column) return; while (column > 0 && the_line->text[column-1].codepoint == ' ') { delete_at_cursor(); } do { if (column > 0) { delete_at_cursor(); } } while (column > 0 && the_line->text[column-1].codepoint != ' '); } /** * Insert at cursor position */ static void insert_char(uint32_t c) { char_t _c; _c.codepoint = c; _c.flags = 0; _c.display_width = codepoint_width(c); the_line = line_insert(the_line, _c, column); column++; } static char * paren_pairs = "()[]{}<>"; static int is_paren(int c) { char * p = paren_pairs; while (*p) { if (c == *p) return 1; p++; } return 0; } static void find_matching_paren(int * out_col, int in_col) { if (column - in_col > the_line->actual) { return; /* Invalid cursor position */ } int paren_match = 0; int direction = 0; int start = the_line->text[column-in_col].codepoint; int flags = the_line->text[column-in_col].flags & 0x1F; int count = 0; /* TODO what about unicode parens? */ for (int i = 0; paren_pairs[i]; ++i) { if (start == paren_pairs[i]) { direction = (i % 2 == 0) ? 1 : -1; paren_match = paren_pairs[(i % 2 == 0) ? (i+1) : (i-1)]; break; } } if (!paren_match) return; /* Scan for match */ int col = column - in_col; while (col > -1 && col < the_line->actual) { /* Only match on same syntax */ if ((the_line->text[col].flags & 0x1F) == flags) { /* Count up on same direction */ if (the_line->text[col].codepoint == start) count++; /* Count down on opposite direction */ if (the_line->text[col].codepoint == paren_match) { count--; /* When count == 0 we have a match */ if (count == 0) goto _match_found; } } col += direction; } _match_found: *out_col = col; } static void redraw_matching_paren(int col) { for (int j = 0; j < the_line->actual; ++j) { if (j == col) { the_line->text[j].flags |= FLAG_SELECT; } else { the_line->text[j].flags &= ~(FLAG_SELECT); } } } static void highlight_matching_paren(void) { int col = -1; if (column < the_line->actual && is_paren(the_line->text[column].codepoint)) { find_matching_paren(&col, 0); } else if (column > 0 && is_paren(the_line->text[column-1].codepoint)) { find_matching_paren(&col, 1); } redraw_matching_paren(col); } /** * Move cursor left */ static void cursor_left(void) { if (column > 0) column--; rline_place_cursor(); } /** * Move cursor right */ static void cursor_right(void) { if (column < the_line->actual) column++; rline_place_cursor(); } /** * Move cursor one whole word left */ static void word_left(void) { if (column == 0) return; column--; while (column && the_line->text[column].codepoint == ' ') { column--; } while (column > 0) { if (the_line->text[column-1].codepoint == ' ') break; column--; } rline_place_cursor(); } /** * Move cursor one whole word right */ static void word_right(void) { while (column < the_line->actual && the_line->text[column].codepoint == ' ') { column++; } while (column < the_line->actual) { column++; if (column < the_line->actual && the_line->text[column].codepoint == ' ') break; } rline_place_cursor(); } /** * Move cursor to start of line */ static void cursor_home(void) { column = 0; rline_place_cursor(); } /* * Move cursor to end of line */ static void cursor_end(void) { column = the_line->actual; rline_place_cursor(); } /** * Temporary buffer for holding utf-8 data */ static char temp_buffer[1024]; /** * Cycle to previous history entry */ static void history_previous(void) { if (rline_scroll == 0) { /* Convert to temporaary buffer */ unsigned int off = 0; memset(temp_buffer, 0, sizeof(temp_buffer)); for (int j = 0; j < the_line->actual; j++) { char_t c = the_line->text[j]; off += to_eight(c.codepoint, &temp_buffer[off]); } } if (rline_scroll < rline_history_count) { rline_scroll++; /* Copy in from history */ the_line->actual = 0; column = 0; loading = 1; unsigned char * buf = (unsigned char *)rline_history_prev(rline_scroll); uint32_t istate = 0, c = 0; for (unsigned int i = 0; i < strlen((char *)buf); ++i) { if (!decode(&istate, &c, buf[i])) { insert_char(c); } } loading = 0; } /* Set cursor at end */ column = the_line->actual; offset = 0; recalculate_tabs(the_line); recalculate_syntax(the_line); render_line(); rline_place_cursor(); } /** * Cycle to next history entry */ static void history_next(void) { if (rline_scroll >= 1) { unsigned char * buf; if (rline_scroll > 1) buf = (unsigned char *)rline_history_prev(rline_scroll-1); else buf = (unsigned char *)temp_buffer; rline_scroll--; /* Copy in from history */ the_line->actual = 0; column = 0; loading = 1; uint32_t istate = 0, c = 0; for (unsigned int i = 0; i < strlen((char *)buf); ++i) { if (!decode(&istate, &c, buf[i])) { insert_char(c); } } loading = 0; } /* Set cursor at end */ column = the_line->actual; offset = 0; recalculate_tabs(the_line); recalculate_syntax(the_line); render_line(); rline_place_cursor(); } /** * Handle escape sequences (arrow keys, etc.) */ static int handle_escape(int * this_buf, int * timeout, int c) { if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '\033') { this_buf[0]= c; *timeout = 1; return 1; } if (*timeout >= 1 && this_buf[*timeout-1] == '\033' && c == '[') { *timeout = 1; this_buf[*timeout] = c; (*timeout)++; return 0; } if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[' && (isdigit(c) || c == ';')) { this_buf[*timeout] = c; (*timeout)++; return 0; } if (*timeout >= 2 && this_buf[0] == '\033' && this_buf[1] == '[') { switch (c) { case 'A': // up history_previous(); break; case 'B': // down history_next(); break; case 'C': // right if (this_buf[*timeout-1] == '5') { word_right(); } else { cursor_right(); } break; case 'D': // left if (this_buf[*timeout-1] == '5') { word_left(); } else { cursor_left(); } break; case 'H': // home cursor_home(); break; case 'F': // end cursor_end(); break; case '~': switch (this_buf[*timeout-1]) { case '1': cursor_home(); break; case '3': /* Delete forward */ if (column < the_line->actual) { line_delete(the_line, column+1); if (offset > 0) offset--; } break; case '4': cursor_end(); break; } break; default: break; } *timeout = 0; return 0; } *timeout = 0; return 0; } #ifndef _WIN32 static unsigned int _INTR, _EOF; static struct termios old; static void set_unbuffered(void) { tcgetattr(STDOUT_FILENO, &old); _INTR = old.c_cc[VINTR]; _EOF = old.c_cc[VEOF]; struct termios new = old; new.c_lflag &= (~ICANON & ~ECHO & ~ISIG); tcsetattr(STDOUT_FILENO, TCSADRAIN, &new); if (wcwidth(0x3042) != 2) setlocale(LC_CTYPE, ""); } static void set_buffered(void) { tcsetattr(STDOUT_FILENO, TCSADRAIN, &old); } #else static unsigned int _INTR = 3; static unsigned int _EOF = 4; static void set_unbuffered(void) { /* Disables line input, echo, ^C processing, and a few others. */ SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_VIRTUAL_TERMINAL_INPUT); SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WRAP_AT_EOL_OUTPUT); setlocale(LC_CTYPE, "C.UTF-8"); } static void set_buffered(void) { /* These are the defaults */ SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), ENABLE_ECHO_INPUT | ENABLE_INSERT_MODE | ENABLE_LINE_INPUT | ENABLE_MOUSE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_QUICK_EDIT_MODE | ENABLE_VIRTUAL_TERMINAL_INPUT ); } #endif static int tabbed; static void dummy_redraw(rline_context_t * context) { /* Do nothing */ } /** * Juggle our buffer with an rline context so we can * call original rline functions such as a tab-completion callback * or reverse search. */ static void call_rline_func(rline_callback_t func, rline_context_t * context) { /* Unicode parser state */ uint32_t istate = 0; uint32_t c; /* Don't let rline draw things */ context->quiet = 1; /* Allocate a temporary buffer */ context->buffer = malloc(buf_size_max); memset(context->buffer,0,buf_size_max); /* Convert current data to utf-8 */ unsigned int off = 0; for (int j = 0; j < the_line->actual; j++) { if (j == column) { /* Track cursor position */ context->offset = off; } char_t c = the_line->text[j]; off += to_eight(c.codepoint, &context->buffer[off]); } /* If the cursor was at the end, the loop above didn't catch it */ if (column == the_line->actual) context->offset = off; /* * Did we just press tab before this? This is actually managed * by the tab-completion function. */ context->tabbed = tabbed; /* Empty callbacks */ rline_callbacks_t tmp = {0}; /* * Because some clients expect this to be set... * (we don't need it, we'll redraw ourselves later) */ tmp.redraw_prompt = dummy_redraw; /* Setup context */ context->callbacks = &tmp; context->collected = off; context->buffer[off] = '\0'; context->requested = 1024; /* Reset colors (for tab completion candidates, etc. */ printf("\033[0m"); /* Call the function */ func(context); /* Now convert back */ loading = 1; int final_column = 0; the_line->actual = 0; column = 0; istate = 0; for (int i = 0; i < context->collected; ++i) { if (i == context->offset) { final_column = column; } if (!decode(&istate, &c, ((unsigned char *)context->buffer)[i])) { insert_char(c); } } free(context->buffer); /* Position cursor */ if (context->offset == context->collected) { column = the_line->actual; } else { column = final_column; } tabbed = context->tabbed; loading = 0; /* Recalculate + redraw */ recalculate_tabs(the_line); recalculate_syntax(the_line); render_line(); rline_place_cursor(); } static int reverse_search(void) { /* Store state */ char * old_prompt = prompt; int old_prompt_width = prompt_width; int old_prompt_width_calc = prompt_width_calc; line_t * old_line = the_line; char buffer[1024] = {0}; unsigned int off = 0; the_line = NULL; prompt = "(r-search) "; prompt_width = strlen(prompt); prompt_width_calc = prompt_width; int cin, timeout = 0; uint32_t c = 0, istate = 0; int start_at = 0; int retval = 0; while (1) { _next: (void)0; off = 0; buffer[0] = '\0'; for (int j = 0; j < old_line->actual; j++) { buffer[off] = '\0'; char_t c = old_line->text[j]; off += to_eight(c.codepoint, &buffer[off]); } if (the_line) free(the_line); the_line = line_create(); int match_offset = 0; if (off) { for (int i = start_at; i < rline_history_count; ++i) { char * buf= rline_history_prev(i+1); char * match = strstr(buf, buffer); if (match) { match_offset = i; column = 0; loading = 1; uint32_t istate = 0, c = 0; int invert_start = 0; for (unsigned int i = 0; i < strlen((char *)buf); ++i) { if (match == &buf[i]) invert_start = the_line->actual; if (!decode(&istate, &c, buf[i])) { insert_char(c); } } loading = 0; offset = 0; recalculate_tabs(the_line); recalculate_syntax(the_line); for (int i = 0; i < old_line->actual; ++i) { the_line->text[invert_start+i].flags |= FLAG_SELECT; } column = invert_start; break; } } } render_line(); if (the_line->actual == 0) { offset = 0; column = 0; rline_place_cursor(); set_fg_color(COLOR_ALT_FG); printf("%s", buffer); fflush(stdout); } while ((cin = getch(timeout))) { if (cin == -1) continue; if (!decode(&istate, &c, cin)) { if (_INTR && c == _INTR) { goto _done; } switch (c) { case '\033': have_unget = '\033'; goto _done; case DELETE_KEY: case BACKSPACE_KEY: line_delete(old_line, old_line->actual); goto _next; case 13: case ENTER_KEY: retval = 1; goto _done; case 18: start_at = match_offset + 1; goto _next; default: { char_t _c; _c.codepoint = c; _c.flags = 0; _c.display_width = codepoint_width(c); old_line = line_insert(old_line, _c, old_line->actual); goto _next; } } } } } _done: free(old_line); prompt = old_prompt; prompt_width = old_prompt_width; prompt_width_calc = old_prompt_width_calc; offset = 0; render_line(); rline_place_cursor(); return retval; } /** * Perform actual interactive line editing. * * This is mostly a reimplementation of bim's * INSERT mode, but with some cleanups and fixes * to work on a single line and to add some new * key bindings we don't have in bim. */ static int read_line(void) { int cin; uint32_t c = 0; int timeout = 0; int this_buf[20]; uint32_t istate = 0; /* Let's disable this under Windows... */ set_colors(COLOR_ALT_FG, COLOR_ALT_BG); fprintf(stdout, "◄\033[0m"); /* TODO: This could be retrieved from an envvar */ for (int i = 0; i < rline_terminal_width - 1; ++i) { fprintf(stdout, " "); } if (rline_preload) { char * c = rline_preload; while (*c) { insert_char(*c); c++; } free(rline_preload); rline_preload = NULL; } render_line(); rline_place_cursor(); while ((cin = getch(timeout))) { if (cin == -1) continue; get_size(); if (!decode(&istate, &c, cin)) { if (timeout == 0) { if (c != '\t') tabbed = 0; if (_INTR && c == _INTR) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("^%c", (int)('@' + c)); printf("\033[0m"); loading = 1; the_line->actual = 0; column = 0; insert_char('\n'); raise(SIGINT); return 1; } if (_EOF && c == _EOF) { if (column == 0 && the_line->actual == 0) { for (char *_c = rline_exit_string; *_c; ++_c) { insert_char(*_c); } redraw_matching_paren(-1); render_line(); rline_place_cursor(); if (!*rline_exit_string) { set_colors(COLOR_ALT_FG, COLOR_ALT_BG); printf("^D\033[0m"); } return 1; } else { /* Otherwise act like delete */ if (column < the_line->actual) { line_delete(the_line, column+1); if (offset > 0) offset--; } continue; } } switch (c) { case '\033': if (timeout == 0) { this_buf[timeout] = c; timeout++; } break; case DELETE_KEY: case BACKSPACE_KEY: smart_backspace(); break; case 13: case ENTER_KEY: /* Finished */ loading = 1; column = the_line->actual; redraw_matching_paren(-1); render_line(); insert_char('\n'); return 1; case 1: /* ^A */ cursor_home(); break; case 5: /* ^E */ cursor_end(); break; case 22: /* ^V */ /* Don't bother with unicode, just take the next byte */ rline_place_cursor(); printf("^\b"); insert_char(getc(stdin)); break; case 23: /* ^W */ delete_word(); break; case 18: /* ^R - Begin reverse search */ if (reverse_search()) { loading = 1; column = the_line->actual; recalculate_syntax(the_line); render_line(); insert_char('\n'); return 1; } break; case 12: /* ^L - Repaint the whole screen */ printf("\033[2J\033[H"); render_line(); rline_place_cursor(); break; case 11: /* ^K - Clear to end */ the_line->actual = column; break; case 21: /* ^U - Kill to beginning */ while (column) { delete_at_cursor(); } break; case '\t': if ((syntax && syntax->tabIndents) && (column == 0 || the_line->text[column-1].codepoint == ' ')) { /* Insert tab character */ insert_char(' '); insert_char(' '); insert_char(' '); insert_char(' '); } else if (tab_complete_func) { /* Tab complete */ rline_context_t context = {0}; call_rline_func(tab_complete_func, &context); continue; } break; default: insert_char(c); break; } } else { if (handle_escape(this_buf,&timeout,c)) { render_line(); rline_place_cursor(); continue; } } highlight_matching_paren(); render_line(); rline_place_cursor(); } else if (istate == UTF8_REJECT) { istate = 0; } } return 0; } /** * Read a line of text with interactive editing. */ int rline(char * buffer, int buf_size) { set_unbuffered(); get_size(); column = 0; offset = 0; buf_size_max = buf_size; char * theme = getenv("RLINE_THEME"); if (theme && !strcmp(theme,"sunsmoke")) { /* TODO bring back theme tables */ rline_exp_load_colorscheme_sunsmoke(); } else { rline_exp_load_colorscheme_default(); } the_line = line_create(); loading = 0; read_line(); printf("\r\033[?25h\033[0m\n"); unsigned int off = 0; for (int j = 0; j < the_line->actual; j++) { char_t c = the_line->text[j]; off += to_eight(c.codepoint, &buffer[off]); } free(the_line); set_buffered(); return strlen(buffer); } void rline_insert(rline_context_t * context, const char * what) { size_t insertion_length = strlen(what); if (context->collected + (int)insertion_length > context->requested) { insertion_length = context->requested - context->collected; } /* Move */ memmove(&context->buffer[context->offset + insertion_length], &context->buffer[context->offset], context->collected - context->offset); memcpy(&context->buffer[context->offset], what, insertion_length); context->collected += insertion_length; context->offset += insertion_length; } ================================================ FILE: lib/rline_exp.c ================================================ /** * @brief Dummy library to provide rline to Python, but * our Python port is currently on hold. * * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * @copyright 2018-2021 K. Lange */ #include #include #include void * rline_exp_for_python(void * _stdin, void * _stdout, char * prompt) { rline_exp_set_prompts(prompt, "", strlen(prompt), 0); char * buf = malloc(1024); memset(buf, 0, 1024); rline_exp_set_syntax("python"); rline_exit_string = ""; rline(buf, 1024); rline_history_insert(strdup(buf)); rline_scroll = 0; return buf; } char * rline_exit_string; int rline_history_count; ================================================ FILE: lib/termemu.c ================================================ /** * @brief Terrible little ANSI escape parser. * * @copyright * 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-2018 K. Lange */ #include #include #include #include #include #include #include #define MAX_ARGS 1024 static wchar_t box_chars[] = L"▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥"; /* Returns the lower of two shorts */ static uint16_t min(uint16_t a, uint16_t b) { return (a < b) ? a : b; } /* Returns the higher of two shorts */ static uint16_t max(uint16_t a, uint16_t b) { return (a > b) ? a : b; } /* Write the contents of the buffer, as they were all non-escaped data. */ static void ansi_dump_buffer(term_state_t * s) { for (int i = 0; i < s->buflen; ++i) { s->callbacks->writer(s->buffer[i]); } } /* Add to the internal buffer for the ANSI parser */ static void ansi_buf_add(term_state_t * s, char c) { if (s->buflen >= TERM_BUF_LEN-1) return; s->buffer[s->buflen] = c; s->buflen++; s->buffer[s->buflen] = '\0'; } static int to_eight(uint32_t codepoint, char * out) { memset(out, 0x00, 7); if (codepoint < 0x0080) { out[0] = (char)codepoint; } else if (codepoint < 0x0800) { out[0] = 0xC0 | (codepoint >> 6); out[1] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x10000) { out[0] = 0xE0 | (codepoint >> 12); out[1] = 0x80 | ((codepoint >> 6) & 0x3F); out[2] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x200000) { out[0] = 0xF0 | (codepoint >> 18); out[1] = 0x80 | ((codepoint >> 12) & 0x3F); out[2] = 0x80 | ((codepoint >> 6) & 0x3F); out[3] = 0x80 | ((codepoint) & 0x3F); } else if (codepoint < 0x4000000) { out[0] = 0xF8 | (codepoint >> 24); out[1] = 0x80 | (codepoint >> 18); out[2] = 0x80 | ((codepoint >> 12) & 0x3F); out[3] = 0x80 | ((codepoint >> 6) & 0x3F); out[4] = 0x80 | ((codepoint) & 0x3F); } else { out[0] = 0xF8 | (codepoint >> 30); out[1] = 0x80 | ((codepoint >> 24) & 0x3F); out[2] = 0x80 | ((codepoint >> 18) & 0x3F); out[3] = 0x80 | ((codepoint >> 12) & 0x3F); out[4] = 0x80 | ((codepoint >> 6) & 0x3F); out[5] = 0x80 | ((codepoint) & 0x3F); } return strlen(out); } static void _ansi_put(term_state_t * s, char c) { term_callbacks_t * callbacks = s->callbacks; switch (s->escape) { case 0: /* We are not escaped, check for escape character */ if (c == ANSI_ESCAPE) { /* * Enable escape mode, setup a buffer, * fill the buffer, get out of here. */ s->escape = 1; s->buflen = 0; ansi_buf_add(s, c); return; } else if (c == 0) { return; } else { if (s->box && c >= 'a' && c <= 'z') { char buf[7]; char *w = (char *)&buf; to_eight(box_chars[c-'a'], w); while (*w) { callbacks->writer(*w); w++; } } else { callbacks->writer(c); } } break; case 1: /* We're ready for [ */ if (c == ANSI_BRACKET) { s->escape = 2; ansi_buf_add(s, c); } else if (c == ANSI_BRACKET_RIGHT) { s->escape = 3; ansi_buf_add(s, c); } else if (c == ANSI_OPEN_PAREN) { s->escape = 4; ansi_buf_add(s, c); } else if (c == 'T') { s->escape = 5; ansi_buf_add(s, c); } else if (c == '7') { s->escape = 0; s->buflen = 0; s->save_x = callbacks->get_csr_x(); s->save_y = callbacks->get_csr_y(); } else if (c == '8') { s->escape = 0; s->buflen = 0; callbacks->set_csr(s->save_x, s->save_y); } else if (c == 'c') { /* * "Full reset" * First, reset anything we own. Then call a callback * to inform the app to reset things it owns (buffers, cursors). */ s->escape = 0; s->buflen = 0; s->mouse_on = 0; s->save_x = 0; s->save_y = 0; s->box = 0; s->fg = TERM_DEFAULT_FG; s->bg = TERM_DEFAULT_BG; s->flags = TERM_DEFAULT_FLAGS; if (callbacks->full_reset) callbacks->full_reset(); } else { /* This isn't a bracket, we're not actually escaped! * Get out of here! */ ansi_dump_buffer(s); callbacks->writer(c); s->escape = 0; s->buflen = 0; return; } break; case 2: if (c >= ANSI_LOW && c <= ANSI_HIGH) { /* Woah, woah, let's see here. */ char * pch; /* tokenizer pointer */ char * save; /* strtok_r pointer */ char * argv[MAX_ARGS]; /* escape arguments */ /* Get rid of the front of the buffer */ strtok_r(s->buffer,"[",&save); pch = strtok_r(NULL,";",&save); /* argc = Number of arguments, obviously */ int argc = 0; while (pch != NULL) { argv[argc] = (char *)pch; ++argc; if (argc > MAX_ARGS) break; pch = strtok_r(NULL,";",&save); } /* Alright, let's do this */ switch (c) { case ANSI_EXT_IOCTL: { if (argc > 0) { int arg = atoi(argv[0]); switch (arg) { case 1: callbacks->redraw_cursor(); break; default: break; } } } break; case ANSI_SCP: { s->save_x = callbacks->get_csr_x(); s->save_y = callbacks->get_csr_y(); } break; case ANSI_RCP: { callbacks->set_csr(s->save_x, s->save_y); } break; case ANSI_SGR: /* Set Graphics Rendition */ if (argc == 0) { /* Default = 0 */ argv[0] = "0"; argc = 1; } for (int i = 0; i < argc; ++i) { int arg = atoi(argv[i]); if (arg >= 100 && arg < 110) { /* Bright background */ s->bg = 8 + (arg - 100); s->flags |= ANSI_SPECBG; } else if (arg >= 90 && arg < 100) { /* Bright foreground */ s->fg = 8 + (arg - 90); } else if (arg >= 40 && arg < 49) { /* Set background */ s->bg = arg - 40; s->flags |= ANSI_SPECBG; } else if (arg == 49) { s->bg = TERM_DEFAULT_BG; s->flags &= ~ANSI_SPECBG; } else if (arg >= 30 && arg < 39) { /* Set Foreground */ s->fg = arg - 30; } else if (arg == 39) { /* Default Foreground */ s->fg = 7; } else if (arg == 24) { /* Underline off */ s->flags &= ~ANSI_UNDERLINE; } else if (arg == 23) { /* Oblique off */ s->flags &= ~ANSI_ITALIC; } else if (arg == 21 || arg == 22) { /* Bold off */ s->flags &= ~ANSI_BOLD; } else if (arg == 9) { /* X-OUT */ s->flags |= ANSI_CROSS; } else if (arg == 7) { /* INVERT: Swap foreground / background */ uint32_t temp = s->fg; s->fg = s->bg; s->bg = temp; } else if (arg == 6) { /* proprietary RGBA color support */ if (i == 0) { break; } if (i < argc) { int r = atoi(argv[i+1]); int g = atoi(argv[i+2]); int b = atoi(argv[i+3]); int a = atoi(argv[i+4]); if (a == 0) a = 1; /* Override a = 0 */ uint32_t c = rgba(r,g,b,a); if (atoi(argv[i-1]) == 48) { s->bg = c; s->flags |= ANSI_SPECBG; } else if (atoi(argv[i-1]) == 38) { s->fg = c; } i += 4; } } else if (arg == 5) { /* Supposed to be blink; instead, support X-term 256 colors */ if (i == 0) { break; } if (i < argc) { if (atoi(argv[i-1]) == 48) { /* Background to i+1 */ s->bg = atoi(argv[i+1]); s->flags |= ANSI_SPECBG; } else if (atoi(argv[i-1]) == 38) { /* Foreground to i+1 */ s->fg = atoi(argv[i+1]); } ++i; } } else if (arg == 4) { /* UNDERLINE */ s->flags |= ANSI_UNDERLINE; } else if (arg == 3) { /* ITALIC: Oblique */ s->flags |= ANSI_ITALIC; } else if (arg == 2) { /* Konsole RGB color support */ if (i == 0) { break; } if (i < argc - 2) { int r = atoi(argv[i+1]); int g = atoi(argv[i+2]); int b = atoi(argv[i+3]); uint32_t c = rgb(r,g,b); if (atoi(argv[i-1]) == 48) { /* Background to i+1 */ s->bg = c; s->flags |= ANSI_SPECBG; } else if (atoi(argv[i-1]) == 38) { /* Foreground to i+1 */ s->fg = c; } i += 3; } } else if (arg == 1) { /* BOLD/BRIGHT: Brighten the output color */ s->flags |= ANSI_BOLD; } else if (arg == 0) { /* Reset everything */ s->fg = TERM_DEFAULT_FG; s->bg = TERM_DEFAULT_BG; s->flags = TERM_DEFAULT_FLAGS; } } break; case ANSI_SHOW: if (argc > 0) { if (!strcmp(argv[0], "?1049")) { if (callbacks->switch_buffer) callbacks->switch_buffer(1); } else if (!strcmp(argv[0], "?1000")) { s->mouse_on |= TERMEMU_MOUSE_ENABLE; } else if (!strcmp(argv[0], "?1002")) { s->mouse_on |= TERMEMU_MOUSE_DRAG; } else if (!strcmp(argv[0], "?1006")) { s->mouse_on |= TERMEMU_MOUSE_SGR; } else if (!strcmp(argv[0], "?25")) { callbacks->set_csr_on(1); } else if (!strcmp(argv[0], "?2004")) { s->paste_mode = 1; } } break; case ANSI_HIDE: if (argc > 0) { if (!strcmp(argv[0], "?1049")) { if (callbacks->switch_buffer) callbacks->switch_buffer(0); } else if (!strcmp(argv[0], "?1000")) { s->mouse_on &= ~TERMEMU_MOUSE_ENABLE; } else if (!strcmp(argv[0], "?1002")) { s->mouse_on &= ~TERMEMU_MOUSE_DRAG; } else if (!strcmp(argv[0],"?1006")) { s->mouse_on &= ~TERMEMU_MOUSE_SGR; } else if (!strcmp(argv[0], "?25")) { callbacks->set_csr_on(0); } else if (!strcmp(argv[0], "?2004")) { s->paste_mode = 0; } } break; case ANSI_CUF: { int i = 1; if (argc) { i = atoi(argv[0]); } callbacks->set_csr(min(callbacks->get_csr_x() + i, s->width - 1), callbacks->get_csr_y()); } break; case ANSI_CUU: { int i = 1; if (argc) { i = atoi(argv[0]); } callbacks->set_csr(callbacks->get_csr_x(), max(callbacks->get_csr_y() - i, 0)); } break; case ANSI_CUD: { int i = 1; if (argc) { i = atoi(argv[0]); } callbacks->set_csr(callbacks->get_csr_x(), min(callbacks->get_csr_y() + i, s->height - 1)); } break; case ANSI_CUB: { int i = 1; if (argc) { i = atoi(argv[0]); } callbacks->set_csr(max(callbacks->get_csr_x() - i,0), callbacks->get_csr_y()); } break; case ANSI_CHA: if (argc < 1) { callbacks->set_csr(0,callbacks->get_csr_y()); } else { callbacks->set_csr(min(max(atoi(argv[0]), 1), s->width) - 1, callbacks->get_csr_y()); } break; case ANSI_CUP: if (argc < 2) { callbacks->set_csr(0,0); } else { callbacks->set_csr(min(max(atoi(argv[1]), 1), s->width) - 1, min(max(atoi(argv[0]), 1), s->height) - 1); } break; case ANSI_ED: if (argc < 1) { callbacks->cls(0); } else { callbacks->cls(atoi(argv[0])); } break; case ANSI_EL: { int what = 0, x = 0, y = 0; if (argc >= 1) { what = atoi(argv[0]); } if (what == 0) { x = callbacks->get_csr_x(); y = s->width; } else if (what == 1) { x = 0; y = callbacks->get_csr_x(); } else if (what == 2) { x = 0; y = s->width; } for (int i = x; i < y; ++i) { callbacks->set_cell(i, callbacks->get_csr_y(), ' '); } } break; case ANSI_DSR: { char out[27]; sprintf(out, "\033[%d;%dR", callbacks->get_csr_y() + 1, callbacks->get_csr_x() + 1); callbacks->input_buffer_stuff(out); } break; case ANSI_SU: { int how_many = 1; if (argc > 0) { how_many = atoi(argv[0]); } callbacks->scroll(how_many); } break; case ANSI_SD: { int how_many = 1; if (argc > 0) { how_many = atoi(argv[0]); } callbacks->scroll(-how_many); } break; case ANSI_IL: { int how_many = 1; if (argc > 0) { how_many = atoi(argv[0]); } callbacks->insert_delete_lines(how_many); } break; case ANSI_DL: { int how_many = 1; if (argc > 0) { how_many = atoi(argv[0]); } callbacks->insert_delete_lines(-how_many); } break; case 'X': { int how_many = 1; if (argc > 0) { how_many = atoi(argv[0]); } for (int i = 0; i < how_many; ++i) { callbacks->writer(' '); } } break; case 'd': if (argc < 1) { callbacks->set_csr(callbacks->get_csr_x(), 0); } else { callbacks->set_csr(callbacks->get_csr_x(), atoi(argv[0]) - 1); } break; default: /* Meh */ break; } /* Set the states */ if (s->flags & ANSI_BOLD && s->fg < 9) { callbacks->set_color(s->fg % 8 + 8, s->bg); } else { callbacks->set_color(s->fg, s->bg); } /* Clear out the buffer */ s->buflen = 0; s->escape = 0; return; } else { /* Still escaped */ ansi_buf_add(s, c); } break; case 3: if (c == '\007') { /* Tokenize on semicolons, like we always do */ char * pch; /* tokenizer pointer */ char * save; /* strtok_r pointer */ char * argv[MAX_ARGS]; /* escape arguments */ /* Get rid of the front of the buffer */ strtok_r(s->buffer,"]",&save); pch = strtok_r(NULL,";",&save); /* argc = Number of arguments, obviously */ int argc = 0; while (pch != NULL) { argv[argc] = (char *)pch; ++argc; if (argc > MAX_ARGS) break; pch = strtok_r(NULL,";",&save); } /* Start testing the first argument for what command to use */ if (argv[0]) { if (!strcmp(argv[0], "1")) { if (argc > 1) { callbacks->set_title(argv[1]); } } /* Currently, no other options */ } /* Clear out the buffer */ s->buflen = 0; s->escape = 0; return; } else { /* Still escaped */ if (c == '\n' || s->buflen == 255) { ansi_dump_buffer(s); callbacks->writer(c); s->buflen = 0; s->escape = 0; return; } ansi_buf_add(s, c); } break; case 4: if (c == '0') { s->box = 1; } else if (c == 'B') { s->box = 0; } else { ansi_dump_buffer(s); callbacks->writer(c); } s->escape = 0; s->buflen = 0; break; case 5: if (c == 'q') { char out[24]; sprintf(out, "\033T%d;%dq", callbacks->get_cell_width(), callbacks->get_cell_height()); callbacks->input_buffer_stuff(out); s->escape = 0; s->buflen = 0; } else if (c == 's') { s->img_collected = 0; s->escape = 6; s->img_size = sizeof(uint32_t) * callbacks->get_cell_width() * callbacks->get_cell_height(); if (!s->img_data) { s->img_data = malloc(s->img_size); } memset(s->img_data, 0x00, s->img_size); } else { ansi_dump_buffer(s); callbacks->writer(c); s->escape = 0; s->buflen = 0; } break; case 6: s->img_data[s->img_collected++] = c; if (s->img_collected == s->img_size) { callbacks->set_cell_contents(callbacks->get_csr_x(), callbacks->get_csr_y(), s->img_data); callbacks->set_csr(min(callbacks->get_csr_x() + 1, s->width - 1), callbacks->get_csr_y()); s->escape = 0; s->buflen = 0; } break; } } void ansi_put(term_state_t * s, char c) { spin_lock(&s->lock); _ansi_put(s, c); spin_unlock(&s->lock); } term_state_t * ansi_init(term_state_t * s, int w, int y, term_callbacks_t * callbacks_in) { if (!s) { s = malloc(sizeof(term_state_t)); } memset(s, 0x00, sizeof(term_state_t)); /* Terminal Defaults */ s->fg = TERM_DEFAULT_FG; /* Light grey */ s->bg = TERM_DEFAULT_BG; /* Black */ s->flags = TERM_DEFAULT_FLAGS; /* Nothing fancy*/ s->width = w; s->height = y; s->box = 0; s->callbacks = callbacks_in; s->callbacks->set_color(s->fg, s->bg); s->mouse_on = 0; return s; } ================================================ FILE: lib/text.c ================================================ /** * @brief Toaru Text library - TrueType parser. * @file lib/text.c * @author K. Lange * * Implementation of TrueType font file parsing and basic * glyph rendering. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include "toaru/text.h" #undef min #define min(a,b) ((a) < (b) ? (a) : (b)) struct TT_Table { off_t offset; size_t length; }; struct TT_Coord { float x; float y; }; struct TT_Line { struct TT_Coord start; struct TT_Coord end; }; struct TT_Contour { size_t edgeCount; size_t nextAlloc; size_t flags; size_t last_start; struct TT_Line edges[]; }; struct TT_Intersection { float x; int affect; }; struct TT_Edge { struct TT_Coord start; struct TT_Coord end; int direction; }; struct TT_Shape { size_t edgeCount; int lastY; int startY; int lastX; int startX; struct TT_Edge edges[]; }; struct TT_Vertex { unsigned char flags; int x; int y; }; struct TT_Font { int privFlags; FILE * filePtr; uint8_t * buffer; uint8_t * memPtr; struct TT_Table head_ptr; struct TT_Table cmap_ptr; struct TT_Table loca_ptr; struct TT_Table glyf_ptr; struct TT_Table hhea_ptr; struct TT_Table hmtx_ptr; struct TT_Table name_ptr; struct TT_Table os_2_ptr; off_t cmap_start; size_t cmap_maxInd; float scale; float emSize; int cmap_type; int loca_type; }; /* Currently, the edge sorter is disabled. It doesn't really help much, * and it's very slow with our horrible qsort implementation. */ #if 0 static int edge_sorter_high_scanline(const void * a, const void * b) { const struct TT_Edge * left = a; const struct TT_Edge * right = b; if (left->start.y < right->start.y) return -1; if (left->start.y > right->start.y) return 1; return 0; } static void sort_edges(size_t edgeCount, struct TT_Edge edges[edgeCount]) { qsort(edges, edgeCount, sizeof(struct TT_Edge), edge_sorter_high_scanline); } #endif static int intersection_sorter(const void * a, const void * b) { const struct TT_Intersection * left = a; const struct TT_Intersection * right = b; if (left->x < right->x) return -1; if (left->x > right->x) return 1; return 0; } static inline void sort_intersections(size_t cnt, struct TT_Intersection intersections[cnt]) { qsort(intersections, cnt, sizeof(struct TT_Intersection), intersection_sorter); } static inline float edge_at(float y, const struct TT_Edge * edge) { float u = (y - edge->start.y) / (edge->end.y - edge->start.y); return edge->start.x + u * (edge->end.x - edge->start.x); } __attribute__((hot)) static inline size_t prune_edges(size_t edgeCount, float y, const struct TT_Edge edges[edgeCount], struct TT_Intersection into[edgeCount]) { size_t outWriter = 0; for (size_t i = 0; i < edgeCount; ++i) { if (y > edges[i].end.y || y <= edges[i].start.y) continue; into[outWriter].x = edge_at(y,&edges[i]); into[outWriter].affect = edges[i].direction; outWriter++; } return outWriter; } static void process_scanline( float _y, const struct TT_Shape * shape, size_t subsample_width, float subsamples[subsample_width], size_t cnt, const struct TT_Intersection crosses[cnt] ) { int wind = 0; size_t j = 0; for (int x = shape->startX; x < shape->lastX && j < cnt; ++x) { while (j < cnt && x > crosses[j].x) { wind += crosses[j].affect; j++; } float last = x; while (j < cnt && (x+1) > crosses[j].x) { if (wind != 0) { subsamples[x - shape->startX] += crosses[j].x - last; } last = crosses[j].x; wind += crosses[j].affect; j++; } if (wind != 0) { subsamples[x - shape->startX] += (x+1) - last; } } } static inline uint32_t tt_rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { return (a << 24U) | (r << 16) | (g << 8) | (b); } static inline uint32_t tt_apply_alpha(uint32_t color, uint16_t alpha) { uint8_t r = ((uint32_t)(_RED(color) * alpha + 0x80) * 0x101) >> 16UL; uint8_t g = ((uint32_t)(_GRE(color) * alpha + 0x80) * 0x101) >> 16UL; uint8_t b = ((uint32_t)(_BLU(color) * alpha + 0x80) * 0x101) >> 16UL; uint8_t a = ((uint32_t)(_ALP(color) * alpha + 0x80) * 0x101) >> 16UL; return tt_rgba(r,g,b,a); } static inline uint32_t tt_alpha_blend_rgba(uint32_t bottom, uint32_t top) { if (_ALP(bottom) == 0) return top; if (_ALP(top) == 255) return top; if (_ALP(top) == 0) return bottom; uint8_t a = _ALP(top); uint16_t t = 0xFF ^ a; uint8_t d_r = _RED(top) + (((uint32_t)(_RED(bottom) * t + 0x80) * 0x101) >> 16UL); uint8_t d_g = _GRE(top) + (((uint32_t)(_GRE(bottom) * t + 0x80) * 0x101) >> 16UL); uint8_t d_b = _BLU(top) + (((uint32_t)(_BLU(bottom) * t + 0x80) * 0x101) >> 16UL); uint8_t d_a = _ALP(top) + (((uint32_t)(_ALP(bottom) * t + 0x80) * 0x101) >> 16UL); return tt_rgba(d_r, d_g, d_b, d_a); } static void paint_scanline(gfx_context_t * ctx, int y, const struct TT_Shape * shape, float * subsamples, uint32_t color) { for (int x = shape->startX < 0 ? 0 : shape->startX; x < shape->lastX && x < ctx->width; ++x) { uint16_t na = (int)(255 * subsamples[x - shape->startX]) >> 2; uint32_t nc = tt_apply_alpha(color, na); GFX(ctx, x, y) = tt_alpha_blend_rgba(GFX(ctx, x, y), nc); subsamples[x-shape->startX] = 0; } } static inline int _is_in_clip(gfx_context_t * ctx, int32_t y) { if (!ctx->clips) return 1; if (y < 0 || y >= ctx->clips_size) return 1; return ctx->clips[y]; } void tt_path_paint(gfx_context_t * ctx, const struct TT_Shape * shape, uint32_t color) { size_t size = shape->edgeCount; struct TT_Intersection * crosses = malloc(sizeof(struct TT_Intersection) * size); size_t subsample_width = shape->lastX - shape->startX; float * subsamples = malloc(sizeof(float) * subsample_width); memset(subsamples, 0, sizeof(float) * subsample_width); int startY = shape->startY < 0 ? 0 : shape->startY; int endY = shape->lastY <= ctx->height ? shape->lastY : ctx->height; for (int y = startY; y < endY; ++y) { if (!_is_in_clip(ctx,y)) continue; float _y = y + 0.0001; for (int l = 0; l < 4; ++l) { size_t cnt; if ((cnt = prune_edges(size, _y, shape->edges, crosses))) { sort_intersections(cnt, crosses); process_scanline(_y, shape, subsample_width, subsamples, cnt, crosses); } _y += 1.0/4.0; } paint_scanline(ctx, y, shape, subsamples, color); } free(subsamples); free(crosses); } struct TT_Contour * tt_contour_line_to(struct TT_Contour * shape, float x, float y) { if (shape->flags & 1) { shape->edges[shape->edgeCount].end.x = x; shape->edges[shape->edgeCount].end.y = y; shape->edgeCount++; shape->flags &= ~1; } else { if (shape->edgeCount + 1 == shape->nextAlloc) { shape->nextAlloc *= 2; shape = realloc(shape, sizeof(struct TT_Contour) + sizeof(struct TT_Line) * (shape->nextAlloc)); } shape->edges[shape->edgeCount].start.x = shape->edges[shape->edgeCount-1].end.x; shape->edges[shape->edgeCount].start.y = shape->edges[shape->edgeCount-1].end.y; shape->edges[shape->edgeCount].end.x = x; shape->edges[shape->edgeCount].end.y = y; shape->edgeCount++; shape->flags &= ~1; } return shape; } struct TT_Contour * tt_contour_move_to(struct TT_Contour * shape, float x, float y) { if (!(shape->flags & 1) && shape->edgeCount) { shape = tt_contour_line_to(shape, shape->edges[shape->last_start].start.x, shape->edges[shape->last_start].start.y); } if (shape->edgeCount + 1 == shape->nextAlloc) { shape->nextAlloc *= 2; shape = realloc(shape, sizeof(struct TT_Contour) + sizeof(struct TT_Line) * (shape->nextAlloc)); } shape->edges[shape->edgeCount].start.x = x; shape->edges[shape->edgeCount].start.y = y; shape->last_start = shape->edgeCount; shape->flags |= 1; return shape; } struct TT_Contour * tt_contour_start(float x, float y) { struct TT_Contour * shape = malloc(sizeof(struct TT_Contour) + sizeof(struct TT_Line) * 2); shape->edgeCount = 0; shape->nextAlloc = 2; shape->flags = 0; shape->last_start = 0; shape->edges[shape->edgeCount].start.x = x; shape->edges[shape->edgeCount].start.y = y; shape->last_start = shape->edgeCount; shape->flags |= 1; return shape; } struct TT_Shape * tt_contour_finish(const struct TT_Contour * in) { size_t size = in->edgeCount + 1; struct TT_Shape * tmp = malloc(sizeof(struct TT_Shape) + sizeof(struct TT_Edge) * size); for (size_t i = 0; i < in->edgeCount; ++i) { memcpy(&tmp->edges[i], &in->edges[i], sizeof(struct TT_Line)); } if (in->flags & 1) { size--; } else { tmp->edges[in->edgeCount].start.x = in->edges[in->edgeCount-1].end.x; tmp->edges[in->edgeCount].start.y = in->edges[in->edgeCount-1].end.y; tmp->edges[in->edgeCount].end.x = in->edges[in->last_start].start.x; tmp->edges[in->edgeCount].end.y = in->edges[in->last_start].start.y; } for (size_t i = 0; i < size; ++i) { if (tmp->edges[i].start.y < tmp->edges[i].end.y) { tmp->edges[i].direction = 1; } else { tmp->edges[i].direction = -1; struct TT_Coord j = tmp->edges[i].start; tmp->edges[i].start = tmp->edges[i].end; tmp->edges[i].end = j; } } //sort_edges(size, tmp->edges); tmp->edgeCount = size; tmp->startY = INT_MAX; tmp->lastY = INT_MIN; tmp->startX = INT_MAX; tmp->lastX = INT_MIN; for (size_t i = 0; i < size; ++i) { if (tmp->edges[i].end.y + 1 > tmp->lastY) tmp->lastY = tmp->edges[i].end.y + 1; if (tmp->edges[i].start.y + 1 > tmp->lastY) tmp->lastY = tmp->edges[i].start.y + 1; if (tmp->edges[i].end.y < tmp->startY) tmp->startY = tmp->edges[i].end.y; if (tmp->edges[i].start.y < tmp->startY) tmp->startY = tmp->edges[i].start.y; if (tmp->edges[i].end.x + 2 > tmp->lastX) tmp->lastX = tmp->edges[i].end.x + 2; if (tmp->edges[i].start.x + 2 > tmp->lastX) tmp->lastX = tmp->edges[i].start.x + 2; if (tmp->edges[i].end.x < tmp->startX) tmp->startX = tmp->edges[i].end.x; if (tmp->edges[i].start.x < tmp->startX) tmp->startX = tmp->edges[i].start.x; } if (tmp->lastY < tmp->startY) tmp->startY = tmp->lastY; if (tmp->lastX < tmp->startX) tmp->startX = tmp->lastX; return tmp; } static inline int tt_seek(struct TT_Font * font, off_t offset) { if (font->privFlags & 1) { return fseek(font->filePtr, offset, SEEK_SET); } else { font->memPtr = font->buffer + offset; return 0; } } static inline long tt_tell(struct TT_Font * font) { if (font->privFlags & 1) { return ftell(font->filePtr); } else { return font->memPtr - font->buffer; } } static inline uint8_t tt_read_8(struct TT_Font * font) { if (font->privFlags & 1) { return fgetc(font->filePtr); } else { return *(font->memPtr++); } } static inline uint32_t tt_read_32(struct TT_Font * font) { int a = tt_read_8(font); int b = tt_read_8(font); int c = tt_read_8(font); int d = tt_read_8(font); if (a < 0 || b < 0 || c < 0 || d < 0) return 0; return ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) | ((d & 0xFF) << 0); } static inline uint16_t tt_read_16(struct TT_Font * font) { int a = tt_read_8(font); int b = tt_read_8(font); if (a < 0 || b < 0) return 0; return ((a & 0xFF) << 8) | ((b & 0xFF) << 0); } int tt_measure_font(struct TT_Font * font, struct TT_FontMetrics * metrics) { int a, d, l; if (font->os_2_ptr.offset) { tt_seek(font, font->os_2_ptr.offset + 2 * 37); a = (int16_t)tt_read_16(font); d = -(int16_t)tt_read_16(font); tt_seek(font, font->hhea_ptr.offset + 2 * 4); l = (int16_t)tt_read_16(font); } else { tt_seek(font, font->hhea_ptr.offset + 2 * 2); a = (int16_t)tt_read_16(font); d = (int16_t)tt_read_16(font); l = (int16_t)tt_read_16(font); } metrics->ascender = a * font->scale; metrics->descender = d * font->scale; metrics->lineGap = l * font->scale; return 0; } int tt_xadvance_for_glyph(struct TT_Font * font, unsigned int ind) { tt_seek(font, font->hhea_ptr.offset + 2 * 17); uint16_t numLong = tt_read_16(font); if (ind < numLong) { tt_seek(font, font->hmtx_ptr.offset + ind * 4); return tt_read_16(font); } tt_seek(font, font->hmtx_ptr.offset + (numLong - 1) * 4); return tt_read_16(font); } void tt_set_size(struct TT_Font * font, float size) { font->scale = size / font->emSize; } void tt_set_size_px(struct TT_Font * font, float size) { tt_set_size(font, size * 4.0 / 3.0); } off_t tt_get_glyph_offset(struct TT_Font * font, unsigned int glyph) { if (font->loca_type == 0) { tt_seek(font, font->loca_ptr.offset + glyph * 2); return tt_read_16(font) * 2; } else { tt_seek(font, font->loca_ptr.offset + glyph * 4); return tt_read_32(font); } } int tt_glyph_for_codepoint(struct TT_Font * font, unsigned int codepoint) { if (font->cmap_type == 12) { /* Get group count */ tt_seek(font, font->cmap_start + 4 + 8); uint32_t ngroups = tt_read_32(font); for (unsigned int i = 0; i < ngroups; ++i) { uint32_t start = tt_read_32(font); uint32_t end = tt_read_32(font); uint32_t ind = tt_read_32(font); if (codepoint >= start && codepoint <= end) { return ind + (codepoint - start); } } } else if (font->cmap_type == 4) { if (codepoint > 0xFFFF) return 0; tt_seek(font, font->cmap_start + 6); uint16_t segCount = tt_read_16(font) / 2; for (int i = 0; i < segCount; ++i) { tt_seek(font, font->cmap_start + 12 + 2 * i); uint16_t endCode = tt_read_16(font); if (endCode >= codepoint) { tt_seek(font, font->cmap_start + 12 + 2 * segCount + 2 + 2 * i); uint16_t startCode = tt_read_16(font); if (startCode > codepoint) { return 0; } tt_seek(font, font->cmap_start + 12 + 4 * segCount + 2 + 2 * i); int16_t idDelta = tt_read_16(font); tt_seek(font, font->cmap_start + 12 + 6 * segCount + 2 + 2 * i); uint16_t idRangeOffset = tt_read_16(font); if (idRangeOffset == 0) { return idDelta + codepoint; } else { tt_seek(font, font->cmap_start + 12 + 6 * segCount + 2 + 2 * i + idRangeOffset + (codepoint - startCode) * 2); return tt_read_16(font); } } } } return 0; } static void midpoint(float x_0, float y_0, float cx, float cy, float x_1, float y_1, float t, float * outx, float * outy) { float t2 = t * t; float nt = 1.0 - t; float nt2 = nt * nt; *outx = nt2 * x_0 + 2 * t * nt * cx + t2 * x_1; *outy = nt2 * y_0 + 2 * t * nt * cy + t2 * y_1; } __attribute__((visibility("protected"))) struct TT_Contour * tt_draw_glyph_into(struct TT_Contour * contour, struct TT_Font * font, float x_offset, float y_offset, unsigned int glyph) { off_t glyf_offset = tt_get_glyph_offset(font, glyph); if (tt_get_glyph_offset(font, glyph + 1) == glyf_offset) return contour; tt_seek(font, font->glyf_ptr.offset + glyf_offset); int16_t numContours = tt_read_16(font); /* int16_t xMin = */ tt_read_16(font); /* int16_t yMin = */ tt_read_16(font); /* int16_t xMax = */ tt_read_16(font); /* int16_t yMax = */ tt_read_16(font); tt_seek(font, font->glyf_ptr.offset + glyf_offset + 10); if (numContours > 0) { uint16_t endPt; for (int i = 0; i < numContours; ++i) { endPt = tt_read_16(font); } uint16_t numInstr = tt_read_16(font); for (unsigned int i = 0; i < numInstr; ++i) { tt_read_8(font); } struct TT_Vertex * vertices = malloc(sizeof(struct TT_Vertex) * (endPt + 1)); for (int i = 0; i < endPt + 1; ) { uint8_t v = tt_read_8(font); vertices[i].flags = v; i++; if (v & 8) { uint8_t repC = tt_read_8(font); while (repC) { vertices[i].flags = v; repC--; i++; } } } int last_x = 0; int last_y = 0; for (int i = 0; i < endPt + 1; i++) { unsigned char flags = vertices[i].flags; if (flags & (1 << 1)) { /* One byte */ if (flags & (1 << 4)) { /* Positive */ vertices[i].x = last_x + tt_read_8(font); } else { vertices[i].x = last_x - tt_read_8(font); } } else { if (flags & (1 << 4)) { vertices[i].x = last_x; } else { int16_t diff = tt_read_16(font); vertices[i].x = last_x + diff; } } last_x = vertices[i].x; } for (int i = 0; i < endPt + 1; i++) { unsigned char flags = vertices[i].flags; if (flags & (1 << 2)) { /* One byte */ if (flags & (1 << 5)) { /* Positive */ vertices[i].y = last_y + tt_read_8(font); } else { vertices[i].y = last_y - tt_read_8(font); } } else { if (flags & (1 << 5)) { vertices[i].y = last_y; } else { int16_t diff = tt_read_16(font); vertices[i].y = last_y + diff; } } last_y = vertices[i].y; } tt_seek(font, font->glyf_ptr.offset + glyf_offset + 10); int move_next = 1; int next_end = tt_read_16(font); float lx = 0, ly = 0, cx = 0, cy = 0, x = 0, y = 0; float sx = 0, sy = 0; int wasControl = 0; for (int i = 0; i < endPt + 1; ++i) { x = ((float)vertices[i].x) * font->scale + x_offset; y = (-(float)vertices[i].y) * font->scale + y_offset; int isCurve = !(vertices[i].flags & (1 << 0)); if (move_next) { contour = tt_contour_move_to(contour, x, y); if (isCurve) { /* Is the point before this on-curve? */ float px = (float)vertices[next_end].x * font->scale + x_offset; float py = (-(float)vertices[next_end].y) * font->scale + y_offset; if (vertices[next_end].flags & (1 << 0)) { /* Else we're just a regular off-curve point? */ sx = px; sy = py; lx = px; ly = py; } else { float dx = (px + x) / 2.0; float dy = (py + y) / 2.0; lx = dx; ly = dy; sx = dx; sy = dy; } cx = x; cy = y; wasControl = 1; } else { lx = x; ly = y; sx = x; sy = y; wasControl = 0; } move_next = 0; } else { if (isCurve) { if (wasControl) { float dx = (cx + x) / 2.0; float dy = (cy + y) / 2.0; for (int i = 1; i < 10; ++i) { float mx, my; midpoint(lx,ly,cx,cy,dx,dy,(float)i / 10.0,&mx,&my); contour = tt_contour_line_to(contour, mx, my); } contour = tt_contour_line_to(contour, dx, dy); lx = dx; ly = dy; } cx = x; cy = y; wasControl = 1; } else { if (wasControl) { for (int i = 1; i < 10; ++i) { float mx, my; midpoint(lx,ly,cx,cy,x,y,(float)i / 10.0,&mx,&my); contour = tt_contour_line_to(contour, mx, my); } } contour = tt_contour_line_to(contour, x, y); lx = x; ly = y; wasControl = 0; } } if (i == next_end) { if (wasControl) { for (int i = 1; i < 10; ++i) { float mx, my; midpoint(lx,ly,cx,cy,sx,sy,(float)i / 10.0,&mx,&my); contour = tt_contour_line_to(contour, mx, my); } } contour = tt_contour_line_to(contour, sx, sy); move_next = 1; next_end = tt_read_16(font); } } free(vertices); } else if (numContours < 0) { while (1) { uint16_t flags = tt_read_16(font); uint16_t ind = tt_read_16(font); int16_t x, y; if (flags & (1 << 0)) { x = tt_read_16(font); y = tt_read_16(font); } else { x = tt_read_8(font); y = tt_read_8(font); } float x_f = x_offset; float y_f = y_offset; if (flags & (1 << 1)) { x_f = x_offset + x * font->scale; y_f = y_offset - y * font->scale; } if (flags & (1 << 3)) { /* TODO */ tt_read_16(font); } else if (flags & (1 << 6)) { /* TODO */ tt_read_16(font); tt_read_16(font); } else if (flags & (1 << 7)) { /* TODO */ tt_read_16(font); tt_read_16(font); tt_read_16(font); tt_read_16(font); } else { long o = tt_tell(font); contour = tt_draw_glyph_into(contour,font,x_f,y_f,ind); tt_seek(font, o); } if (!(flags & (1 << 5))) break; } } return contour; } sprite_t * tt_bake_glyph(struct TT_Font * font, unsigned int glyph, uint32_t color, int *_x, int *_y, float xadjust) { struct TT_Contour * contour = tt_contour_start(0, 0); contour = tt_draw_glyph_into(contour,font,100+xadjust,100,glyph); if (!contour->edgeCount) { *_x = 0; *_y = 0; free(contour); return NULL; } /* Calculate bounds to render a sprite */ struct TT_Shape * shape = tt_contour_finish(contour); int width = shape->lastX - shape->startX + 3; int height = shape->lastY - shape->startY + 2; int off_x = shape->startX - 1; shape->startX -= off_x; shape->lastX -= off_x; int off_y = shape->startY - 1; shape->startY -= off_y; shape->lastY -= off_y; /* Adjust the entire shape */ for (size_t i = 0; i < shape->edgeCount; ++i) { shape->edges[i].start.x -= off_x; shape->edges[i].end.x -= off_x; shape->edges[i].start.y -= off_y; shape->edges[i].end.y -= off_y; } *_x = off_x - 100; *_y = off_y - 100; /* Create sprite */ sprite_t * out = create_sprite(width,height,ALPHA_EMBEDDED); gfx_context_t * ctx = init_graphics_sprite(out); /* Fill to clear */ draw_fill(ctx, 0); tt_path_paint(ctx, shape, color); free(ctx); free(shape); free(contour); return out; } void tt_draw_glyph(gfx_context_t * ctx, struct TT_Font * font, int x, int y, unsigned int glyph, uint32_t color) { struct TT_Contour * contour = tt_contour_start(0, 0); contour = tt_draw_glyph_into(contour,font,x,y,glyph); if (contour->edgeCount) { struct TT_Shape * shape = tt_contour_finish(contour); tt_path_paint(ctx, shape, color); free(shape); } free(contour); } int tt_string_width(struct TT_Font * font, const char * s) { float x_offset = 0; uint32_t cp = 0; uint32_t istate = 0; for (const unsigned char * c = (const unsigned char*)s; *c; ++c) { if (!decode(&istate, &cp, *c)) { unsigned int glyph = tt_glyph_for_codepoint(font, cp); x_offset += tt_xadvance_for_glyph(font, glyph) * font->scale; } } return x_offset; } int tt_string_width_int(struct TT_Font * font, const char * s) { int x_offset = 0; uint32_t cp = 0; uint32_t istate = 0; for (const unsigned char * c = (const unsigned char*)s; *c; ++c) { if (!decode(&istate, &cp, *c)) { unsigned int glyph = tt_glyph_for_codepoint(font, cp); x_offset += tt_xadvance_for_glyph(font, glyph) * font->scale; } } return x_offset; } float tt_glyph_width(struct TT_Font * font, unsigned int glyph) { return tt_xadvance_for_glyph(font, glyph) * font->scale; } __attribute__((visibility("protected"))) 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) { if (contour == NULL) { contour = tt_contour_start(0, 0); } float x_offset = x; uint32_t cp = 0; uint32_t istate = 0; for (const unsigned char * c = (const unsigned char*)s; *c; ++c) { if (!decode(&istate, &cp, *c)) { unsigned int glyph = tt_glyph_for_codepoint(font, cp); contour = tt_draw_glyph_into(contour,font,x_offset,y,glyph); x_offset += tt_xadvance_for_glyph(font, glyph) * font->scale; } } if (out_width) *out_width = x_offset - x; return contour; } __attribute__((visibility("protected"))) struct TT_Contour * tt_prepare_string(struct TT_Font * font, float x, float y, const char * s, float * out_width) { return tt_prepare_string_into(NULL, font, x, y, s, out_width); } int tt_draw_string(gfx_context_t * ctx, struct TT_Font * font, int x, int y, const char * s, uint32_t color) { float width; struct TT_Contour * contour = tt_prepare_string(font,x,y,s,&width); if (contour->edgeCount) { struct TT_Shape * shape = tt_contour_finish(contour); tt_path_paint(ctx, shape, color); free(shape); } free(contour); return width; } static int tt_font_load(struct TT_Font * font) { if (tt_seek(font, 4)) { fprintf(stderr, "tt: failed to seek to 4\n"); goto _fail_free; } uint16_t numTables = tt_read_16(font); if (tt_seek(font, 12)) { fprintf(stderr, "tt: failed to seek to 12\n"); goto _fail_free; } for (unsigned int i = 0; i < numTables; ++i) { uint32_t tag = tt_read_32(font); /* uint32_t checkSum = */ tt_read_32(font); uint32_t offset = tt_read_32(font); uint32_t length = tt_read_32(font); switch (tag) { case 0x68656164: /* head */ font->head_ptr.offset = offset; font->head_ptr.length = length; break; case 0x636d6170: /* cmap */ font->cmap_ptr.offset = offset; font->cmap_ptr.length = length; break; case 0x676c7966: /* glyf */ font->glyf_ptr.offset = offset; font->glyf_ptr.length = length; break; case 0x6c6f6361: /* loca */ font->loca_ptr.offset = offset; font->loca_ptr.length = length; break; case 0x68686561: /* hhea */ font->hhea_ptr.offset = offset; font->hhea_ptr.length = length; break; case 0x686d7478: /* hmtx */ font->hmtx_ptr.offset = offset; font->hmtx_ptr.length = length; break; case 0x6e616d65: /* name */ font->name_ptr.offset = offset; font->name_ptr.length = length; break; case 0x4f532f32: /* OS/2 */ font->os_2_ptr.offset = offset; font->name_ptr.length = length; break; } } if (!font->head_ptr.offset) { fprintf(stderr, "tt: no head table\n"); goto _fail_free; } if (!font->glyf_ptr.offset) { fprintf(stderr, "tt: no glyf table\n"); goto _fail_free; } if (!font->cmap_ptr.offset) { fprintf(stderr, "tt: no cmap table\n"); goto _fail_free; } if (!font->loca_ptr.offset) { fprintf(stderr, "tt: no loca table\n"); goto _fail_free; } /* Get emSize */ tt_seek(font, font->head_ptr.offset + 18); font->emSize = (float)tt_read_16(font); /* Try to pick a viable cmap */ tt_seek(font, font->cmap_ptr.offset); uint32_t best = 0; int bestScore = 0; /* Read size */ /* uint16_t cmap_vers = */ tt_read_16(font); uint16_t cmap_size = tt_read_16(font); for (unsigned int i = 0; i < cmap_size; ++i) { uint16_t platform = tt_read_16(font); uint16_t type = tt_read_16(font); uint32_t offset = tt_read_32(font); if ((platform == 3 || platform == 0) && type == 10) { best = offset; bestScore = 4; } else if (platform == 0 && type == 4) { best = offset; bestScore = 4; } else if (((platform == 0 && type == 3) || (platform == 3 && type == 1)) && bestScore < 2) { best = offset; bestScore = 2; } } if (!best) { fprintf(stderr, "tt: TODO: unsupported cmap (best = %#x bestScore = %d)\n", best, bestScore); goto _fail_free; } /* What type is this */ tt_seek(font, font->cmap_ptr.offset + best); font->cmap_type = tt_read_16(font); if (font->cmap_type != 12 && font->cmap_type != 4) { fprintf(stderr, "tt: TODO: unsupported cmap indexing %d\n", font->cmap_type); goto _fail_free; } font->cmap_start = font->cmap_ptr.offset + best; tt_seek(font, font->head_ptr.offset + 50); font->loca_type = tt_read_16(font); return 1; _fail_free: return 0; free(font); } struct TT_Font * tt_font_from_file(const char * fileName) { FILE * f = fopen(fileName, "r"); if (!f) return NULL; struct TT_Font * font = calloc(sizeof(struct TT_Font), 1); font->filePtr = f; font->privFlags = 1; if (!tt_font_load(font)) goto _fail_close; return font; _fail_close: fclose(f); return NULL; } struct TT_Font * tt_font_from_memory(uint8_t * buffer) { struct TT_Font * font = calloc(sizeof(struct TT_Font), 1); font->privFlags = 0; font->buffer = buffer; if (!tt_font_load(font)) return NULL; return font; } struct TT_Font * tt_font_from_file_mem(const char * fileName) { FILE * f = fopen(fileName, "r"); if (!f) return NULL; fseek(f, 0, SEEK_END); long size = ftell(f); fseek(f, 0, SEEK_SET); uint8_t * buf = malloc(size); fread(buf, 1, size, f); fclose(f); return tt_font_from_memory(buf); } static hashmap_t * shm_font_cache = NULL; static int volatile shm_font_lock = 0; struct TT_Font * tt_font_from_shm(const char * identifier) { spin_lock(&shm_font_lock); if (!shm_font_cache) { shm_font_cache = hashmap_create(10); } void * fontData = hashmap_get(shm_font_cache, (char*)identifier); if (fontData) goto shm_success; char * display = getenv("DISPLAY"); if (!display) goto shm_fail; char fullIdentifier[1024]; snprintf(fullIdentifier, 1023, "sys.%s.fonts.%s", display, identifier); size_t fontSize = 0; fontData = shm_obtain(fullIdentifier, &fontSize); if (fontSize == 0) { shm_release(identifier); goto shm_fail; } hashmap_set(shm_font_cache, (char*)identifier, fontData); shm_success: spin_unlock(&shm_font_lock); return tt_font_from_memory(fontData); shm_fail: spin_unlock(&shm_font_lock); return NULL; } 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) { tt_set_size(font, font_size); int w = tt_string_width(font, string); /* TODO: We need to check the bounds of descenders and ascenders so we can fit things more correctly... */ sprite_t * _tmp_s = create_sprite(w + blur * 2, font_size + blur * 2 + 5, ALPHA_EMBEDDED); gfx_context_t * _tmp = init_graphics_sprite(_tmp_s); draw_fill(_tmp, rgba(0,0,0,0)); tt_draw_string(_tmp, font, blur, blur + font_size, string, shadow_color); blur_context_box(_tmp, blur); blur_context_box(_tmp, blur); free(_tmp); draw_sprite(ctx, _tmp_s, left - blur, top - blur); sprite_free(_tmp_s); tt_draw_string(ctx, font, left, top + font_size, string, text_color); } static int to_eight(uint32_t codepoint, char * out) { memset(out, 0x00, 7); if (codepoint < 0x0080) { out[0] = (char)codepoint; } else if (codepoint < 0x0800) { out[0] = 0xC0 | (codepoint >> 6); out[1] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x10000) { out[0] = 0xE0 | (codepoint >> 12); out[1] = 0x80 | ((codepoint >> 6) & 0x3F); out[2] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x200000) { out[0] = 0xF0 | (codepoint >> 18); out[1] = 0x80 | ((codepoint >> 12) & 0x3F); out[2] = 0x80 | ((codepoint >> 6) & 0x3F); out[3] = 0x80 | ((codepoint) & 0x3F); } else if (codepoint < 0x4000000) { out[0] = 0xF8 | (codepoint >> 24); out[1] = 0x80 | (codepoint >> 18); out[2] = 0x80 | ((codepoint >> 12) & 0x3F); out[3] = 0x80 | ((codepoint >> 6) & 0x3F); out[4] = 0x80 | ((codepoint) & 0x3F); } else { out[0] = 0xF8 | (codepoint >> 30); out[1] = 0x80 | ((codepoint >> 24) & 0x3F); out[2] = 0x80 | ((codepoint >> 18) & 0x3F); out[3] = 0x80 | ((codepoint >> 12) & 0x3F); out[4] = 0x80 | ((codepoint >> 6) & 0x3F); out[5] = 0x80 | ((codepoint) & 0x3F); } return strlen(out); } char * tt_get_name_string(struct TT_Font * font, int identifier) { if (!font->name_ptr.offset) return NULL; tt_seek(font, font->name_ptr.offset); uint16_t nameFormat = tt_read_16(font); uint16_t count = tt_read_16(font); uint16_t stringOffset = tt_read_16(font); if (nameFormat != 0) return NULL; /* Unsupported table format */ /* Read records until we find one that matches what we asked for, in a suitable format */ for (unsigned int i = 0; i < count; ++i) { uint16_t platformId = tt_read_16(font); uint16_t platformSpecificId = tt_read_16(font); /* uint16_t languageId = */ tt_read_16(font); uint16_t nameId = tt_read_16(font); uint16_t length = tt_read_16(font); uint16_t offset = tt_read_16(font); if (nameId != identifier) continue; if (!(platformId == 3 && platformSpecificId == 1)) continue; char * tmp = calloc(length * 3 + 1,1); /* Should be enough ? */ char * c = tmp; tt_seek(font, stringOffset + offset + font->name_ptr.offset); for (unsigned int j = 0; j < length; j += 2) { uint32_t cp = tt_read_16(font); if (cp > 0xD7FF && cp < 0xE000) { uint32_t highBits = cp - 0xD800; uint32_t lowBits = tt_read_16(font) - 0xDC00; cp = 0x10000 + (highBits << 10) + lowBits; j += 2; } c += to_eight(cp, c); } return tmp; } return NULL; } struct PenPoly { float x; float y; float inner; float outer; float basis; }; static float tangent(float x_0, float y_0, float x_1, float y_1) { return fmod(atan2(y_0 - y_1, x_1 - x_0) + 2.0 * M_PI, 2.0 * M_PI); } static int angle_compare(float s, struct PenPoly * pen, int a) { if (s >= pen[a].inner && s < pen[a].outer) return 0; if (s >= pen[a].inner && pen[a].outer < pen[a].inner) return 0; if (s < pen[a].outer && pen[a].outer < pen[a].inner) return 0; if (s < pen[a].outer && (2.0 * M_PI + s - pen[a].outer > M_PI)) return 1; if (s > pen[a].outer && (s - pen[a].outer > M_PI)) return 1; return -1; } static int best_angle(int sides, struct PenPoly * pen, float s) { for (int a = 0; a < sides; ++a) { if (angle_compare(s,pen,a) == 0) return a; } return 0; } __attribute__((visibility("protected"))) struct TT_Contour * tt_contour_stroke_contour(const struct TT_Contour * in, float width) { struct TT_Contour * stroke = tt_contour_start(0,0); if (in->edgeCount) { int sides = width < 1.0 ? 4 : 16; float inner = 2.0 * M_PI / (float)sides; float outer = (M_PI - inner) / 2.0; struct PenPoly * pen = malloc(sizeof(struct PenPoly) * sides); /* Arbitrary */ for (int i = 0; i < sides; ++i) { float angle = (float)i * 2.0 * M_PI / (float)sides; pen[i].x = cos(angle) * width; pen[i].y = -sin(angle) * width; pen[i].basis = angle; pen[i].inner = fmod(angle + outer, 2.0 * M_PI); pen[i].outer = fmod(angle + outer + inner, 2.0 * M_PI); } int start_of_segment = 0; int next_segment = (int)in->edgeCount; do { int started = 0; int v = start_of_segment; float s = tangent(in->edges[v].start.x, in->edges[v].start.y, in->edges[v].end.x, in->edges[v].end.y); int a = best_angle(sides,pen,s); while (v < (int)in->edgeCount) { s = tangent(in->edges[v].start.x, in->edges[v].start.y, in->edges[v].end.x, in->edges[v].end.y); stroke = (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); started = 1; int comp = angle_compare(s,pen,a); if (comp == 0) { if (v + 1 == (int)in->edgeCount) { next_segment = in->edgeCount; break; } if (in->edges[v+1].start.x != in->edges[v].end.x || in->edges[v+1].start.y != in->edges[v].end.y) { next_segment = v + 1; break; } v++; } else if (comp == 1) { a = (sides + a - 1) % sides; } else { a = (a + 1) % sides; } } while (v >= start_of_segment) { s = tangent(in->edges[v].end.x, in->edges[v].end.y, in->edges[v].start.x, in->edges[v].start.y); stroke = tt_contour_line_to(stroke, in->edges[v].end.x + pen[a].x, in->edges[v].end.y + pen[a].y); int comp = angle_compare(s,pen,a); if (comp == 0) { if (v == start_of_segment) break; v--; } else if (comp == 1) { a = (sides + a - 1) % sides; } else { a = (a + 1) % sides; } } while (v == start_of_segment) { s = tangent(in->edges[v].start.x, in->edges[v].start.y, in->edges[v].end.x, in->edges[v].end.y); stroke = tt_contour_line_to(stroke, pen[a].x + in->edges[v].start.x, pen[a].y + in->edges[v].start.y); int comp = angle_compare(s,pen,a); if (comp == 0) { break; } else if (comp == 1) { a = (sides + a - 1) % sides; } else { a = (a + 1) % sides; } } start_of_segment = next_segment; } while (next_segment != (int)in->edgeCount); free(pen); } return stroke; } struct TT_Shape * tt_contour_stroke_shape(const struct TT_Contour * in, float width) { struct TT_Contour * stroke = tt_contour_stroke_contour(in,width); struct TT_Shape * out = tt_contour_finish(stroke); free(stroke); return out; } void tt_contour_transform(struct TT_Contour * cnt, gfx_matrix_t matrix) { for (size_t i = 0; i < cnt->edgeCount; i++) { double x, y; gfx_apply_matrix(cnt->edges[i].start.x, cnt->edges[i].start.y, matrix, &x, &y); cnt->edges[i].start.x = x; cnt->edges[i].start.y = y; gfx_apply_matrix(cnt->edges[i].end.x, cnt->edges[i].end.y, matrix, &x, &y); cnt->edges[i].end.x = x; cnt->edges[i].end.y = y; } } static inline int out_of_bounds(const sprite_t * tex, int x, int y) { return x < 0 || y < 0 || x >= tex->width || y >= tex->height; } static inline uint32_t linear_interp(uint32_t left, uint32_t right, uint16_t pr) { uint16_t pl = 0xFF ^ pr; uint8_t d_r = (((uint32_t)(_RED(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_RED(left) * pl + 0x80) * 0x101) >> 16UL); uint8_t d_g = (((uint32_t)(_GRE(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_GRE(left) * pl + 0x80) * 0x101) >> 16UL); uint8_t d_b = (((uint32_t)(_BLU(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_BLU(left) * pl + 0x80) * 0x101) >> 16UL); uint8_t d_a = (((uint32_t)(_ALP(right) * pr + 0x80) * 0x101) >> 16UL) + (((uint32_t)(_ALP(left) * pl + 0x80) * 0x101) >> 16UL); return tt_rgba(d_r, d_g, d_b, d_a); } static inline uint32_t sprite_pixel_no_repeat(const sprite_t * tex, int x, int y) { return out_of_bounds(tex,x,y) ? 0 : SPRITE(tex,x,y); } static inline int wrap(int x, int w) { return x < 0 ? (w - 1 - (-x -1) % w) : (x % w); } static inline uint32_t sprite_pixel_repeat(const sprite_t * tex, int x, int y) { int w = tex->width; int h = tex->height; return SPRITE(tex,wrap(x,w),wrap(y,h)); } static inline uint32_t sprite_pixel_pad(const sprite_t * tex, int x, int y) { int w = tex->width; int h = tex->height; if (x < 0) x = 0; if (x >= w) x = w-1; if (y < 0) y = 0; if (y >= h) y = h-1; return SPRITE(tex,x,y); } typedef uint32_t (*pixel_getter_t)(const sprite_t*,int,int); __attribute__((hot)) static inline uint32_t sprite_interpolate_bilinear(const sprite_t * tex, double u, double v, pixel_getter_t pixel_getter) { int x = floor(u); int y = floor(v); uint32_t ul = pixel_getter(tex,x,y); uint32_t ur = pixel_getter(tex,x+1,y); uint32_t ll = pixel_getter(tex,x,y+1); uint32_t lr = pixel_getter(tex,x+1,y+1); if ((ul | ur | ll | lr) == 0) return 0; uint8_t u_ratio = (u - x) * 0xFF; uint8_t v_ratio = (v - y) * 0xFF; uint32_t top = linear_interp(ul,ur,u_ratio); uint32_t bot = linear_interp(ll,lr,u_ratio); return linear_interp(top,bot,v_ratio); } static inline uint32_t sprite_interpolate_nearest(const sprite_t * tex, double u, double v, pixel_getter_t pixel_getter) { int x = floor(u); int y = floor(v); return pixel_getter(tex,x,y); } typedef uint32_t (*sprite_interp_t)(const sprite_t *, double, double, pixel_getter_t); __attribute__((hot)) static 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) { for (int x = shape->startX < 0 ? 0 : shape->startX; x < shape->lastX && x < ctx->width; ++x) { uint16_t na = (int)(255 * subsamples[x - shape->startX]) >> 2; uint32_t color = sprite_interp(sprite, u, v, pixel_getter); uint32_t nc = tt_apply_alpha(color, na); GFX(ctx, x, y) = tt_alpha_blend_rgba(GFX(ctx, x, y), nc); subsamples[x-shape->startX] = 0; u += filter_dxx; v += filter_dxy; } } static 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) { gfx_matrix_t inverse; gfx_matrix_invert(matrix,inverse); size_t size = shape->edgeCount; struct TT_Intersection * crosses = malloc(sizeof(struct TT_Intersection) * size); size_t subsample_width = shape->lastX - shape->startX; float * subsamples = malloc(sizeof(float) * subsample_width); memset(subsamples, 0, sizeof(float) * subsample_width); int startY = shape->startY < 0 ? 0 : shape->startY; int endY = shape->lastY <= ctx->height ? shape->lastY : ctx->height; double filter_x, filter_y, filter_dxx, filter_dxy, filter_dyx, filter_dyy; int _left = shape->startX < 0 ? 0 : shape->startX; gfx_apply_matrix(_left, startY, inverse, &filter_x, &filter_y); gfx_apply_matrix(_left+1, startY, inverse, &filter_dxx, &filter_dxy); filter_dxx -= filter_x; filter_dxy -= filter_y; gfx_apply_matrix(_left, startY+1, inverse, &filter_dyx, &filter_dyy); filter_dyx -= filter_x; filter_dyy -= filter_y; for (int y = startY; y < endY; ++y) { float u = filter_x; float v = filter_y; filter_x += filter_dyx; filter_y += filter_dyy; if (!_is_in_clip(ctx,y)) continue; float _y = y + 0.0001; for (int l = 0; l < 4; ++l) { size_t cnt; if ((cnt = prune_edges(size, _y, shape->edges, crosses))) { sort_intersections(cnt, crosses); process_scanline(_y, shape, subsample_width, subsamples, cnt, crosses); } _y += 1.0/4.0; } paint_scanline_sprite(ctx, y, shape, subsamples, sprite, u, v, filter_dxx, filter_dxy, sprite_interp, pixel_getter); } free(subsamples); free(crosses); } void tt_path_paint_sprite(gfx_context_t * ctx, const struct TT_Shape * shape, sprite_t * sprite, gfx_matrix_t matrix) { tt_path_paint_sprite_internal(ctx,shape,sprite,matrix,sprite_interpolate_bilinear,sprite_pixel_repeat); } 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) { sprite_interp_t sprite_interp = sprite_interpolate_bilinear; pixel_getter_t pixel_getter = sprite_pixel_repeat; switch (filter) { case TT_PATH_FILTER_BILINEAR: default: break; case TT_PATH_FILTER_NEAREST: sprite_interp = sprite_interpolate_nearest; break; } switch (wrap) { case TT_PATH_WRAP_REPEAT: default: break; case TT_PATH_WRAP_NONE: pixel_getter = sprite_pixel_no_repeat; break; case TT_PATH_WRAP_PAD: pixel_getter = sprite_pixel_pad; break; } tt_path_paint_sprite_internal(ctx,shape,sprite,matrix,sprite_interp,pixel_getter); } char * tt_ellipsify(const char * input, int font_size, struct TT_Font * font, int max_width, int * out_width) { int width; int len = strlen(input); char * out = malloc(len + 4); if (max_width <= 0) { out[0] = '\0'; width = 0; goto _finish; } memcpy(out, input, len + 1); tt_set_size(font, font_size); while ((width = tt_string_width(font, out)) > max_width) { len--; if (len+0>=0) out[len+0] = '.'; if (len+1>=0) out[len+1] = '.'; if (len+2>=0) out[len+2] = '.'; out[len+3] = '\0'; } _finish: if (out_width) *out_width = width; return out; } ================================================ FILE: lib/tree.c ================================================ /** * @brief General-purpose tree implementation * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2011-2018 K. Lange */ #ifdef _KERNEL_ # include #else # include # include #endif #include tree_t * tree_create(void) { /* Create a new tree */ tree_t * out = malloc(sizeof(tree_t)); out->nodes = 0; out->root = NULL; return out; } void tree_set_root(tree_t * tree, void * value) { /* Set the root node for a new tree. */ tree_node_t * root = tree_node_create(value); tree->root = root; tree->nodes = 1; } void tree_node_destroy(tree_node_t * node) { /* Free the contents of a node and its children, but not the nodes themselves */ foreach(child, node->children) { tree_node_destroy((tree_node_t *)child->value); } free(node->value); } void tree_destroy(tree_t * tree) { /* Free the contents of a tree, but not the nodes */ if (tree->root) { tree_node_destroy(tree->root); } } void tree_node_free(tree_node_t * node) { /* Free a node and its children, but not their contents */ if (!node) return; foreach(child, node->children) { tree_node_free(child->value); } free(node); } void tree_free(tree_t * tree) { /* Free all of the nodes in a tree, but not their contents */ tree_node_free(tree->root); } tree_node_t * tree_node_create(void * value) { /* Create a new tree node pointing to the given value */ tree_node_t * out = malloc(sizeof(tree_node_t)); out->value = value; out->children = list_create(); out->parent = NULL; return out; } void tree_node_insert_child_node(tree_t * tree, tree_node_t * parent, tree_node_t * node) { /* Insert a node as a child of parent */ list_insert(parent->children, node); node->parent = parent; tree->nodes++; } tree_node_t * tree_node_insert_child(tree_t * tree, tree_node_t * parent, void * value) { /* Insert a (fresh) node as a child of parent */ tree_node_t * out = tree_node_create(value); tree_node_insert_child_node(tree, parent, out); return out; } tree_node_t * tree_node_find_parent(tree_node_t * haystack, tree_node_t * needle) { /* Recursive node part of tree_find_parent */ tree_node_t * found = NULL; foreach(child, haystack->children) { if (child->value == needle) { return haystack; } found = tree_node_find_parent((tree_node_t *)child->value, needle); if (found) { break; } } return found; } tree_node_t * tree_find_parent(tree_t * tree, tree_node_t * node) { /* Return the parent of a node, inefficiently. */ if (!tree->root) return NULL; return tree_node_find_parent(tree->root, node); } size_t tree_count_children(tree_node_t * node) { /* return the number of children this node has */ if (!node) return 0; if (!node->children) return 0; size_t out = node->children->length; foreach(child, node->children) { out += tree_count_children((tree_node_t *)child->value); } return out; } void tree_node_parent_remove(tree_t * tree, tree_node_t * parent, tree_node_t * node) { /* remove a node when we know its parent; update node counts for the tree */ tree->nodes -= tree_count_children(node) + 1; list_delete(parent->children, list_find(parent->children, node)); tree_node_free(node); } void tree_node_remove(tree_t * tree, tree_node_t * node) { /* remove an entire branch given its root */ tree_node_t * parent = node->parent; if (!parent) { if (node == tree->root) { tree->nodes = 0; tree->root = NULL; tree_node_free(node); } } tree_node_parent_remove(tree, parent, node); } void tree_remove(tree_t * tree, tree_node_t * node) { /* Remove this node and move its children into its parent's list of children */ tree_node_t * parent = node->parent; /* This is something we just can't do. We don't know how to merge our * children into our "parent" because then we'd have more than one root node. * A good way to think about this is actually what this tree struct * primarily exists for: processes. Trying to remove the root is equivalent * to trying to kill init! Which is bad. We immediately fault on such * a case anyway ("Tried to kill init, shutting down!"). */ if (!parent) return; tree->nodes--; list_delete(parent->children, list_find(parent->children, node)); foreach(child, node->children) { /* Reassign the parents */ ((tree_node_t *)child->value)->parent = parent; } list_merge(parent->children, node->children); free(node); } void tree_remove_reparent_root(tree_t * tree, tree_node_t * node) { /* Remove this node and move its children into the root children */ tree_node_t * parent = node->parent; if (!parent) return; tree->nodes--; list_delete(parent->children, list_find(parent->children, node)); foreach(child, node->children) { /* Reassign the parents */ ((tree_node_t *)child->value)->parent = tree->root; } list_merge(tree->root->children, node->children); free(node); } void tree_break_off(tree_t * tree, tree_node_t * node) { tree_node_t * parent = node->parent; if (!parent) return; list_delete(parent->children, list_find(parent->children, node)); } tree_node_t * tree_node_find(tree_node_t * node, void * search, tree_comparator_t comparator) { if (comparator(node->value,search)) { return node; } tree_node_t * found; foreach(child, node->children) { found = tree_node_find((tree_node_t *)child->value, search, comparator); if (found) return found; } return NULL; } tree_node_t * tree_find(tree_t * tree, void * value, tree_comparator_t comparator) { return tree_node_find(tree->root, value, comparator); } ================================================ FILE: lib/yutani.c ================================================ /** * @brief Yutani Client Library * * Client library for the compositing window system. * * @copyright * 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-2018 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include /* We need the flags but don't want the library dep (maybe the flags should be here?) */ #include /** * yutani_wait_for * * Wait for a particular kind of message, queuing other types * of messages for processing later. */ yutani_msg_t * yutani_wait_for(yutani_t * y, uint32_t type) { do { yutani_msg_t * out; size_t size; { char tmp[MAX_PACKET_SIZE]; size = pex_recv(y->sock, tmp); out = malloc(size); memcpy(out, tmp, size); } if (out->type == type) { return out; } else { list_insert(y->queued, out); } } while (1); /* XXX: (!y->abort) */ } /** * yutani_query * * Check if there is an available message, either in the * internal queue or directly from the server interface. */ size_t yutani_query(yutani_t * y) { if (y->queued->length > 0) return 1; return pex_query(y->sock); } /** * _handle_internal * * Some messages are processed internally. They are still * available to the client application, but some work will * be done before they are handed off. * * WELCOME: Update the display_width and display_height for the connection. * WINDOW_MOVE: Update the window location. */ static void _handle_internal(yutani_t * y, yutani_msg_t * out) { switch (out->type) { case YUTANI_MSG_WELCOME: { struct yutani_msg_welcome * mw = (void *)out->data; y->display_width = mw->display_width; y->display_height = mw->display_height; } break; case YUTANI_MSG_WINDOW_MOVE: { struct yutani_msg_window_move * wm = (void *)out->data; yutani_window_t * win = hashmap_get(y->windows, (void *)(uintptr_t)wm->wid); if (win) { win->x = wm->x; win->y = wm->y; } } break; case YUTANI_MSG_RESIZE_OFFER: { struct yutani_msg_window_resize * wr = (void *)out->data; yutani_window_t * win = hashmap_get(y->windows, (void *)(uintptr_t)wr->wid); if (win) { win->decorator_flags &= ~(DECOR_FLAG_TILED); win->decorator_flags |= (wr->flags & YUTANI_RESIZE_TILED) << 2; } } default: break; } } /** * yutani_poll * * Wait for a message to be available, processing it if * it has internal processing requirements. */ yutani_msg_t * yutani_poll(yutani_t * y) { yutani_msg_t * out; if (y->queued->length > 0) { node_t * node = list_dequeue(y->queued); out = (yutani_msg_t *)node->value; free(node); _handle_internal(y, out); return out; } ssize_t size; { char tmp[MAX_PACKET_SIZE]; size = pex_recv(y->sock, tmp); if (size <= 0) return NULL; out = malloc(size); memcpy(out, tmp, size); } _handle_internal(y, out); return out; } /** * yutani_poll_async * * Get the next available message, if there is one, otherwise * return immediately. Generally should be called in a loop * after an initial call to yutani_poll in case processing * caused additional messages to be queued. */ yutani_msg_t * yutani_poll_async(yutani_t * y) { if (yutani_query(y) > 0) { return yutani_poll(y); } return NULL; } void yutani_msg_buildx_hello(yutani_msg_t * msg) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_HELLO; msg->size = sizeof(struct yutani_message); } void yutani_msg_buildx_flip(yutani_msg_t * msg, yutani_wid_t wid) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_FLIP; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_flip); struct yutani_msg_flip * mw = (void *)msg->data; mw->wid = wid; } void yutani_msg_buildx_welcome(yutani_msg_t * msg, uint32_t width, uint32_t height) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WELCOME; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_welcome); struct yutani_msg_welcome * mw = (void *)msg->data; mw->display_width = width; mw->display_height = height; } void yutani_msg_buildx_window_new(yutani_msg_t * msg, uint32_t width, uint32_t height) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_NEW; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_new); struct yutani_msg_window_new * mw = (void *)msg->data; mw->width = width; mw->height = height; } 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) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_NEW_FLAGS; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_new_flags); struct yutani_msg_window_new_flags * mw = (void *)msg->data; mw->width = width; mw->height = height; mw->flags = flags; mw->parent_wid = parent_wid; } void yutani_msg_buildx_window_init(yutani_msg_t * msg, yutani_wid_t wid, uint32_t width, uint32_t height, uint32_t bufid) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_INIT; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_init); struct yutani_msg_window_init * mw = (void *)msg->data; mw->wid = wid; mw->width = width; mw->height = height; mw->bufid = bufid; } void yutani_msg_buildx_window_close(yutani_msg_t * msg, yutani_wid_t wid) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_CLOSE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_close); struct yutani_msg_window_close * mw = (void *)msg->data; mw->wid = wid; } void yutani_msg_buildx_key_event(yutani_msg_t * msg, yutani_wid_t wid, key_event_t * event, key_event_state_t * state) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_KEY_EVENT; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_key_event); struct yutani_msg_key_event * mw = (void *)msg->data; mw->wid = wid; memcpy(&mw->event, event, sizeof(key_event_t)); memcpy(&mw->state, state, sizeof(key_event_state_t)); } void yutani_msg_buildx_mouse_event(yutani_msg_t * msg, yutani_wid_t wid, mouse_device_packet_t * event, int32_t type) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_MOUSE_EVENT; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_mouse_event); struct yutani_msg_mouse_event * mw = (void *)msg->data; mw->wid = wid; memcpy(&mw->event, event, sizeof(mouse_device_packet_t)); mw->type = type; } void yutani_msg_buildx_window_move(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_MOVE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_move); struct yutani_msg_window_move * mw = (void *)msg->data; mw->wid = wid; mw->x = x; mw->y = y; } 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) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_MOVE_RELATIVE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_move_relative); struct yutani_msg_window_move_relative * mw = (void *)msg->data; mw->wid_to_move = wid; mw->wid_base = wid2; mw->x = x; mw->y = y; } void yutani_msg_buildx_window_set_parent(yutani_msg_t * msg, yutani_wid_t wid, yutani_wid_t wid2) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_SET_PARENT; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_set_parent); struct yutani_msg_window_set_parent * mw = (void *)msg->data; mw->wid = wid; mw->parent_wid = wid2; } void yutani_msg_buildx_window_stack(yutani_msg_t * msg, yutani_wid_t wid, int z) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_STACK; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_stack); struct yutani_msg_window_stack * mw = (void *)msg->data; mw->wid = wid; mw->z = z; } void yutani_msg_buildx_window_focus_change(yutani_msg_t * msg, yutani_wid_t wid, int focused) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_FOCUS_CHANGE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_focus_change); struct yutani_msg_window_focus_change * mw = (void *)msg->data; mw->wid = wid; mw->focused = focused; } 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) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_MOUSE_EVENT; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_mouse_event); struct yutani_msg_window_mouse_event * mw = (void *)msg->data; mw->wid = wid; mw->new_x = new_x; mw->new_y = new_y; mw->old_x = old_x; mw->old_y = old_y; mw->buttons = buttons; mw->command = command; mw->modifiers = modifiers; } 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) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_FLIP_REGION; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_flip_region); struct yutani_msg_flip_region * mw = (void *)msg->data; mw->wid = wid; mw->x = x; mw->y = y; mw->width = width; mw->height = height; } 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) { msg->magic = YUTANI_MSG__MAGIC; msg->type = type; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_resize); struct yutani_msg_window_resize * mw = (void *)msg->data; mw->wid = wid; mw->width = width; mw->height = height; mw->bufid = bufid; mw->flags = flags; } 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) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_ADVERTISE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_advertise) + length; struct yutani_msg_window_advertise * mw = (void *)msg->data; mw->wid = wid; mw->flags = flags; mw->size = length; mw->icon = icon; mw->bufid = bufid; mw->width = width; mw->height = height; if (data) { memcpy(mw->strings, data, mw->size); } } void yutani_msg_buildx_subscribe(yutani_msg_t * msg) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_SUBSCRIBE; msg->size = sizeof(struct yutani_message); } void yutani_msg_buildx_unsubscribe(yutani_msg_t * msg) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_UNSUBSCRIBE; msg->size = sizeof(struct yutani_message); } void yutani_msg_buildx_query_windows(yutani_msg_t * msg) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_QUERY_WINDOWS; msg->size = sizeof(struct yutani_message); } void yutani_msg_buildx_notify(yutani_msg_t * msg) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_NOTIFY; msg->size = sizeof(struct yutani_message); } void yutani_msg_buildx_session_end(yutani_msg_t * msg) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_SESSION_END; msg->size = sizeof(struct yutani_message); } void yutani_msg_buildx_window_focus(yutani_msg_t * msg, yutani_wid_t wid) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_FOCUS; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_focus); struct yutani_msg_window_focus * mw = (void *)msg->data; mw->wid = wid; } void yutani_msg_buildx_key_bind(yutani_msg_t * msg, kbd_key_t key, kbd_mod_t mod, int response) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_KEY_BIND; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_key_bind); struct yutani_msg_key_bind * mw = (void *)msg->data; mw->key = key; mw->modifiers = mod; mw->response = response; } void yutani_msg_buildx_window_drag_start(yutani_msg_t * msg, yutani_wid_t wid) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_DRAG_START; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_drag_start); struct yutani_msg_window_drag_start * mw = (void *)msg->data; mw->wid = wid; } void yutani_msg_buildx_window_update_shape(yutani_msg_t * msg, yutani_wid_t wid, int set_shape) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_UPDATE_SHAPE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_update_shape); struct yutani_msg_window_update_shape * mw = (void *)msg->data; mw->wid = wid; mw->set_shape = set_shape; } void yutani_msg_buildx_window_warp_mouse(yutani_msg_t * msg, yutani_wid_t wid, int32_t x, int32_t y) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_WARP_MOUSE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_warp_mouse); struct yutani_msg_window_warp_mouse * mw = (void *)msg->data; mw->wid = wid; mw->x = x; mw->y = y; } void yutani_msg_buildx_window_show_mouse(yutani_msg_t * msg, yutani_wid_t wid, int32_t show_mouse) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_SHOW_MOUSE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_show_mouse); struct yutani_msg_window_show_mouse * mw = (void *)msg->data; mw->wid = wid; mw->show_mouse = show_mouse; } void yutani_msg_buildx_window_resize_start(yutani_msg_t * msg, yutani_wid_t wid, yutani_scale_direction_t direction) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_RESIZE_START; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_resize_start); struct yutani_msg_window_resize_start * mw = (void *)msg->data; mw->wid = wid; mw->direction = direction; } void yutani_msg_buildx_special_request(yutani_msg_t * msg, yutani_wid_t wid, uint32_t request) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_SPECIAL_REQUEST; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_special_request); struct yutani_msg_special_request * sr = (void *)msg->data; sr->wid = wid; sr->request = request; } void yutani_msg_buildx_clipboard(yutani_msg_t * msg, char * content) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_CLIPBOARD; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_clipboard) + strlen(content); struct yutani_msg_clipboard * cl = (void *)msg->data; cl->size = strlen(content); memcpy(cl->content, content, strlen(content)); } 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) { msg->magic = YUTANI_MSG__MAGIC; msg->type = YUTANI_MSG_WINDOW_PANEL_SIZE; msg->size = sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_panel_size); struct yutani_msg_window_panel_size * ps = (void *)msg->data; ps->wid = wid; ps->x = x; ps->y = y; ps->w = w; ps->h = h; } int yutani_msg_send(yutani_t * y, yutani_msg_t * msg) { return pex_reply(y->sock, msg->size, (char *)msg); } yutani_t * yutani_context_create(FILE * socket) { yutani_t * out = malloc(sizeof(yutani_t)); out->sock = socket; out->display_width = 0; out->display_height = 0; out->windows = hashmap_create_int(10); out->queued = list_create(); return out; } /** * yutani_init * * Connect to the compositor. * * Connects and handles the initial welcome message. */ yutani_t * yutani_init(void) { char * server_name = getenv("DISPLAY"); if (!server_name) { server_name = "compositor"; } FILE * c = pex_connect(server_name); if (!c) { return NULL; /* Connection failed. */ } yutani_t * y = yutani_context_create(c); yutani_msg_buildx_hello_alloc(m); yutani_msg_buildx_hello(m); yutani_msg_send(y, m); yutani_msg_t * mm = yutani_wait_for(y, YUTANI_MSG_WELCOME); struct yutani_msg_welcome * mw = (void *)&mm->data; y->display_width = mw->display_width; y->display_height = mw->display_height; y->server_ident = server_name; free(mm); return y; } /** * yutani_window_create_flags * * Create a window with certain pre-specified properties. */ yutani_window_t * yutani_window_create_flags(yutani_t * y, int width, int height, uint32_t flags, ...) { va_list ap; va_start(ap, flags); yutani_wid_t parent_wid = 0; if (flags & YUTANI_WINDOW_FLAG_PARENT_WID) { yutani_window_t * parent = va_arg(ap, yutani_window_t *); if (parent) parent_wid = parent->wid; } va_end(ap); yutani_window_t * win = malloc(sizeof(yutani_window_t)); yutani_msg_buildx_window_new_flags_alloc(m); yutani_msg_buildx_window_new_flags(m, width, height, flags, parent_wid); yutani_msg_send(y, m); yutani_msg_t * mm = yutani_wait_for(y, YUTANI_MSG_WINDOW_INIT); struct yutani_msg_window_init * mw = (void *)&mm->data; win->width = mw->width; win->height = mw->height; win->bufid = mw->bufid; win->wid = mw->wid; win->focused = 0; win->decorator_flags = 0; win->x = 0; win->y = 0; win->user_data = NULL; win->ctx = y; win->mouse_state = -1; free(mm); hashmap_set(y->windows, (void*)(uintptr_t)win->wid, win); char key[1024]; YUTANI_SHMKEY(y->server_ident, key, 1024, win); size_t size = (width * height * 4); win->buffer = shm_obtain(key, &size); return win; } /** * yutani_window_create * * Create a basic window. */ yutani_window_t * yutani_window_create(yutani_t * y, int width, int height) { return yutani_window_create_flags(y,width,height,0); } /** * yutani_flip * * Ask the server to redraw the window. */ void yutani_flip(yutani_t * y, yutani_window_t * win) { yutani_msg_buildx_flip_alloc(m); yutani_msg_buildx_flip(m, win->wid); yutani_msg_send(y, m); } /** * yutani_flip_region * * Ask the server to redraw a region relative the window. */ void yutani_flip_region(yutani_t * yctx, yutani_window_t * win, int32_t x, int32_t y, int32_t width, int32_t height) { yutani_msg_buildx_flip_region_alloc(m); yutani_msg_buildx_flip_region(m, win->wid, x, y, width, height); yutani_msg_send(yctx, m); } /** * yutani_close * * Close a window. A closed window should not be used again, * and its associated buffers will be freed. */ void yutani_close(yutani_t * y, yutani_window_t * win) { yutani_msg_buildx_window_close_alloc(m); yutani_msg_buildx_window_close(m, win->wid); yutani_msg_send(y, m); /* Now destroy our end of the window */ { char key[1024]; YUTANI_SHMKEY_EXP(y->server_ident, key, 1024, win->bufid); shm_release(key); } hashmap_remove(y->windows, (void*)(uintptr_t)win->wid); free(win); } /** * yutani_window_move * * Request a window be moved to new a location on screen. */ void yutani_window_move(yutani_t * yctx, yutani_window_t * window, int x, int y) { yutani_msg_buildx_window_move_alloc(m); yutani_msg_buildx_window_move(m, window->wid, x, y); yutani_msg_send(yctx, m); } /** * yutani_window_move_relative * * Move a window to a location based on the local coordinate space of a base window. */ void yutani_window_move_relative(yutani_t * yctx, yutani_window_t * window, yutani_window_t * base, int x, int y) { yutani_msg_buildx_window_move_relative_alloc(m); yutani_msg_buildx_window_move_relative(m, window->wid, base->wid, x, y); yutani_msg_send(yctx, m); } /** * yutani_window_set_parent * * Tell the compositor that this window has a parent that should inherit its focus state * for advertisements, and certain other properties (tbd.). */ void yutani_window_set_parent(yutani_t * yctx, yutani_window_t * window, yutani_window_t * parent) { yutani_msg_buildx_window_set_parent_alloc(m); yutani_msg_buildx_window_set_parent(m, window->wid, parent ? parent->wid : 0); yutani_msg_send(yctx, m); } /** * yutani_set_stack * * Set the stacking order of the window. */ void yutani_set_stack(yutani_t * yctx, yutani_window_t * window, int z) { yutani_msg_buildx_window_stack_alloc(m); yutani_msg_buildx_window_stack(m, window->wid, z); yutani_msg_send(yctx, m); } /** * yutani_window_resize * * Request that the server resize a window. */ void yutani_window_resize(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height) { yutani_msg_buildx_window_resize_alloc(m); yutani_msg_buildx_window_resize(m, YUTANI_MSG_RESIZE_REQUEST, window->wid, width, height, 0, 0); yutani_msg_send(yctx, m); } /** * yutani_window_resize_offer * * In a response to a server resize message, offer an alternative size. * Allows the client to reject a user-provided resize request due to * size constraints or other reasons. */ void yutani_window_resize_offer(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height) { yutani_msg_buildx_window_resize_alloc(m); yutani_msg_buildx_window_resize(m, YUTANI_MSG_RESIZE_OFFER, window->wid, width, height, 0, 0); yutani_msg_send(yctx, m); } /** * yutani_window_resize_accept * * Accept the server's resize request, initialize new buffers * and all the client to draw into the new buffers. */ void yutani_window_resize_accept(yutani_t * yctx, yutani_window_t * window, uint32_t width, uint32_t height) { yutani_msg_buildx_window_resize_alloc(m); yutani_msg_buildx_window_resize(m, YUTANI_MSG_RESIZE_ACCEPT, window->wid, width, height, 0, 0); yutani_msg_send(yctx, m); /* Now wait for the new bufid */ yutani_msg_t * mm = yutani_wait_for(yctx, YUTANI_MSG_RESIZE_BUFID); struct yutani_msg_window_resize * wr = (void*)mm->data; if (window->wid != wr->wid) { /* I am not sure what to do here. */ return; } /* Update the window */ window->width = wr->width; window->height = wr->height; window->oldbufid = window->bufid; window->bufid = wr->bufid; free(mm); /* Allocate the buffer */ { char key[1024]; YUTANI_SHMKEY(yctx->server_ident, key, 1024, window); size_t size = (window->width * window->height * 4); window->buffer = shm_obtain(key, &size); } } /** * yutani_window_resize_done * * The client has finished drawing into the new buffers after * accepting a resize request and the server should now * discard the old buffer and switch to the new one. */ void yutani_window_resize_done(yutani_t * yctx, yutani_window_t * window) { /* Destroy the old buffer */ { char key[1024]; YUTANI_SHMKEY_EXP(yctx->server_ident, key, 1024, window->oldbufid); shm_release(key); } yutani_msg_buildx_window_resize_alloc(m); yutani_msg_buildx_window_resize(m, YUTANI_MSG_RESIZE_DONE, window->wid, window->width, window->height, window->bufid, 0); yutani_msg_send(yctx, m); } /** * yutani_window_advertise * * Provide a title for a window to have it show up * in the panel window list. */ void yutani_window_advertise(yutani_t * yctx, yutani_window_t * window, char * name) { uint32_t flags = 0; /* currently, no client flags */ uint32_t length = 0; uint32_t icon = 0; char * strings; if (!name) { length = 1; strings = " "; } else { length = strlen(name) + 1; strings = name; icon = strlen(name); } yutani_msg_buildx_window_advertise_alloc(m, length); yutani_msg_buildx_window_advertise(m, window->wid, flags, icon, 0, 0, 0, length, strings); yutani_msg_send(yctx, m); } /** * yutani_window_advertise_icon * * Provide a title and an icon for the panel to show. * * Note that three additional fields are available in the advertisement * messages which are not yet used. This is to allow for future expansion. */ void yutani_window_advertise_icon(yutani_t * yctx, yutani_window_t * window, char * name, char * icon) { uint32_t flags = 0; /* currently no client flags */ uint32_t iconx = 0; uint32_t length = strlen(name) + strlen(icon) + 2; char * strings = malloc(length); if (name) { memcpy(&strings[0], name, strlen(name)+1); iconx = strlen(name); } if (icon) { memcpy(&strings[strlen(name)+1], icon, strlen(icon)+1); iconx = strlen(name)+1; } yutani_msg_buildx_window_advertise_alloc(m, length); yutani_msg_buildx_window_advertise(m, window->wid, flags, iconx, 0, 0, 0, length, strings); yutani_msg_send(yctx, m); free(strings); } /** * yutani_subscribe_windows * * Subscribe to messages about new window advertisements. * Basically, if you're a panel, you want to do this, so * you can know when windows move around or change focus. */ void yutani_subscribe_windows(yutani_t * y) { yutani_msg_buildx_subscribe_alloc(m); yutani_msg_buildx_subscribe(m); yutani_msg_send(y, m); } /** * yutani_unsubscribe_windows * * If you no longer wish to receive window change messages, * you can unsubscribe your client from them. */ void yutani_unsubscribe_windows(yutani_t * y) { yutani_msg_buildx_unsubscribe_alloc(m); yutani_msg_buildx_unsubscribe(m); yutani_msg_send(y, m); } /** * yutani_query_windows * * When notified of changes, call this to request * the new information. */ void yutani_query_windows(yutani_t * y) { yutani_msg_buildx_query_windows_alloc(m); yutani_msg_buildx_query_windows(m); yutani_msg_send(y, m); } /** * yutani_session_end * * For use by session managers, tell the compositor * that the session has ended and it should inform * other clients of this so they can exit. */ void yutani_session_end(yutani_t * y) { yutani_msg_buildx_session_end_alloc(m); yutani_msg_buildx_session_end(m); yutani_msg_send(y, m); } /** * yutani_focus_window * * Change focus to the given window. Mostly used by * panels and other window management things, but if you * have a multi-window application, such as one with a * model dialog, and you want to force focus away from one * window and onto another, you can use this. */ void yutani_focus_window(yutani_t * yctx, yutani_wid_t wid) { yutani_msg_buildx_window_focus_alloc(m); yutani_msg_buildx_window_focus(m, wid); yutani_msg_send(yctx, m); } /** * yutani_key_bind * * Request a key combination always be sent to this client. * You can request for the combination to be sent only to * this client (steal binding) or to also go to other clients * (spy binding), the latter of which is useful for catching * changes to modifier keys. */ void yutani_key_bind(yutani_t * yctx, kbd_key_t key, kbd_mod_t mod, int response) { yutani_msg_buildx_key_bind_alloc(m); yutani_msg_buildx_key_bind(m, key,mod,response); yutani_msg_send(yctx, m); } /** * yutani_window_drag_start * * Begin a mouse-driven window movement action. * Typically used by decorators to start moving the window * when the user clicks and drags on the title bar. */ void yutani_window_drag_start(yutani_t * yctx, yutani_window_t * window) { yutani_msg_buildx_window_drag_start_alloc(m); yutani_msg_buildx_window_drag_start(m, window->wid); yutani_msg_send(yctx, m); } /** * yutani_window_drag_start_wid * * Same as above, but takes a wid (of a presumably-foreign window) * instead of a window pointer; used by the panel to initiate * window movement through a drop-down menu for other clients. */ void yutani_window_drag_start_wid(yutani_t * yctx, yutani_wid_t wid) { yutani_msg_buildx_window_drag_start_alloc(m); yutani_msg_buildx_window_drag_start(m, wid); yutani_msg_send(yctx, m); } /** * yutani_window_update_shape * * Change the window shaping threshold. * Allows partially-transparent windows to control whether they * should still receive mouse events in their transparent regions. */ void yutani_window_update_shape(yutani_t * yctx, yutani_window_t * window, int set_shape) { yutani_msg_buildx_window_update_shape_alloc(m); yutani_msg_buildx_window_update_shape(m, window->wid, set_shape); yutani_msg_send(yctx, m); } /** * yutani_window_warp_mouse * * Move the mouse to a locate relative to the window. * Only works with relative mouse cursor. * Useful for games. * * TODO: We still need a way to lock the cursor to a particular window. * Even in games where warping happens quickly, we can still * end up with the cursor outside of the window when a click happens. */ void yutani_window_warp_mouse(yutani_t * yctx, yutani_window_t * window, int32_t x, int32_t y) { yutani_msg_buildx_window_warp_mouse_alloc(m); yutani_msg_buildx_window_warp_mouse(m, window->wid, x, y); yutani_msg_send(yctx, m); } /** * yutani_window_show_mouse * * Set the cursor type. Used to change to risize and drag indicators. * Could be used to show a text insertion bar, or a link-clicking hand, * but those cursors need to be added in the server. * * TODO: We should add a way to use client-provided cursor textures. */ void yutani_window_show_mouse(yutani_t * yctx, yutani_window_t * window, int32_t show_mouse) { if (window->mouse_state != show_mouse) { window->mouse_state = show_mouse; yutani_msg_buildx_window_show_mouse_alloc(m); yutani_msg_buildx_window_show_mouse(m, window->wid, show_mouse); yutani_msg_send(yctx, m); } } /** * yutani_window_resize_start * * Start a mouse-driven window resize action. * Used by decorators. */ void yutani_window_resize_start(yutani_t * yctx, yutani_window_t * window, yutani_scale_direction_t direction) { yutani_msg_buildx_window_resize_start_alloc(m); yutani_msg_buildx_window_resize_start(m, window->wid, direction); yutani_msg_send(yctx, m); } /** * yutani_special_request * * Send one of the special request messages that aren't * important enough to get their own message types. * * (MAXIMIZE, PLEASE_CLOSE, CLIPBOARD) * * Note that, especially in the CLIPBOARD case, the * window does not to be set. */ void yutani_special_request(yutani_t * yctx, yutani_window_t * window, uint32_t request) { /* wid isn't necessary; if window is null, set to 0 */ yutani_msg_buildx_special_request_alloc(m); yutani_msg_buildx_special_request(m, window ? window->wid : 0, request); yutani_msg_send(yctx, m); } /** * yutani_special_request_wid * * Same as above, but takes a wid instead of a window pointer, * for use with foreign windows. */ void yutani_special_request_wid(yutani_t * yctx, yutani_wid_t wid, uint32_t request) { /* For working with other applications' windows */ yutani_msg_buildx_special_request_alloc(m); yutani_msg_buildx_special_request(m, wid, request); yutani_msg_send(yctx, m); } /** * yutani_set_clipboard * * Set the clipboard content. * * If the clipboard content is too large for a message, * it will be stored in a file and a special clipboard string * will be set to indicate the real contents are * in the file. * * To get the clipboard contents, send a CLIPBOARD special * request and wait for the CLIPBOARD response message. */ void yutani_set_clipboard(yutani_t * yctx, char * content) { /* Set clipboard contents */ int len = strlen(content); if (len > 511) { char tmp_file[100]; sprintf(tmp_file, "/tmp/.clipboard.%s", yctx->server_ident); FILE * tmp = fopen(tmp_file, "w+"); fwrite(content, len, 1, tmp); fclose(tmp); char tmp_data[100]; sprintf(tmp_data, "\002 %d", len); yutani_msg_buildx_clipboard_alloc(m, strlen(tmp_data)); yutani_msg_buildx_clipboard(m, tmp_data); yutani_msg_send(yctx, m); } else { yutani_msg_buildx_clipboard_alloc(m, len); yutani_msg_buildx_clipboard(m, content); yutani_msg_send(yctx, m); } } void yutani_window_panel_size(yutani_t * yctx, yutani_wid_t wid, int32_t x, int32_t y, int32_t w, int32_t h) { yutani_msg_buildx_window_panel_size_alloc(m); yutani_msg_buildx_window_panel_size(m,wid,x,y,w,h); yutani_msg_send(yctx, m); } /** * yutani_open_clipboard * * Open the clipboard contents file. */ FILE * yutani_open_clipboard(yutani_t * yctx) { char tmp_file[100]; sprintf(tmp_file, "/tmp/.clipboard.%s", yctx->server_ident); return fopen(tmp_file, "r"); } /** * init_graphics_yutani * * Create a graphical context around a Yutani window. */ gfx_context_t * init_graphics_yutani(yutani_window_t * window) { gfx_context_t * out = malloc(sizeof(gfx_context_t)); out->width = window->width; out->height = window->height; out->stride = window->width * sizeof(uint32_t); out->depth = 32; out->size = GFX_H(out) * GFX_W(out) * GFX_B(out); out->buffer = window->buffer; out->backbuffer = out->buffer; out->clips = NULL; return out; } /** * init_graphics_yutani_double_buffer * * Create a graphics context around a Yutani window * with a separate backing store for double-buffering. */ gfx_context_t * init_graphics_yutani_double_buffer(yutani_window_t * window) { gfx_context_t * out = init_graphics_yutani(window); out->backbuffer = malloc(GFX_B(out) * GFX_W(out) * GFX_H(out)); return out; } /** * reinit_graphics_yutani * * Reinitialize a graphics context, such as when * the window size changes. */ void reinit_graphics_yutani(gfx_context_t * out, yutani_window_t * window) { out->width = window->width; out->height = window->height; out->stride = window->width * 4; out->depth = 32; out->size = GFX_H(out) * GFX_W(out) * GFX_B(out); if (out->clips && out->clips_size != out->height) { free(out->clips); out->clips = NULL; out->clips_size = 0; } if (out->buffer == out->backbuffer) { out->buffer = window->buffer; out->backbuffer = out->buffer; } else { out->buffer = window->buffer; out->backbuffer = realloc(out->backbuffer, GFX_B(out) * GFX_W(out) * GFX_H(out)); } } /** * release_graphics_yutani * * Release a graphics context. * XXX: This seems to work generically for any graphics context? */ void release_graphics_yutani(gfx_context_t * gfx) { if (gfx->backbuffer != gfx->buffer) { free(gfx->backbuffer); } free(gfx); } void yutani_internal_refocus(yutani_t * yctx, yutani_window_t * window) { /* Check if a refocus is already in our queue to be processed */ foreach(node, yctx->queued) { yutani_msg_t * out = (yutani_msg_t *)node->value; if (out->type == YUTANI_MSG_WINDOW_FOCUS_CHANGE) return; } /* Otherwise, produce an artificial one matching the reported focus state of the window */ yutani_msg_t * msg = malloc(sizeof(struct yutani_message) + sizeof(struct yutani_msg_window_focus_change)); yutani_msg_buildx_window_focus_change(msg, window->wid, window->focused); list_insert(yctx->queued, msg); } ================================================ FILE: libc/arch/aarch64/bad.c ================================================ /* bad math */ #include #include #include double sqrt(double x) { asm volatile ("fsqrt %d0, %d1" : "=w"(x) : "w"(x)); return x; } double tan(double theta) { return sin(theta) / cos(theta); } /** * Polynomial approximation of arctangent * * @see https://www.dsprelated.com/showarticle/1052.php */ static inline double _atan(double z) {; double n1 = 0.97239411; double n2 = -0.19194795; return (n1 + n2 * z * z) * z; } double atan2(double y, double x) { if (x != 0.0) { if (fabs(x) > fabs(y)) { double z = y / x; if (x > 0.0) return _atan(z); else if (y >= 0.0) return _atan(z) + M_PI; else return _atan(z) - M_PI; } else { double z = x / y; if (y > 0.0) return -_atan(z) + M_PI/2.0; else return -_atan(z) - M_PI/2.0; } } else { if (y > 0.0) return M_PI/2.0; else if (y < 0.0) return -M_PI/2.0; } return 0.0; } double pow(double x, double y) { if (getenv("LIBM_DEBUG")) { fprintf(stderr, "pow(%f, %f)\n", x, y); } return x; } double fmod(double x, double y) { int _x = fpclassify(x); int _y = fpclassify(y); if (_y == FP_NAN) return NAN; if (_x == FP_INFINITE) return NAN; if (_y == FP_ZERO) return NAN; if (_x == FP_ZERO) return x; long div = x / y; return x - (double)div * y; } ================================================ FILE: libc/arch/aarch64/crt0.S ================================================ .global _start .type _start, %function _start: mov x4, x0 adr x0, main mov x3, x0 mov x0, x4 bl pre_main ================================================ FILE: libc/arch/aarch64/crti.S ================================================ .global _init .section .init _init: stp x29,x30,[sp,-16]! mov x29,sp .global _fini .section .fini _fini: stp x29,x30,[sp,-16]! mov x29,sp ================================================ FILE: libc/arch/aarch64/crtn.S ================================================ .section .init ldp x29,x30,[sp],#16 ret .section .fini ldp x29,x30,[sp],#16 ret ================================================ FILE: libc/arch/aarch64/memcpy.c ================================================ #include #include #include void * memcpy(void * restrict dest, const void * restrict src, size_t n) { uint64_t * d_64 = dest; const uint64_t * s_64 = src; for (; n >= 8; n -= 8) { *d_64++ = *s_64++; } uint32_t * d_32 = (void*)d_64; const uint32_t * s_32 = (const void*)s_64; for (; n >= 4; n -= 4) { *d_32++ = *s_32++; } uint8_t * d = (void*)d_32; const uint8_t * s = (const void*)s_32; for (; n > 0; n--) { *d++ = *s++; } return dest; } ================================================ FILE: libc/arch/aarch64/memset.c ================================================ #include void * memset(void * dest, int c, size_t n) { size_t i = 0; for ( ; i < n; ++i ) { ((char *)dest)[i] = c; } return dest; } ================================================ FILE: libc/arch/aarch64/setjmp.c ================================================ #include #include asm ( ".globl setjmp\n" "setjmp:\n" "mov x2, sp\n" "str x2, [x0]\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" "stp x29, x30, [x0, (1 * 16)]\n" "mov x0, 0\n" "ret\n" ".globl longjmp\n" "longjmp:\n" "ldr x2, [x0]\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" "ldp x29, x30, [x0, (1 * 16)]\n" "mov sp, x2\n" "mov x0, x1\n" "ret\n" ); #if 0 int _setjmp(jmp_buf env); void _longjmp(jmp_buf env, int val); int setjmp(jmp_buf env) { fprintf(stderr, "setjmp called\n"); return _setjmp(env); } void longjmp(jmp_buf env, int val) { fprintf(stderr, "longjmp called\n"); _longjmp(env,val); } #endif ================================================ FILE: libc/arch/x86_64/crt0.S ================================================ .global _start _start: movq $main, %rcx and $0xFFFFFFFFFFFFFFF0, %rsp call pre_main /* call pre_main */ ================================================ FILE: libc/arch/x86_64/crti.S ================================================ .global _init .section .init _init: push %rbp movq %rsp, %rbp .global _fini .section .fini _fini: push %rbp movq %rsp, %rbp ================================================ FILE: libc/arch/x86_64/crtn.S ================================================ .section .init pop %rbp ret .section .fini pop %rbp ret ================================================ FILE: libc/arch/x86_64/math.c ================================================ #include double floor(double x) { if (x == 0.0) return x; double out; asm volatile ( "frndint\n" : "=t"(out) : "0"(x) ); if (out > x) return out - 1.0; return out; } double pow(double x, double y) { double out; asm volatile ( "fyl2x;" "fld %%st;" "frndint;" "fsub %%st,%%st(1);" "fxch;" "fchs;" "f2xm1;" "fld1;" "faddp;" "fxch;" "fld1;" "fscale;" "fstp %%st(1);" "fmulp;" : "=t"(out) : "0"(x),"u"(y) : "st(1)" ); return out; } double fmod(double x, double y) { long double out; asm volatile ( "1: fprem;" /* Partial remainder */ " fnstsw %%ax;" /* store status word */ " sahf;" /* store AX (^ FPU status) into flags */ " jp 1b;" /* jump back to 1 above if parity flag==1 */ : "=t"(out) : "0"(x), "u"(y) : "ax", "cc"); return out; } double tan(double x) { double out; double _x = x; double one; asm volatile ( "fldl %2\n" "fptan\n" "fstpl %1\n" "fstpl %0\n" : "=m"(out), "=m"(one) : "m"(_x) ); return out; } double atan2(double y, double x) { double out; double _x = x; double _y = y; asm volatile ( "fldl %1\n" "fldl %2\n" "fpatan\n" "fstpl %0\n" : "=m"(out) : "m"(_y), "m"(_x) ); return out; } double sqrt(double x) { /* This is what __builtin_sqrt was doing anyway? */ asm volatile ( "sqrtsd %1, %0\n" : "=x"(x) : "x"(x) ); return x; } ================================================ FILE: libc/arch/x86_64/memcpy.c ================================================ #include void * memcpy(void * restrict dest, const void * restrict src, size_t n) { asm volatile("cld; rep movsb" : "=c"((int){0}) : "D"(dest), "S"(src), "c"(n) : "flags", "memory"); return dest; } ================================================ FILE: libc/arch/x86_64/memset.c ================================================ #include void * memset(void * dest, int c, size_t n) { asm volatile("cld; rep stosb" : "=c"((int){0}) : "rdi"(dest), "a"(c), "c"(n) : "flags", "memory", "rdi"); return dest; } ================================================ FILE: libc/arch/x86_64/setjmp.c ================================================ #include __attribute__((naked)) __attribute__((returns_twice)) int setjmp(jmp_buf env) { asm volatile ( "leaq 8(%%rsp), %%rax\n" /* Return address into rax */ "movq %%rax, 0(%%rdi)\n" /* jmp_buf[0] = rsp */ "movq %%rbp, 8(%%rdi)\n" /* jmp_buf[1] = rbp */ "movq (%%rsp), %%rax\n" "movq %%rax, 16(%%rdi)\n" /* jmp_buf[2] = return address */ "movq %%rbx, 24(%%rdi)\n" /* jmp_buf[3] = rbx */ "movq %%r12, 32(%%rdi)\n" /* jmp_buf[4] = r12 */ "movq %%r13, 40(%%rdi)\n" /* jmp_buf[5] = r12 */ "movq %%r14, 48(%%rdi)\n" /* jmp_buf[6] = r12 */ "movq %%r15, 56(%%rdi)\n" /* jmp_buf[7] = r12 */ "xor %%rax, %%rax\n" /* return 0 */ "retq" :::"memory" ); } __attribute__((naked)) __attribute__((noreturn)) void longjmp(jmp_buf env, int val) { asm volatile ( "movq 0(%%rdi), %%rsp\n" "movq 8(%%rdi), %%rbp\n" "movq 24(%%rdi), %%rbx\n" "movq 32(%%rdi), %%r12\n" "movq 40(%%rdi), %%r13\n" "movq 48(%%rdi), %%r14\n" "movq 56(%%rdi), %%r15\n" "movq %%rsi, %%rax\n" "jmpq *16(%%rdi)\n" :::"memory" ); } ================================================ FILE: libc/assert/assert.c ================================================ #include #include void __assert_func(const char * file, int line, const char * func, const char * failedexpr) { fprintf(stderr, "Assertion failed in %s:%d (%s): %s\n", file, line, func, failedexpr); exit(1); } ================================================ FILE: libc/ctype/_ctype.c ================================================ #include unsigned char _ctype_[256]= { _C, _C, _C, _C, _C, _C, _C, _C, _C, _C|_S, _C|_S, _C|_S, _C|_S, _C|_S, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _C, _S|_B, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _N, _N, _N, _N, _N, _N, _N, _N, _N, _N, _P, _P, _P, _P, _P, _P, _P, _U|_X, _U|_X, _U|_X, _U|_X, _U|_X, _U|_X, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _P, _P, _P, _P, _P, _P, _L|_X, _L|_X, _L|_X, _L|_X, _L|_X, _L|_X, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _P, _P, _P, _P, _C }; ================================================ FILE: libc/ctype/isalnum.c ================================================ #include int isalnum(int c) { return isalpha(c) || isdigit(c); } ================================================ FILE: libc/ctype/isalpha.c ================================================ int isalpha(int c) { return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); } ================================================ FILE: libc/ctype/isascii.c ================================================ int isascii(int c) { return (c <= 0x7f); } ================================================ FILE: libc/ctype/iscntrl.c ================================================ int iscntrl(int c) { return ((c >= 0 && c <= 0x1f) || (c == 0x7f)); } ================================================ FILE: libc/ctype/isdigit.c ================================================ int isdigit(int c) { return (c >= '0' && c <= '9'); } ================================================ FILE: libc/ctype/isgraph.c ================================================ int isgraph(int c) { return (c >= '!' && c <= '~'); } ================================================ FILE: libc/ctype/islower.c ================================================ int islower(int c) { return (c >= 'a' && c <= 'z'); } ================================================ FILE: libc/ctype/isprint.c ================================================ #include int isprint(int c) { return isgraph(c) || c == ' '; } ================================================ FILE: libc/ctype/ispunct.c ================================================ #include int ispunct(int c) { return isgraph(c) && !isalnum(c); } ================================================ FILE: libc/ctype/isspace.c ================================================ int isspace(int c) { return (c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v' || c == ' '); } ================================================ FILE: libc/ctype/isupper.c ================================================ int isupper(int c) { return (c >= 'A' && c <= 'Z'); } ================================================ FILE: libc/ctype/isxdigit.c ================================================ int isxdigit(int c) { return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); } ================================================ FILE: libc/ctype/tolower.c ================================================ int tolower(int c) { if (c >= 'A' && c <= 'Z') { return c - 'A' + 'a'; } return c; } ================================================ FILE: libc/ctype/toupper.c ================================================ int toupper(int c) { if (c >= 'a' && c <= 'z') { return c - 'a' + 'A'; } return c; } ================================================ FILE: libc/dirent/dir.c ================================================ #include #include #include #include #include #include #include #include DEFN_SYSCALL3(readdir, SYS_READDIR, int, int, void *); DIR * opendir (const char * dirname) { int fd = open(dirname, O_RDONLY|O_DIRECTORY); if (fd < 0) { /* errno was set by open */ return NULL; } DIR * dir = (DIR *)malloc(sizeof(DIR)); dir->fd = fd; dir->cur_entry = -1; return dir; } int closedir (DIR * dir) { if (dir && (dir->fd != -1)) { return close(dir->fd); } else { return -EBADF; } } struct dirent * readdir (DIR * dirp) { static struct dirent ent; int ret = syscall_readdir(dirp->fd, ++dirp->cur_entry, &ent); if (ret < 0) { errno = -ret; memset(&ent, 0, sizeof(struct dirent)); return NULL; } if (ret == 0) { /* end of directory */ memset(&ent, 0, sizeof(struct dirent)); return NULL; } return &ent; } long telldir(DIR * dirp) { return (long)dirp->cur_entry; } void rewinddir(DIR * dirp) { dirp->cur_entry = -1; } void seekdir(DIR * dirp, long loc) { dirp->cur_entry = loc; } struct dirent32 { unsigned int d_ino; char d_name[256]; }; struct dirent32 * readdir32 (DIR * dirp) { static struct dirent32 ent; struct dirent* big = readdir(dirp); if (!big) return NULL; ent.d_ino = big->d_ino; memcpy(ent.d_name,big->d_name,sizeof(ent.d_name)); return &ent; } struct dirent32 * readdir32 (DIR * dirp) __asm__("readdir"); ================================================ FILE: libc/dirent/mkdir.c ================================================ #include #include #include #include DEFN_SYSCALL2(mkdir, SYS_MKDIR, char *, unsigned int); int mkdir(const char *pathname, mode_t mode) { __sets_errno(syscall_mkdir((char *)pathname, mode)); } ================================================ FILE: libc/dlfcn/dlfcn.c ================================================ /* * Stub library for providing dlopen, dlclose, dlsym. */ #include static char * error = "dlopen functions not available"; void * __attribute__((weak)) dlopen(const char * filename, int flags) { return NULL; } int __attribute__((weak)) dlclose(void * handle) { return -1; } void * dlsym(void * handle, const char * symbol) { return NULL; } char * dlerror(void) { return error; } int __cxa_atexit(void (*fn)(void *), void * arg, void *d) { return 0; } void __ld_symbol_table(void) { } void __ld_objects_table(void) { } ================================================ FILE: libc/errno/errorno.c ================================================ #include int errno = 0; ================================================ FILE: libc/iconv/iconv.c ================================================ #include #include #include struct _iconv_state { char *tocode; char *fromcode; }; iconv_t iconv_open(const char *tocode, const char *fromcode) { errno = EINVAL; return (iconv_t)-1; #if 0 struct _iconv_state * state = malloc(sizeof(struct _iconv_state)); state->tocode = strdup(tocode); state->fromcode = strdup(fromcode); return (iconv_t)state; #endif } int iconv_close(iconv_t cd) { struct _iconv_state * state = (struct _iconv_state*)cd; free(state->tocode); free(state->fromcode); free(cd); return 0; } size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft) { return -1; } ================================================ FILE: libc/ioctl/ioctl.c ================================================ #include #include #include #include #include DEFN_SYSCALL3(ioctl, SYS_IOCTL, int, unsigned long, void *); int ioctl(int fd, unsigned long request, void * argp) { __sets_errno(syscall_ioctl(fd, request, argp)); } /* termios */ speed_t cfgetispeed(const struct termios * tio) { return 0; } speed_t cfgetospeed(const struct termios * tio) { return tio->c_cflag & CBAUD; } int cfsetispeed(struct termios * tio, speed_t speed) { /* hahahaha, yeah right */ return 0; } int cfsetospeed(struct termios * tio, speed_t speed) { tio->c_cflag = (tio->c_cflag & ~CBAUD) | (speed & CBAUD); return 0; } int tcdrain(int i) { //DEBUG_STUB("tcdrain(%d)\n", i); return 0; } int tcflow(int fd, int arg) { return ioctl(fd, TCXONC, (void*)(uintptr_t)arg); } int tcflush(int fd, int arg) { return ioctl(fd, TCFLSH, (void*)(uintptr_t)arg); } pid_t tcgetsid(int fd) { //DEBUG_STUB("tcgetsid(%d)\n", fd); return getpid(); } int tcsendbreak(int fd, int arg) { return ioctl(fd, TCSBRK, (void*)(uintptr_t)arg); } int tcgetattr(int fd, struct termios * tio) { return ioctl(fd, TCGETS, tio); } int tcsetattr(int fd, int actions, struct termios * tio) { switch (actions) { case TCSANOW: return ioctl(fd, TCSETS, tio); case TCSADRAIN: return ioctl(fd, TCSETSW, tio); case TCSAFLUSH: return ioctl(fd, TCSETSF, tio); default: return 0; } } int tcsetpgrp(int fd, pid_t pgrp) { return ioctl(fd, TIOCSPGRP, &pgrp); } pid_t tcgetpgrp(int fd) { pid_t pgrp; ioctl(fd, TIOCGPGRP, &pgrp); return pgrp; } ================================================ FILE: libc/libgen/basename.c ================================================ #include #include #include char * basename(char * path) { char * s = path; char * c = NULL; do { while (*s == '/') { *s = '\0'; s++; if (!*s) goto _done; } c = s; s = strchr(c,'/'); } while (s); _done: if (!c) { return "/"; } return c; } ================================================ FILE: libc/libgen/dirname.c ================================================ #include #include #include char * dirname(char * path) { int has_slash = 0; char * c = path; while (*c) { if (*c == '/') { has_slash = 1; } c++; } if (!has_slash) { return "."; } c--; while (*c == '/') { *c = '\0'; if (c == path) break; c--; } if (c == path) { return "/"; } /* All trailing slashes are cleared out */ while (*c != '/') { *c = '\0'; if (c == path) break; c--; } if (c == path) { if (*c == '/') return "/"; return "."; } while (*c == '/') { if (c == path) return "/"; *c = '\0'; c--; } return path; } ================================================ FILE: libc/libintl/libintl.c ================================================ /* Stub. */ #include char * gettext (const char * msgid) { return (char*)msgid; } char * dgettext (const char * domainname, const char * msgid) { return (char*)msgid; } char * dcgettext (const char * domainname, const char * msgid, int category) { return (char*)msgid; } char * ngettext (const char * msgid, const char * msgid_plural, unsigned long int n) { if (n != 1) return (char*)msgid_plural; return (char*)msgid; } char * dngettext (const char * domainname, const char * msgid, const char * msgid_plural, unsigned long int n) { if (n != 1) return (char*)msgid_plural; return (char*)msgid; } char * dcngettext (const char * domainname, const char * msgid, const char * msgid_plural, unsigned long int n, int category) { if (n != 1) return (char*)msgid_plural; return (char*)msgid; } ================================================ FILE: libc/locale/localeconv.c ================================================ #include static struct lconv _en_US = { .decimal_point = ".", .thousands_sep = ",", .grouping = "\x03\x03", .int_curr_symbol = "USD ", .currency_symbol = "$", .mon_decimal_point = ".", .mon_thousands_sep = ",", .mon_grouping = "\x03\x03", .positive_sign = "+", .negative_sign = "-", .int_frac_digits = 2, .frac_digits = 2, .p_cs_precedes = 1, .p_sep_by_space = 0, .n_cs_precedes = 1, .n_sep_by_space = 0, .p_sign_posn = 1, .n_sign_posn = 1, }; struct lconv * localeconv(void) { return &_en_US; } ================================================ FILE: libc/locale/setlocale.c ================================================ #include #include char * setlocale(int category, const char *locale) { return "en_US"; } ================================================ FILE: libc/main.c ================================================ #include #include #include #include #include #include DEFN_SYSCALL1(exit, SYS_EXT, int); DEFN_SYSCALL2(sleepabs, SYS_SLEEPABS, unsigned long, unsigned long); extern void _init(); extern void _fini(); char ** environ = NULL; int _environ_size = 0; char * _argv_0 = NULL; int __libc_debug = 0; char ** __argv = NULL; extern char ** __get_argv(void) { return __argv; } extern void __stdio_init_buffers(void); extern void __stdio_cleanup(void); void _exit(int val){ _fini(); __stdio_cleanup(); syscall_exit(val); __builtin_unreachable(); } extern void __make_tls(void); int __libc_is_multicore = 0; static int __libc_init_called = 0; __attribute__((constructor)) static void _libc_init(void) { __libc_init_called = 1; __make_tls(); __stdio_init_buffers(); __libc_is_multicore = sysfunc(TOARU_SYS_FUNC_NPROC, NULL) > 1; unsigned int x = 0; unsigned int nulls = 0; for (x = 0; 1; ++x) { if (!__get_argv()[x]) { ++nulls; if (nulls == 2) { break; } continue; } if (nulls == 1) { environ = &__get_argv()[x]; break; } } if (!environ) { environ = malloc(sizeof(char *) * 4); environ[0] = NULL; environ[1] = NULL; environ[2] = NULL; environ[3] = NULL; _environ_size = 4; } else { /* Find actual size */ int size = 0; char ** tmp = environ; while (*tmp) { size++; tmp++; } if (size < 4) { _environ_size = 4; } else { /* Multiply by two */ _environ_size = size * 2; } char ** new_environ = malloc(sizeof(char*) * _environ_size); int i = 0; while (i < _environ_size && environ[i]) { new_environ[i] = environ[i]; i++; } while (i < _environ_size) { new_environ[i] = NULL; i++; } environ = new_environ; } if (getenv("__LIBC_DEBUG")) __libc_debug = 1; _argv_0 = __get_argv()[0]; } void pre_main(int argc, char * argv[], char ** envp, int (*main)(int,char**)) { if (!__get_argv()) { /* Statically loaded, must set __argv so __get_argv() works */ __argv = argv; /* Run our initializers, because I'm pretty sure the kernel didn't... */ if (!__libc_init_called) { extern uintptr_t __init_array_start __attribute__((weak)); extern uintptr_t __init_array_end __attribute__((weak)); for (uintptr_t * constructor = &__init_array_start; constructor < &__init_array_end; ++constructor) { void (*constr)(void) = (void*)*constructor; constr(); } } } _init(); exit(main(argc, argv)); } ================================================ FILE: libc/math/bad.c ================================================ /* STUB MATH LIBRARY */ #include #include #include extern char * _argv_0; #define BAD do { if (getenv("LIBM_DEBUG")) { fprintf(stderr, "%s called bad math function %s\n", _argv_0, __func__); } } while (0) double acos(double x) { BAD; return 0.0; } double asin(double x) { BAD; return 0.0; } double ldexp(double a, int exp) { double out = a; while (exp) { out *= 2.0; exp--; } return out; } double log(double x) { BAD; return 0.0; } double log10(double x) { BAD; return 0.0; } double log2(double x) { BAD; return 0.0; } double log1p(double x) { BAD; return log(x+1.0); } double expm1(double x) { BAD; return exp(x) - 1.0; } double trunc(double x) { BAD; return (double)(long)x; } double acosh(double x) { BAD; return 0.0; } double asinh(double x) { BAD; return 0.0; } double atanh(double x) { BAD; return 0.0; } double erf(double x) { BAD; return 0.0; } double erfc(double x) { BAD; return 0.0; } double gamma(double x) { BAD; return 0.0; } double tgamma(double x){ BAD; return 0.0; } double lgamma(double x){ BAD; return 0.0; } double remainder(double x, double y) { BAD; return 0.0; } double copysign(double x, double y) { if (y < 0) { if (x < 0) return x; return -x; } else { if (x < 0) return -x; return x; } } ================================================ FILE: libc/math/math.c ================================================ #include #include #include #include #if 0 extern char * _argv_0; #define MATH do { if (getenv("LIBM_DEBUG")) { fprintf(stderr, "%s called math function %s\n", _argv_0, __func__); } } while (0) #else #define MATH (void)0 #endif double exp(double x) { return pow(M_E, x); } int abs(int j) { return (j < 0 ? -j : j); } double fabs(double x) { MATH; return __builtin_fabs(x); } float fabsf(float x) { return fabs(x); } float sqrtf(float x) { return sqrt(x); } static double bad_sine_table[] = { 0, 0.01745240644, 0.03489949671, 0.05233595625, 0.06975647375, 0.08715574276, 0.1045284633, 0.1218693434, 0.139173101, 0.1564344651, 0.1736481777, 0.1908089954, 0.2079116908, 0.2249510544, 0.2419218956, 0.2588190451, 0.2756373559, 0.2923717048, 0.3090169944, 0.3255681545, 0.3420201434, 0.3583679496, 0.3746065935, 0.3907311285, 0.4067366431, 0.4226182618, 0.4383711468, 0.4539904998, 0.4694715628, 0.4848096203, 0.5000000001, 0.515038075, 0.5299192643, 0.5446390351, 0.5591929035, 0.5735764364, 0.5877852524, 0.6018150232, 0.6156614754, 0.6293203911, 0.6427876098, 0.6560590291, 0.6691306064, 0.6819983601, 0.6946583705, 0.7071067813, 0.7193398004, 0.7313537017, 0.7431448256, 0.7547095803, 0.7660444432, 0.7771459615, 0.7880107537, 0.7986355101, 0.8090169944, 0.8191520444, 0.8290375726, 0.838670568, 0.8480480962, 0.8571673008, 0.8660254039, 0.8746197072, 0.8829475929, 0.8910065243, 0.8987940464, 0.9063077871, 0.9135454577, 0.9205048535, 0.9271838546, 0.9335804266, 0.9396926208, 0.9455185757, 0.9510565163, 0.956304756, 0.961261696, 0.9659258263, 0.9702957263, 0.9743700648, 0.9781476008, 0.9816271835, 0.984807753, 0.9876883406, 0.9902680688, 0.9925461517, 0.9945218954, 0.9961946981, 0.9975640503, 0.9986295348, 0.999390827, 0.9998476952, 1, 0.9998476952, 0.999390827, 0.9986295347, 0.9975640502, 0.9961946981, 0.9945218953, 0.9925461516, 0.9902680687, 0.9876883406, 0.984807753, 0.9816271834, 0.9781476007, 0.9743700647, 0.9702957262, 0.9659258262, 0.9612616959, 0.9563047559, 0.9510565162, 0.9455185755, 0.9396926207, 0.9335804264, 0.9271838545, 0.9205048534, 0.9135454575, 0.9063077869, 0.8987940462, 0.8910065241, 0.8829475927, 0.874619707, 0.8660254036, 0.8571673006, 0.848048096, 0.8386705678, 0.8290375724, 0.8191520441, 0.8090169942, 0.7986355099, 0.7880107534, 0.7771459613, 0.7660444429, 0.75470958, 0.7431448253, 0.7313537014, 0.7193398001, 0.707106781, 0.6946583702, 0.6819983598, 0.6691306061, 0.6560590288, 0.6427876094, 0.6293203908, 0.6156614751, 0.6018150229, 0.587785252, 0.5735764361, 0.5591929032, 0.5446390347, 0.5299192639, 0.5150380746, 0.4999999997, 0.4848096199, 0.4694715625, 0.4539904994, 0.4383711465, 0.4226182614, 0.4067366428, 0.3907311282, 0.3746065931, 0.3583679492, 0.342020143, 0.3255681541, 0.309016994, 0.2923717044, 0.2756373555, 0.2588190447, 0.2419218952, 0.224951054, 0.2079116904, 0.190808995, 0.1736481773, 0.1564344647, 0.1391731006, 0.121869343, 0.1045284629, 0.08715574235, 0.06975647334, 0.05233595584, 0.0348994963, 0.01745240603, -0.000000000410206857, -0.01745240685, -0.03489949712, -0.05233595666, -0.06975647416, -0.08715574317, -0.1045284637, -0.1218693438, -0.1391731014, -0.1564344655, -0.1736481781, -0.1908089958, -0.2079116912, -0.2249510548, -0.241921896, -0.2588190455, -0.2756373562, -0.2923717052, -0.3090169948, -0.3255681549, -0.3420201438, -0.35836795, -0.3746065938, -0.3907311289, -0.4067366435, -0.4226182622, -0.4383711472, -0.4539905002, -0.4694715632, -0.4848096207, -0.5000000004, -0.5150380753, -0.5299192646, -0.5446390354, -0.5591929039, -0.5735764368, -0.5877852527, -0.6018150235, -0.6156614757, -0.6293203914, -0.6427876101, -0.6560590294, -0.6691306067, -0.6819983604, -0.6946583708, -0.7071067815, -0.7193398007, -0.731353702, -0.7431448258, -0.7547095806, -0.7660444435, -0.7771459618, -0.7880107539, -0.7986355104, -0.8090169947, -0.8191520446, -0.8290375729, -0.8386705682, -0.8480480964, -0.857167301, -0.8660254041, -0.8746197074, -0.8829475931, -0.8910065244, -0.8987940465, -0.9063077873, -0.9135454579, -0.9205048537, -0.9271838548, -0.9335804267, -0.939692621, -0.9455185758, -0.9510565165, -0.9563047561, -0.9612616961, -0.9659258264, -0.9702957264, -0.9743700649, -0.9781476009, -0.9816271836, -0.9848077531, -0.9876883407, -0.9902680688, -0.9925461517, -0.9945218954, -0.9961946981, -0.9975640503, -0.9986295348, -0.999390827, -0.9998476952, -1, -0.9998476951, -0.999390827, -0.9986295347, -0.9975640502, -0.996194698, -0.9945218953, -0.9925461516, -0.9902680687, -0.9876883405, -0.9848077529, -0.9816271833, -0.9781476006, -0.9743700646, -0.9702957261, -0.9659258261, -0.9612616958, -0.9563047558, -0.9510565161, -0.9455185754, -0.9396926206, -0.9335804263, -0.9271838543, -0.9205048532, -0.9135454574, -0.9063077868, -0.898794046, -0.8910065239, -0.8829475925, -0.8746197068, -0.8660254034, -0.8571673003, -0.8480480958, -0.8386705676, -0.8290375722, -0.8191520439, -0.809016994, -0.7986355096, -0.7880107532, -0.777145961, -0.7660444427, -0.7547095798, -0.743144825, -0.7313537011, -0.7193397998, -0.7071067807, -0.6946583699, -0.6819983595, -0.6691306058, -0.6560590284, -0.6427876091, -0.6293203905, -0.6156614747, -0.6018150226, -0.5877852517, -0.5735764357, -0.5591929029, -0.5446390344, -0.5299192636, -0.5150380743, -0.4999999993, -0.4848096196, -0.4694715621, -0.4539904991, -0.4383711461, -0.422618261, -0.4067366424, -0.3907311278, -0.3746065927, -0.3583679488, -0.3420201426, -0.3255681537, -0.3090169936, -0.292371704, -0.2756373551, -0.2588190443, -0.2419218948, -0.2249510536, -0.20791169, -0.1908089946, -0.1736481769, -0.1564344643, -0.1391731002, -0.1218693426, -0.1045284625, -0.08715574194, -0.06975647293, -0.05233595543, -0.03489949589, -0.01745240562, 0.0 }; double sin(double x) { MATH; if (x < 0.0) { x += M_PI * 2.0 * 100.0; } int i = x * 360.0 / (M_PI * 2.0); double z = x * 360.0 / (M_PI * 2.0); z -= i; i = i % 360; //fprintf(stderr, "z = %f\n", z); double a = bad_sine_table[i]; double b = bad_sine_table[(i+1)%360]; return a * (1.0-z) + b * (z); } double cos(double x) { return sin(x + M_PI / 2.0); } double atan(double x) { return atan2(x,1.0); } double hypot(double x, double y) { return sqrt(x * x + y * y); } double modf(double x, double *iptr) { MATH; int i = (int)x; *iptr = (double)i; return x - i; } double frexp(double x, int *exp) { MATH; struct { uint32_t lsw; uint32_t msw; } extract; memcpy(&extract, &x, sizeof(double)); *exp = ((extract.msw & 0x7ff00000) >> 20) - 0x3FE; struct { uint32_t lsw; uint32_t msw; } out_double; out_double.msw = (extract.msw & 0x800fffff) | 0x3FE00000; out_double.lsw = extract.lsw; double out; memcpy(&out, &out_double, sizeof(double)); return out; } double cosh(double x) { return (exp(x) + exp(-x)) / 2.0; } double sinh(double x) { return (exp(x) - exp(-x)) / 2.0; } double tanh(double x) { return (exp(2.0*x) - 1.0) / (exp(2.0*x) + 1.0); } int fpclassify(double x) { union { double asFloat; uint64_t asInt; } bits = {x}; uint64_t exponent = (bits.asInt >> 52) & 0x7FF; uint64_t mantissa = (bits.asInt & 0xFffffFFFFffffULL); if (exponent == 0x7FF) { return mantissa ? FP_NAN : FP_INFINITE; } else if (exponent == 0) { return mantissa ? FP_SUBNORMAL : FP_ZERO; } return FP_NORMAL; } double ceil(double x) { union { double asFloat; uint64_t asInt; } bits = {x}; int64_t exponent = ((bits.asInt >> 52) & 0x7FF) - 0x3ff; uint64_t mantissa = (bits.asInt & 0xFffffFFFFffffULL); if (exponent >= 52 || x == 0) return x; if (exponent <= -1) return (bits.asInt >> 63) ? -0.0 : 1.0; uint64_t mask = (0xfffffFFFFffffULL >> exponent); if (!(mask & mantissa)) return x; if (!(bits.asInt >> 63)) { bits.asInt += mask; } bits.asInt &= ~mask; return bits.asFloat; } double round(double x) { union { double asFloat; uint64_t asInt; } bits = {x}; int64_t exponent = ((bits.asInt >> 52) & 0x7FF) - 0x3ff; uint64_t mantissa = (bits.asInt & 0xFffffFFFFffffULL); if (exponent >= 52 || x == 0) return x; if (exponent < -1) return (bits.asInt >> 63) ? -0.0 : 0.0; if (exponent == -1) return x >= 0.5 ? 1.0 : (x <= -0.5 ? -1.0 : ((bits.asInt >> 63) ? -0.0 : 0.0)); uint64_t mask = 0xfffffFFFFffffULL >> exponent; uint64_t a = mantissa & mask; uint64_t b = mantissa & (mask >> 1); if (a & ~b) { bits.asInt += mask; bits.asInt &= ~mask; return bits.asFloat; } bits.asInt &= ~mask; return bits.asFloat; } float ceilf(float x) { return ceil(x); } float roundf(float x) { return round(x); } long lroundf(float x) { return round(x); } ================================================ FILE: libc/poll/poll.c ================================================ #include #include #include #include extern char * _argv_0; static char * poll_print_flags(int flags) { static char buf[100]; char * b = buf; int saw_something = 0; #define OPT(n) do { if (flags & n) { b += sprintf(b, "%s%s", saw_something ? "|" : "", #n); saw_something = 1; } } while (0) OPT(POLLIN); OPT(POLLOUT); OPT(POLLRDHUP); OPT(POLLERR); OPT(POLLHUP); OPT(POLLNVAL); OPT(POLLPRI); return buf; } extern int __libc_debug; int poll(struct pollfd *fds, nfds_t nfds, int timeout) { int count_pollin = 0; for (nfds_t i = 0; i < nfds; ++i) { if (fds[i].events & POLLIN) { count_pollin++; } fds[i].revents = 0; } for (nfds_t i = 0; i < nfds; ++i) { if (fds[i].events & (~POLLIN)) { if (fds[i].events == (POLLIN | POLLPRI)) continue; if (__libc_debug) { fprintf(stderr, "%s: poll: unsupported bit set in fds: %s\n", _argv_0, poll_print_flags(fds[i].events)); } if ((fds[i].events & POLLOUT) && nfds == 1) { fds[i].revents |= POLLOUT; return 1; } //if (fds[i].events & POLLIN) continue; //fprintf(stderr, "%s: poll: the above error is fatal\n", _argv_0); //return -EINVAL; } } int fswait_fds[count_pollin]; int fswait_backref[count_pollin]; int fswait_results[count_pollin]; int j = 0; for (nfds_t i = 0; i < nfds; ++i) { if (fds[i].events & POLLIN) { fswait_fds[j] = fds[i].fd; fswait_backref[j] = i; j++; } } int ret = fswait3(count_pollin, fswait_fds, timeout, fswait_results); if (ret >= 0 && ret < count_pollin) { int count = 0; for (int i = 0; i < count_pollin; ++i) { if (fswait_results[i]) { fds[fswait_backref[i]].revents = POLLIN; count++; } } return count; } else if (ret == count_pollin) { return 0; } else { return ret; /* Error */ } } ================================================ FILE: libc/pthread/pthread.c ================================================ /* This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2012-2018 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include DEFN_SYSCALL3(clone, SYS_CLONE, uintptr_t, uintptr_t, void *); DEFN_SYSCALL0(gettid, SYS_GETTID); struct __pthread { pid_t tid; void * (*entry)(void*); void * arg; }; extern int __libc_is_multicore; static inline void _yield(void) { if (!__libc_is_multicore) syscall_yield(); } #define PTHREAD_STACK_SIZE 0x100000 int clone(uintptr_t a,uintptr_t b,void* c) { __sets_errno(syscall_clone(a,b,c)); } int gettid() { return syscall_gettid(); /* never fails */ } void * __tls_get_addr(void* input) { #ifdef __x86_64__ struct tls_index { uintptr_t module; uintptr_t offset; }; struct tls_index * index = input; /* We only support initial-exec stuff, so this must be %fs:offset */ uintptr_t threadbase; asm ("mov %%fs:0, %0" :"=r"(threadbase)); return (void*)(threadbase + index->offset); #else return NULL; #endif } void __make_tls(void) { char * tlsSpace = valloc(4096); memset(tlsSpace, 0x0, 4096); /* self-pointer start? */ char ** tlsSelf = (char **)(tlsSpace); *tlsSelf = (char*)tlsSelf; sysfunc(TOARU_SYS_FUNC_SETGSBASE, (char*[]){(char*)tlsSelf}); } void pthread_exit(void * value) { syscall_exit(0); __builtin_unreachable(); } void * __thread_start(void * pthreadbase) { struct __pthread * this = pthreadbase; this->tid = gettid(); char ** tlsbase = (char**)((char*)this + 4096); *tlsbase = (char*)tlsbase; sysfunc(TOARU_SYS_FUNC_SETGSBASE, (char*[]){(char*)tlsbase}); pthread_exit(this->entry(this->arg)); return NULL; } int pthread_create(pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg) { char * stack = valloc(PTHREAD_STACK_SIZE + 8192); memset(stack, 0, PTHREAD_STACK_SIZE + 8192); struct __pthread * this = (void*)(stack + PTHREAD_STACK_SIZE); *thread = this; this->entry = start_routine; this->arg = arg; clone((uintptr_t)this, (uintptr_t)__thread_start, this); return 0; } int pthread_kill(pthread_t thread, int sig) { __sets_errno(kill(thread->tid, sig)); } void pthread_cleanup_push(void (*routine)(void *), void *arg) { /* do nothing */ } void pthread_cleanup_pop(int execute) { /* do nothing */ } int pthread_mutex_lock(pthread_mutex_t *mutex) { while (__sync_lock_test_and_set(mutex, 0x01)) { _yield(); } return 0; } int pthread_mutex_trylock(pthread_mutex_t *mutex) { if (__sync_lock_test_and_set(mutex, 0x01)) { return EBUSY; } return 0; } int pthread_mutex_unlock(pthread_mutex_t *mutex) { __sync_lock_release(mutex); return 0; } int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr) { *mutex = 0; return 0; } int pthread_mutex_destroy(pthread_mutex_t *mutex) { return 0; } int pthread_attr_init(pthread_attr_t *attr) { *attr = 0; return 0; } int pthread_attr_destroy(pthread_attr_t *attr) { return 0; } int pthread_join(pthread_t thread, void **retval) { int status; int result = waitpid(thread->tid, &status, 0); if (retval) { *retval = (void*)(uintptr_t)status; } return result; } ================================================ FILE: libc/pthread/pthread_rwlock.c ================================================ #include #include #include #include #include #include #include #include #include extern int __libc_is_multicore; static inline void _yield(void) { if (!__libc_is_multicore) syscall_yield(); } #define ACQUIRE_LOCK() do { while (__sync_lock_test_and_set(&lock->atomic_lock, 0x01)) { _yield(); } } while (0) #define RELEASE_LOCK() do { __sync_lock_release(&lock->atomic_lock); } while (0) int pthread_rwlock_init(pthread_rwlock_t * lock, void * args) { lock->readers = 0; lock->atomic_lock = 0; if (args != NULL) { fprintf(stderr, "pthread: pthread_rwlock_init arg unsupported\n"); return 1; } return 0; } int pthread_rwlock_wrlock(pthread_rwlock_t * lock) { ACQUIRE_LOCK(); while (1) { if (lock->readers == 0) { lock->readers = -1; lock->writerPid = syscall_getpid(); RELEASE_LOCK(); return 0; } _yield(); } } int pthread_rwlock_rdlock(pthread_rwlock_t * lock) { ACQUIRE_LOCK(); while (1) { if (lock->readers >= 0) { lock->readers++; RELEASE_LOCK(); return 0; } _yield(); } } int pthread_rwlock_unlock(pthread_rwlock_t * lock) { ACQUIRE_LOCK(); if (lock->readers > 0) lock->readers--; else if (lock->readers < 0) lock->readers = 0; else fprintf(stderr, "pthread: bad lock state detected\n"); RELEASE_LOCK(); return 0; } int pthread_rwlock_destroy(pthread_rwlock_t * lock) { return 0; } ================================================ FILE: libc/pty/pty.c ================================================ #include #include #include #include #include DEFN_SYSCALL5(openpty, SYS_OPENPTY, int *, int *, char *, void *, void *); int openpty(int * amaster, int * aslave, char * name, const struct termios *termp, const struct winsize * winp) { __sets_errno(syscall_openpty(amaster,aslave,name,(struct termios *)termp,(struct winsize *)winp)); } ================================================ FILE: libc/pwd/pwd.c ================================================ /*** * getpwent, setpwent, endpwent, fgetpwent * getpwuid, getpwnam * * These functions manage entries in the password files. * * @copyright * 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 */ #include #include #include #include #include /* struct passwd { char * pw_name; // username char * pw_passwd; // password (not meaningful) uid_t pw_uid; // user id gid_t pw_gid; // group id char * pw_comment; // used for decoration settings in toaruos char * pw_gecos; // full name char * pw_dir; // home directory char * pw_shell; // shell } */ static FILE * pwdb = NULL; static void open_it(void) { pwdb = fopen("/etc/passwd", "r"); } #define LINE_LEN 2048 static struct passwd * pw_ent; static char * pw_blob; struct passwd * fgetpwent(FILE * stream) { if (!stream) { return NULL; } if (!pw_ent) { pw_ent = malloc(sizeof(struct passwd)); pw_blob = malloc(LINE_LEN); } memset(pw_blob, 0x00, LINE_LEN); fgets(pw_blob, LINE_LEN, stream); if (pw_blob[strlen(pw_blob)-1] == '\n') { pw_blob[strlen(pw_blob)-1] = '\0'; /* erase newline */ } /* Tokenize */ char *p, *tokens[8], *last; int i = 0; for ((p = strtok_r(pw_blob, ":", &last)); p; (p = strtok_r(NULL, ":", &last)), i++) { tokens[i] = p; } if (i < 8) return NULL; pw_ent->pw_name = tokens[0]; pw_ent->pw_passwd = tokens[1]; pw_ent->pw_uid = atoi(tokens[2]); pw_ent->pw_gid = atoi(tokens[3]); pw_ent->pw_gecos = tokens[4]; pw_ent->pw_dir = tokens[5]; pw_ent->pw_shell = tokens[6]; pw_ent->pw_comment = tokens[7]; return pw_ent; } struct passwd * getpwent(void) { if (!pwdb) { open_it(); } if (!pwdb) { return NULL; } return fgetpwent(pwdb); } void setpwent(void) { /* Reset stream to beginning */ if (pwdb) rewind(pwdb); } void endpwent(void) { /* Close stream */ if (pwdb) { fclose(pwdb); pwdb = NULL; } if (pw_ent) { free(pw_ent); free(pw_blob); pw_ent = NULL; pw_blob = NULL; } } struct passwd * getpwnam(const char * name) { struct passwd * p; setpwent(); while ((p = getpwent())) { if (!strcmp(p->pw_name, name)) { return p; } } return NULL; } struct passwd * getpwuid(uid_t uid) { struct passwd * p; setpwent(); while ((p = getpwent())) { if (p->pw_uid == uid) { return p; } } return NULL; } ================================================ FILE: libc/sched/sched_yield.c ================================================ #include #include #include #include DEFN_SYSCALL0(yield, SYS_YIELD); int sched_yield(void) { __sets_errno(syscall_yield()); } ================================================ FILE: libc/signal/kill.c ================================================ #include #include #include #include DEFN_SYSCALL2(kill, SYS_KILL, int, int); int kill(int pid, int sig) { __sets_errno(syscall_kill(pid, sig)); } ================================================ FILE: libc/signal/raise.c ================================================ #include #include int raise(int sig) { return kill(getpid(), sig); } ================================================ FILE: libc/signal/sigaction.c ================================================ #include #include #include #include DEFN_SYSCALL3(sigaction, SYS_SIGACTION, int, struct sigaction*, struct sigaction*); int sigaction(int signum, struct sigaction *act, struct sigaction *oldact) { __sets_errno(syscall_sigaction(signum, act, oldact)); } ================================================ FILE: libc/signal/signal.c ================================================ #include #include #include DEFN_SYSCALL2(signal, SYS_SIGNAL, int, void *); /* XXX This syscall interface is screwy, doesn't allow for good errno handling */ sighandler_t signal(int signum, sighandler_t handler) { return (sighandler_t)syscall_signal(signum, (void *)handler); } ================================================ FILE: libc/signal/sigpending.c ================================================ #include #include #include #include DEFN_SYSCALL1(sigpending, SYS_SIGPENDING, sigset_t *); int sigpending(sigset_t * set) { __sets_errno(syscall_sigpending(set)); } ================================================ FILE: libc/signal/sigprocmask.c ================================================ #include #include #include #include #include DEFN_SYSCALL3(sigprocmask, SYS_SIGPROCMASK, int, const sigset_t * restrict, sigset_t* restrict); int sigprocmask(int how, const sigset_t * restrict set, sigset_t * restrict oset) { __sets_errno(syscall_sigprocmask(how,set,oset)); } ================================================ FILE: libc/signal/sigset.c ================================================ #include #include #include #include int sigemptyset(sigset_t * set) { *set = 0; return 0; } int sigfillset(sigset_t * set) { memset(set, 0xFF, sizeof(sigset_t)); return 0; } int sigaddset(sigset_t * set, int signum) { if (signum > NUMSIGNALS) return -EINVAL; *set |= (1UL << signum); return 0; } int sigdelset(sigset_t * set, int signum) { if (signum > NUMSIGNALS) return -EINVAL; *set &= ~(1UL << signum); return 0; } int sigismember(sigset_t * set, int signum) { if (signum > NUMSIGNALS) return -EINVAL; return !!(*set & (1UL << signum)); } ================================================ FILE: libc/signal/sigsuspend.c ================================================ #include #include #include #include #include DEFN_SYSCALL1(sigsuspend, SYS_SIGSUSPEND,const sigset_t *); int sigsuspend(const sigset_t * restrict set) { __sets_errno(syscall_sigsuspend(set)); } DEFN_SYSCALL2(sigwait,SYS_SIGWAIT,const sigset_t *,int *); int sigwait(const sigset_t * set, int * sig) { int res; do { res = syscall_sigwait(set,sig); } while (res == -EINTR); if (res < 0) { res = -res; errno = res; } return res; } ================================================ FILE: libc/stdio/perror.c ================================================ #include #include #include void perror(const char *s) { fprintf(stderr, "%s: %s\n", s, strerror(errno)); } ================================================ FILE: libc/stdio/printf.c ================================================ #include #include #include #include #include #define OUT(c) do { callback(userData, (c)); written++; } while (0) static 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) { size_t written = 0; unsigned long long n_width = 1; unsigned long long i = 9; if (precision == -1) precision = 1; if (value == 0) { n_width = 0; } else { unsigned long long val = value; while (val >= 10UL) { val /= 10UL; n_width++; } } if (n_width < (unsigned long long)precision) n_width = precision; int printed = 0; if (align_right) { while (n_width + printed < width) { OUT(fill_zero ? '0' : ' '); printed += 1; } i = n_width; char tmp[100]; while (i > 0) { unsigned long long n = value / 10; long long r = value % 10; tmp[i - 1] = r + '0'; i--; value = n; } while (i < n_width) { OUT(tmp[i]); i++; } } else { i = n_width; char tmp[100]; while (i > 0) { unsigned long long n = value / 10; long long r = value % 10; tmp[i - 1] = r + '0'; i--; value = n; printed++; } while (i < n_width) { OUT(tmp[i]); i++; } while (printed < (long long)width) { OUT(fill_zero ? '0' : ' '); printed += 1; } } return written; } /* * Hexadecimal to string */ static 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) { size_t written = 0; int i = width; unsigned long long n_width = 1; unsigned long long j = 0x0F; while (value > j && j < UINT64_MAX) { n_width += 1; j *= 0x10; j += 0x0F; } if (!fill_zero && align == 1) { while (i > (long long)n_width + 2*!!alt) { OUT(' '); i--; } } if (alt) { OUT('0'); OUT(caps ? 'X' : 'x'); } if (fill_zero && align == 1) { while (i > (long long)n_width + 2*!!alt) { OUT('0'); i--; } } i = (long long)n_width; while (i-- > 0) { char c = (caps ? "0123456789ABCDEF" : "0123456789abcdef")[(value>>(i*4))&0xF]; OUT(c); } if (align == 0) { i = width; while (i > (long long)n_width + 2*!!alt) { OUT(' '); i--; } } return written; } static 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) { size_t written = 0; int i = width; unsigned long long n_width = 1; unsigned long long j = 7; while (value > j && j < UINT64_MAX) { n_width += 1; j *= 8; j += 7; } if (!fill_zero && align == 1) { while (i > (long long)n_width + !!alt) { OUT(' '); i--; } } if (alt) { OUT('0'); } if (fill_zero && align == 1) { while (i > (long long)n_width + !!alt) { OUT('0'); i--; } } i = (long long)n_width; while (i-- > 0) { char c = "01234567"[(value>>(i*3))&0x7]; OUT(c); } if (align == 0) { i = width; while (i > (long long)n_width + !!alt) { OUT(' '); i--; } } return written; } /* * vasprintf() */ size_t xvasprintf(int (*callback)(void *, char), void * userData, const char * fmt, va_list args) { char * s; size_t written = 0; for (const char *f = fmt; *f; f++) { if (*f != '%') { OUT(*f); continue; } ++f; unsigned int arg_width = 0; int align = 1; /* right */ int fill_zero = 0; int big = 0; int alt = 0; int always_sign = 0; int precision = -1; while (1) { if (*f == '-') { align = 0; ++f; } else if (*f == '#') { alt = 1; ++f; } else if (*f == '*') { arg_width = (int)va_arg(args, int); ++f; } else if (*f == '0') { fill_zero = 1; ++f; } else if (*f == '+') { always_sign = 1; ++f; } else if (*f == ' ') { always_sign = 2; ++f; } else { break; } } while (*f >= '0' && *f <= '9') { arg_width *= 10; arg_width += *f - '0'; ++f; } if (*f == '.') { ++f; precision = 0; if (*f == '*') { precision = (int)va_arg(args, int); ++f; } else { while (*f >= '0' && *f <= '9') { precision *= 10; precision += *f - '0'; ++f; } } } if (*f == 'l') { big = 1; ++f; if (*f == 'l') { big = 2; ++f; } } if (*f == 'j') { big = (sizeof(uintmax_t) == sizeof(unsigned long long) ? 2 : sizeof(uintmax_t) == sizeof(unsigned long) ? 1 : 0); ++f; } if (*f == 'z') { big = (sizeof(size_t) == sizeof(unsigned long long) ? 2 : sizeof(size_t) == sizeof(unsigned long) ? 1 : 0); ++f; } if (*f == 't') { big = (sizeof(ptrdiff_t) == sizeof(unsigned long long) ? 2 : sizeof(ptrdiff_t) == sizeof(unsigned long) ? 1 : 0); ++f; } /* fmt[i] == '%' */ switch (*f) { case 's': /* String pointer -> String */ { size_t count = 0; if (big) { return written; } else { s = (char *)va_arg(args, char *); if (s == NULL) { s = "(null)"; } if (precision >= 0) { while (*s && precision > 0) { OUT(*s++); count++; precision--; if (arg_width && count == arg_width) break; } } else { while (*s) { OUT(*s++); count++; if (arg_width && count == arg_width) break; } } } while (count < arg_width) { OUT(' '); count++; } } break; case 'c': /* Single character */ OUT((char)va_arg(args,int)); break; case 'p': alt = 1; if (sizeof(void*) == sizeof(long long)) big = 2; /* fallthrough */ case 'X': case 'x': /* Hexadecimal number */ { unsigned long long val; if (big == 2) { val = (unsigned long long)va_arg(args, unsigned long long); } else if (big == 1) { val = (unsigned long)va_arg(args, unsigned long); } else { val = (unsigned int)va_arg(args, unsigned int); } written += print_hex(val, arg_width, callback, userData, fill_zero, alt, !(*f & 32), align); } break; case 'O': case 'o': /* Octal number */ { unsigned long long val; if (big == 2) { val = (unsigned long long)va_arg(args, unsigned long long); } else if (big == 1) { val = (unsigned long)va_arg(args, unsigned long); } else { val = (unsigned int)va_arg(args, unsigned int); } written += print_oct(val, arg_width, callback, userData, fill_zero, alt, !(*f & 32), align); } break; case 'i': case 'd': /* Decimal number */ { long long val; if (big == 2) { val = (long long)va_arg(args, long long); } else if (big == 1) { val = (long)va_arg(args, long); } else { val = (int)va_arg(args, int); } if (val < 0) { OUT('-'); val = -val; } else if (always_sign) { OUT(always_sign == 2 ? ' ' : '+'); } written += print_dec(val, arg_width, callback, userData, fill_zero, align, precision); } break; case 'u': /* Unsigned ecimal number */ { unsigned long long val; if (big == 2) { val = (unsigned long long)va_arg(args, unsigned long long); } else if (big == 1) { val = (unsigned long)va_arg(args, unsigned long); } else { val = (unsigned int)va_arg(args, unsigned int); } written += print_dec(val, arg_width, callback, userData, fill_zero, align, precision); } break; case 'G': case 'F': case 'g': /* supposed to also support e */ case 'f': { if (precision == -1) precision = 8; double val = (double)va_arg(args, double); uint64_t asBits; memcpy(&asBits,&val,sizeof(double)); #define SIGNBIT(d) (d & 0x8000000000000000UL) /* Extract exponent */ int64_t exponent = (asBits & 0x7ff0000000000000UL) >> 52; /* Fraction part */ uint64_t fraction = (asBits & 0x000fffffffffffffUL); if (exponent == 0x7ff) { if (!fraction) { if (SIGNBIT(asBits)) { OUT('-'); } OUT('i'); OUT('n'); OUT('f'); } else { OUT('n'); OUT('a'); OUT('n'); } break; } else if ((*f == 'g' || *f == 'G') && exponent == 0 && fraction == 0) { if (SIGNBIT(asBits)) { OUT('-'); } OUT('0'); break; } /* Okay, now we can do some real work... */ int isNegative = !!SIGNBIT(asBits); if (isNegative) { OUT('-'); val = -val; } written += print_dec((unsigned long long)val, arg_width, callback, userData, fill_zero, align, 1); OUT('.'); for (int j = 0; j < ((precision > -1 && precision < 16) ? precision : 16); ++j) { if ((unsigned long long)(val * 100000.0) % 100000 == 0 && j != 0) break; val = val - (unsigned long long)val; val *= 10.0; double roundy = ((double)(val - (unsigned long long)val) - 0.99999); if (roundy < 0.00001 && roundy > -0.00001 && ((unsigned long long)(val) % 10) != 9) { written += print_dec((unsigned long long)(val) % 10 + 1, 0, callback, userData, 0, 0, 1); break; } written += print_dec((unsigned long long)(val) % 10, 0, callback, userData, 0, 0, 1); } } break; case '%': /* Escape */ OUT('%'); break; default: /* Nothing at all, just dump it */ OUT(*f); break; } } return written; } /* Strings */ struct CBData { char * str; size_t size; size_t written; }; static int cb_sprintf(void * user, char c) { struct CBData * data = user; if (data->size > data->written + 1) { data->str[data->written] = c; data->written++; if (data->written < data->size) { data->str[data->written] = '\0'; } } return 0; } int vsnprintf(char *str, size_t size, const char *format, va_list ap) { struct CBData data = {str,size,0}; int out = xvasprintf(cb_sprintf, &data, format, ap); cb_sprintf(&data, '\0'); return out; } int snprintf(char * str, size_t size, const char * format, ...) { struct CBData data = {str,size,0}; va_list args; va_start(args, format); int out = xvasprintf(cb_sprintf, &data, format, args); va_end(args); cb_sprintf(&data, '\0'); return out; } /* Unlimited strings */ static int cb_sxprintf(void * user, char c) { struct CBData * data = user; data->str[data->written] = c; data->written++; return 0; } int vsprintf(char *str, const char *format, va_list ap) { struct CBData data = {str,0,0}; int out = xvasprintf(cb_sxprintf, &data, format, ap); cb_sxprintf(&data, '\0'); return out; } int sprintf(char * str, const char * format, ...) { struct CBData data = {str,0,0}; va_list args; va_start(args, format); int out = xvasprintf(cb_sxprintf, &data, format, args); va_end(args); cb_sxprintf(&data, '\0'); return out; } /** * String that needs to reallocate as it goes */ static int cb_asprintf(void * user, char c) { struct CBData * data = user; if (data->written + 1 > data->size) { data->size = data->size < 8 ? 8 : data->size * 2; data->str = realloc(data->str, data->size); } data->str[data->written] = c; data->written++; return 0; } int vasprintf(char ** buf, const char * fmt, va_list args) { struct CBData data = {NULL,0,0}; xvasprintf(cb_asprintf, &data, fmt, args); cb_asprintf(&data, '\0'); *buf = data.str; return 0; } int asprintf(char ** ret, const char * fmt, ...) { va_list args; va_start(args, fmt); int out = vasprintf(ret,fmt,args); va_end(args); return out; } /* Streams */ static int cb_fprintf(void * user, char c) { fputc(c,(FILE*)user); return 0; } int fprintf(FILE *stream, const char * fmt, ...) { va_list args; va_start(args, fmt); int out = xvasprintf(cb_fprintf, stream, fmt, args); va_end(args); return out; } int printf(const char * fmt, ...) { va_list args; va_start(args, fmt); int out = xvasprintf(cb_fprintf, stdout, fmt, args); va_end(args); return out; } int vfprintf(FILE * stream, const char *fmt, va_list args) { return xvasprintf(cb_fprintf, stream, fmt, args); } int vprintf(const char *fmt, va_list args) { return xvasprintf(cb_fprintf, stdout, fmt, args); } ================================================ FILE: libc/stdio/puts.c ================================================ #include #include int puts(const char *s) { /* eof? */ fwrite(s, 1, strlen(s), stdout); fwrite("\n", 1, 1, stdout); return 0; } ================================================ FILE: libc/stdio/remove.c ================================================ #include #include int remove(const char * pathname) { /* TODO directories */ return unlink(pathname); } ================================================ FILE: libc/stdio/rename.c ================================================ #include #include #include #include DEFN_SYSCALL2(rename, SYS_RENAME, const char *, const char *); int rename(const char * oldpath, const char * newpath) { __sets_errno(syscall_rename(oldpath, newpath)); } ================================================ FILE: libc/stdio/scanf.c ================================================ #include #include #include extern char * _argv_0; extern int __libc_debug; int vsscanf(const char *str, const char *format, va_list ap) { if (__libc_debug) fprintf(stderr, "%s: sscanf(\"%s\", format=\"%s\", ...);\n", _argv_0, str, format); int count = 0; while (*format) { if (*format == ' ') { /* handle white space */ while (*str && isspace(*str)) { str++; } } else if (*format == '%') { /* Parse */ format++; int _long = 0; if (*format == 'l') { format++; if (*format == 'l') { _long = 1; if (__libc_debug) fprintf(stderr, "%s: \033[33;3mWarning\033[0m: Need to scan a large pointer (%d)\n", _argv_0, _long); format++; } } if (*format == 'd') { int i = 0; int sign = 1; while (isspace(*str)) str++; if (*str == '-') { sign = -1; str++; } while (*str && *str >= '0' && *str <= '9') { i = i * 10 + *str - '0'; str++; } int * out = (int *)va_arg(ap, int*); if (__libc_debug) fprintf(stderr, "%s: sscanf: out %d\n", _argv_0, i); count++; *out = i * sign; } else if (*format == 'u') { unsigned int i = 0; while (isspace(*str)) str++; while (*str && *str >= '0' && *str <= '9') { i = i * 10 + *str - '0'; str++; } unsigned int * out = (unsigned int *)va_arg(ap, unsigned int*); if (__libc_debug) fprintf(stderr, "%s: sscanf: out %d\n", _argv_0, i); count++; *out = i; } } else { /* Expect exact character? */ if (*str == *format) { str++; } else { break; } } format++; } return count; } int vfscanf(FILE * stream, const char *format, va_list ap) { if (__libc_debug) fprintf(stderr, "%s: fscanf(%d, format=%s, ...);\n", _argv_0, fileno(stream), format); while (*format) { if (*format == ' ') { /* Handle whitespace */ } else if (*format == '%') { /* Parse */ } else { /* Expect exact character? */ } format++; } return 0; } int sscanf(const char *str, const char *format, ...) { va_list args; va_start(args, format); int out = vsscanf(str, format, args); va_end(args); return out; } int fscanf(FILE *stream, const char *format, ...) { va_list args; va_start(args, format); int out = vfscanf(stream, format, args); va_end(args); return out; } int scanf(const char *format, ...) { va_list args; va_start(args, format); int out = vfscanf(stdin, format, args); va_end(args); return out; } ================================================ FILE: libc/stdio/stdio.c ================================================ #include #include #include #include #include #include #include #include struct _FILE { int fd; char * read_buf; int available; int offset; int read_from; int ungetc; int bufsiz; long last_read_start; char * _name; char * write_buf; size_t written; size_t wbufsiz; struct _FILE * prev; struct _FILE * next; int flags; }; /* Flag values */ #define STDIO_EOF 0x0001 #define STDIO_ERROR 0x0002 #define STDIO_UNSEEKABLE 0x0004 FILE _stdin = { .fd = 0, .read_buf = NULL, .available = 0, .offset = 0, .read_from = 0, .ungetc = -1, .last_read_start = 0, .bufsiz = BUFSIZ, .wbufsiz = BUFSIZ, .write_buf = NULL, .written = 0, .flags = 0, }; FILE _stdout = { .fd = 1, .read_buf = NULL, .available = 0, .offset = 0, .read_from = 0, .ungetc = -1, .last_read_start = 0, .bufsiz = BUFSIZ, .wbufsiz = BUFSIZ, .write_buf = NULL, .written = 0, .flags = 0, }; FILE _stderr = { .fd = 2, .read_buf = NULL, .available = 0, .offset = 0, .read_from = 0, .ungetc = -1, .last_read_start = 0, .bufsiz = BUFSIZ, .wbufsiz = BUFSIZ, .write_buf = NULL, .written = 0, .flags = 0, }; FILE * stdin = &_stdin; FILE * stdout = &_stdout; FILE * stderr = &_stderr; static FILE * _head = NULL; void __stdio_init_buffers(void) { _stdin.read_buf = malloc(BUFSIZ); _stdout.write_buf = malloc(BUFSIZ); _stderr.write_buf = malloc(BUFSIZ); _stdin._name = strdup("stdin"); _stdout._name = strdup("stdout"); _stderr._name = strdup("stderr"); } void __stdio_cleanup(void) { if (stdout) fflush(stdout); if (stderr) fflush(stderr); while (_head) { fclose(_head); } } #if 0 static char * stream_id(FILE * stream) { static char out[] = "stream\0\0\0\0\0\0"; if (stream == &_stdin) return "stdin"; if (stream == &_stdout) return "stdout"; if (stream == &_stderr) return "stderr"; sprintf(out, "stream %d", fileno(stream)); return out; } #endif extern int __libc_debug; extern char * _argv_0; int setvbuf(FILE * stream, char * buf, int mode, size_t size) { if (mode != _IOLBF) { return -1; /* Unsupported */ } if (buf) { if (stream->read_buf) { free(stream->read_buf); } stream->read_buf = buf; stream->bufsiz = size; } return 0; } int fflush(FILE * stream) { if (!stream->write_buf) return EOF; if (stream->written) { ssize_t written = syscall_write(stream->fd, stream->write_buf, stream->written); if (written < 0) { stream->flags |= STDIO_ERROR; return EOF; } stream->written = 0; } return 0; } static size_t write_bytes(FILE * f, char * buf, size_t len) { if (!f->write_buf) return 0; size_t newBytes = 0; while (len > 0) { f->write_buf[f->written++] = *buf; if (f->written == (size_t)f->wbufsiz || *buf == '\n') { if (fflush(f) == EOF) return newBytes; } newBytes++; buf++; len--; } return newBytes; } static size_t read_bytes(FILE * f, char * out, size_t len) { size_t r_out = 0; //fprintf(stderr, "%s: Read %d bytes from %s\n", _argv_0, len, stream_id(f)); //fprintf(stderr, "%s: off[%d] avail[%d] read[%d]\n", _argv_0, f->offset, f->available, f->read_from); while (len > 0) { if (f->ungetc >= 0) { *out = f->ungetc; len--; out++; r_out++; f->ungetc = -1; continue; } if (f->available == 0) { if (f->offset == f->bufsiz) { f->offset = 0; } if (!(f->flags & STDIO_UNSEEKABLE)) { f->last_read_start = syscall_seek(f->fd, 0, SEEK_CUR); if (f->last_read_start == -ESPIPE) { f->last_read_start = -1; f->flags |= STDIO_UNSEEKABLE; } } ssize_t r = read(fileno(f), &f->read_buf[f->offset], f->bufsiz - f->offset); if (r < 0) { //fprintf(stderr, "error condition\n"); f->flags |= STDIO_ERROR; return r_out; } else { f->read_from = f->offset; f->available = r; f->offset += f->available; } } if (f->available == 0) { /* EOF condition */ //fprintf(stderr, "%s: no bytes available, returning read value of %d\n", _argv_0, r_out); f->flags |= STDIO_EOF; return r_out; } //fprintf(stderr, "%s: reading until %d reaches %d or %d reaches 0\n", _argv_0, f->read_from, f->offset, len); while (f->read_from < f->offset && len > 0 && f->available > 0) { *out = f->read_buf[f->read_from]; len--; f->read_from++; f->available--; out++; r_out += 1; } } //fprintf(stderr, "%s: read completed, returning read value of %d\n", _argv_0, r_out); return r_out; } static void parse_mode(const char * mode, int * flags_, int * mask_) { const char * x = mode; int flags = 0; int mask = 0644; while (*x) { if (*x == 'a') { flags |= O_WRONLY; flags |= O_APPEND; flags |= O_CREAT; } if (*x == 'w') { flags |= O_WRONLY; flags |= O_CREAT; flags |= O_TRUNC; mask = 0666; } if (*x == '+') { flags |= O_RDWR; flags &= ~(O_APPEND); /* uh... */ } if (*x == 'x') { flags |= O_EXCL; } ++x; } *flags_ = flags; *mask_ = mask; } FILE * fopen(const char *path, const char *mode) { int flags, mask; parse_mode(mode, &flags, &mask); int fd = syscall_open(path, flags, mask); if (fd < 0) { errno = -fd; return NULL; } FILE * out = malloc(sizeof(FILE)); memset(out, 0, sizeof(struct _FILE)); out->fd = fd; out->read_buf = malloc(BUFSIZ); out->bufsiz = BUFSIZ; out->available = 0; out->read_from = 0; out->offset = 0; out->ungetc = -1; out->flags = 0; out->_name = strdup(path); out->write_buf = malloc(BUFSIZ); out->written = 0; out->wbufsiz = BUFSIZ; out->next = _head; if (_head) _head->prev = out; _head = out; return out; } /* This is very wrong */ FILE * freopen(const char *path, const char *mode, FILE * stream) { if (path) { fflush(stream); free(stream->_name); syscall_close(stream->fd); int flags, mask; parse_mode(mode, &flags, &mask); int fd = syscall_open(path, flags, mask); stream->fd = fd; stream->available = 0; stream->read_from = 0; stream->offset = 0; stream->ungetc = -1; stream->flags = 0; stream->_name = strdup(path); stream->written = 0; if (fd < 0) { errno = -fd; return NULL; } } return stream; } int ungetc(int c, FILE * stream) { if (stream->ungetc > 0) return EOF; return (stream->ungetc = c); } FILE * fdopen(int fd, const char *mode){ FILE * out = malloc(sizeof(FILE)); memset(out, 0, sizeof(struct _FILE)); out->fd = fd; out->read_buf = malloc(BUFSIZ); out->bufsiz = BUFSIZ; out->available = 0; out->read_from = 0; out->offset = 0; out->ungetc = -1; out->flags = 0; char tmp[30]; sprintf(tmp, "fd[%d]", fd); out->_name = strdup(tmp); out->write_buf = malloc(BUFSIZ); out->written = 0; out->wbufsiz = BUFSIZ; out->next = _head; if (_head) _head->prev = out; _head = out; return out; } int _fwouldblock(FILE * stream) { return !stream->available; } int fclose(FILE * stream) { fflush(stream); int out = syscall_close(stream->fd); free(stream->_name); free(stream->read_buf); if (stream->write_buf) free(stream->write_buf); stream->write_buf = NULL; if (stream == &_stdin || stream == &_stdout || stream == &_stderr) { return out; } else { if (stream->prev) stream->prev->next = stream->next; if (stream->next) stream->next->prev = stream->prev; if (stream == _head) _head = stream->next; free(stream); return out; } } static char * _whence_str(int whence) { if (whence == SEEK_SET) return "SEEK_SET"; if (whence == SEEK_CUR) return "SEEK_CUR"; if (whence == SEEK_END) return "SEEK_END"; return "?"; } int fseek(FILE * stream, long offset, int whence) { if (stream->read_from && whence == SEEK_CUR) { if (_argv_0 && strcmp(_argv_0, "ld.so")) { if (__libc_debug) { fprintf(stderr, "%s: fseek(%s, %ld, %s)\n", _argv_0, stream->_name, offset, _whence_str(whence)); fprintf(stderr, "\033[33;3mWARNING\033[0m: seeking when offset is currently %d\n", stream->read_from); fprintf(stderr, "\033[33;3mWARNING\033[0m: this may not be reflected in kernel\n"); } } offset = offset + stream->read_from + stream->last_read_start; whence = SEEK_SET; } if (stream->written) { fflush(stream); } stream->offset = 0; stream->read_from = 0; stream->available = 0; stream->ungetc = -1; stream->flags = 0; int resp = syscall_seek(stream->fd,offset,whence); if (resp < 0) { errno = -resp; return -1; } return 0; } long ftell(FILE * stream) { if (_argv_0 && strcmp(_argv_0, "ld.so") && __libc_debug) { fprintf(stderr, "%s: ftell(%s)\n", _argv_0, stream->_name); } if (stream->written) { fflush(stream); } if (stream->read_from || stream->last_read_start) { return stream->last_read_start + stream->read_from; } stream->offset = 0; stream->read_from = 0; stream->available = 0; stream->ungetc = -1; stream->flags = 0; long resp = syscall_seek(stream->fd, 0, SEEK_CUR); if (resp < 0) { errno = -resp; return -1; } return resp; } int fgetpos(FILE *stream, fpos_t *pos) { long ret = ftell(stream); if (ret == -1) return -1; *pos = ret; return 0; } int fsetpos(FILE *stream, const fpos_t *pos) { return fseek(stream, *pos, SEEK_SET); } size_t fread(void *ptr, size_t size, size_t nmemb, FILE * stream) { char * tracking = (char*)ptr; for (size_t i = 0; i < nmemb; ++i) { size_t r = read_bytes(stream, tracking, size); tracking += r; if (r < size) { return i; } } return nmemb; } size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE * stream) { char * tracking = (char*)ptr; for (size_t i = 0; i < nmemb; ++i) { size_t r = write_bytes(stream, tracking, size); tracking += r; if (r < size) { return i; } } return nmemb; } int fileno(FILE * stream) { return stream->fd; } int fputs(const char *s, FILE *stream) { while (*s) { fputc(*s++, stream); } return 0; } int fputc(int c, FILE *stream) { char data[] = {c}; write_bytes(stream, data, 1); return c; } int putc(int c, FILE *stream) __attribute__((weak, alias("fputc"))); int fgetc(FILE * stream) { char buf[1]; int r; r = fread(buf, 1, 1, stream); if (r < 0) { stream->flags |= STDIO_EOF; return EOF; } else if (r == 0) { stream->flags |= STDIO_EOF; return EOF; } return (unsigned char)buf[0]; } int getc(FILE * stream) __attribute__((weak, alias("fgetc"))); int getchar(void) { return fgetc(stdin); } char *fgets(char *s, int size, FILE *stream) { if (size == 0) return NULL; if (size == 1) return *s = '\0', s; int c; char * out = s; while ((c = fgetc(stream)) >= 0) { *s++ = c; size--; *s = '\0'; if (size == 1 || c == '\n') { return out; } } if (c == EOF) { stream->flags |= STDIO_EOF; if (out == s) { return NULL; } else { *s = '\0'; return out; } } return NULL; } int putchar(int c) { return fputc(c, stdout); } void rewind(FILE *stream) { fseek(stream, 0, SEEK_SET); } void setbuf(FILE * stream, char * buf) { // ... } int feof(FILE * stream) { return !!(stream->flags & STDIO_EOF); } void clearerr(FILE * stream) { stream->flags = ~(STDIO_ERROR | STDIO_EOF); } int ferror(FILE * stream) { return !!(stream->flags & STDIO_ERROR); } ssize_t getdelim(char **restrict lineptr, size_t *restrict n, int delimiter, FILE *restrict stream) { if (!lineptr || !n) { errno = EINVAL; /* XXX Are we supposed to set the stream to failed for this case? Seems dubious. */ return -1; } size_t c = 0; while (1) { int i = fgetc(stream); if (*n <= c + 1) { size_t nn = *n < 120 ? 120 : (*n * 2); /* TODO: We don't define SSIZE_MAX? Anyway, ssize_t is typedefed to long on all both of our supported * platforms, so use LONG_MAX here as a replacement. size_t should be able to hold SSIZE_MAX * 2, so * the above calculation should not overflow and we can check here if *n has gotten too big. */ if (nn > LONG_MAX) nn = LONG_MAX; if (nn <= c + 1) { /* ... and this check should only trip if we've maxed up *n to SSIZE_MAX * and it's still not big enought to fit our data. */ errno = EOVERFLOW; return -1; } char * nlineptr = realloc(*lineptr, nn); if (!nlineptr) return -1; /* malloc failure */ *n = nn; *lineptr = nlineptr; } if (i == EOF) { (*lineptr)[c] = '\0'; if (c) return c; return -1; } (*lineptr)[c++] = i; if (i == delimiter) break; } (*lineptr)[c] = '\0'; return c; } ssize_t getline(char **restrict lineptr, size_t *restrict n, FILE *restrict stream) { return getdelim(lineptr, n, '\n', stream); } ================================================ FILE: libc/stdio/tmpfile.c ================================================ #include #include FILE * tmpfile(void) { static int tmpfile_num = 1; char tmp[100]; sprintf(tmp, "/tmp/tmp%d.%d", getpid(), tmpfile_num++); FILE * out = fopen(tmp, "w+b"); unlink(tmp); return out; } ================================================ FILE: libc/stdio/tmpnam.c ================================================ #include #include static char _internal[L_tmpnam]; char * tmpnam(char * s) { static int tmp_id = 1; if (!s) { s = _internal; } sprintf(s, "/tmp/tmp%d.%d", getpid(), tmp_id++); return s; } ================================================ FILE: libc/stdlib/abort.c ================================================ #include #include void abort(void) { syscall_exit(-1); __builtin_unreachable(); } ================================================ FILE: libc/stdlib/atexit.c ================================================ #include static void (*_atexit_handlers[32])(void) = {NULL}; static int _atexit_count = 0; void _handle_atexit(void) { if (!_atexit_count) return; do { _atexit_count--; _atexit_handlers[_atexit_count](); } while (_atexit_count); } int atexit(void (*h)(void)) { if (_atexit_count == ATEXIT_MAX) return 1; _atexit_handlers[_atexit_count++] = h; return 0; } ================================================ FILE: libc/stdlib/atof.c ================================================ /* Really bad atof */ #include double atof(const char * nptr) { return strtod(nptr, NULL); } ================================================ FILE: libc/stdlib/bsearch.c ================================================ #include void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { /* Stupid naive implementation */ const char * b = base; size_t i = 0; while (i < nmemb) { const void * a = b; if (!compar(key,a)) return (void *)a; i++; b += size; } return NULL; } ================================================ FILE: libc/stdlib/div.c ================================================ #include div_t div(int numerator, int denominator) { div_t out; out.quot = numerator / denominator; out.rem = numerator % denominator; if (numerator >= 0 && out.rem < 0) { out.quot++; out.rem -= denominator; } else if (numerator < 0 && out.rem > 0) { out.quot--; out.rem += denominator; } return out; } ldiv_t ldiv(long numerator, long denominator) { ldiv_t out; out.quot = numerator / denominator; out.rem = numerator % denominator; if (numerator >= 0 && out.rem < 0) { out.quot++; out.rem -= denominator; } else if (numerator < 0 && out.rem > 0) { out.quot--; out.rem += denominator; } return out; } ================================================ FILE: libc/stdlib/getenv.c ================================================ #include #include extern char ** environ; char * getenv(const char *name) { char ** e = environ; size_t len = strlen(name); while (*e) { char * t = *e; if (strstr(t, name) == *e) { if (t[len] == '=') { return &t[len+1]; } } e++; } return NULL; } ================================================ FILE: libc/stdlib/labs.c ================================================ long int labs(long int j) { return (j < 0) ? -j : j; } ================================================ FILE: libc/stdlib/malloc.c ================================================ /* vim: tabstop=4 shiftwidth=4 noexpandtab * * klange's Slab Allocator * * Implemented for CS241, Fall 2010, machine problem 7 * at the University of Illinois, Urbana-Champaign. * * Overall competition winner for speed. * Well ranked in memory usage. * * Copyright (c) 2010-2018 K. Lange. All rights reserved. * * Developed by: K. Lange * Dave Majnemer * Assocation for Computing Machinery * University of Illinois, Urbana-Champaign * http://acm.uiuc.edu * * 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 Association for Computing Machinery, the * University of Illinois, 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. * * ########## * # README # * ########## * * About the slab allocator * """""""""""""""""""""""" * * This is a simple implementation of a "slab" allocator. It works by operating * on "bins" of items of predefined sizes and a set of pseudo-bins of any size. * When a new allocation request is made, the allocator determines if it will * fit in an existing bin. If there are no bins of the correct size for a given * allocation request, the allocator will make a bin and add it to a(n empty) * list of available bins of that size. In this implementation, we use sizes * from 4 bytes (32 bit) or 8 bytes (64-bit) to 2KB for bins, fitting a 4K page * size. The implementation allows the number of pages in a single bin to be * increased, as well as allowing for changing the size of page (though this * should, for the most part, remain 4KB under any modern system). * * Special thanks * """""""""""""" * * I would like to thank Dave Majnemer, who I have credited above as a * contributor, for his assistance. Without Dave, klmalloc would be a mash * up of bits of forward movement in no discernible pattern. Dave helped * me ensure that I could build a proper slab allocator and has consantly * derided me for not fixing the bugs and to-do items listed in the last * section of this readme. * * GCC Function Attributes * """"""""""""""""""""""" * * A couple of GCC function attributes, designated by the __attribute__ * directive, are used in this code to streamline optimization. * I've chosen to include a brief overview of the particular attributes * I am making use of: * * - malloc: * Tells gcc that a given function is a memory allocator * and that non-NULL values it returns should never be * associated with other chunks of memory. We use this for * alloc, realloc and calloc, as is requested in the gcc * documentation for the attribute. * * - always_inline: * Tells gcc to always inline the given code, regardless of the * optmization level. Small functions that would be noticeably * slower with the overhead of paramter handling are given * this attribute. * * - pure: * Tells gcc that a function only uses inputs and its output. * * Things to work on * """"""""""""""""" * * TODO: Try to be more consistent on comment widths... * FIXME: Make thread safe! Not necessary for competition, but would be nice. * FIXME: Splitting/coalescing is broken. Fix this ASAP! * **/ /* Includes {{{ */ #include #include #include #include #include #include /* }}} */ /* Definitions {{{ */ #define sbrk syscall_sbrk /* * Defines for often-used integral values * related to our binning and paging strategy. */ #if defined(__x86_64__) || defined(__aarch64__) #define NUM_BINS 10U /* Number of bins, total, under 64-bit. */ #define SMALLEST_BIN_LOG 3U /* Logarithm base two of the smallest bin: log_2(sizeof(int32)). */ #else #define NUM_BINS 11U /* Number of bins, total, under 32-bit. */ #define SMALLEST_BIN_LOG 2U /* Logarithm base two of the smallest bin: log_2(sizeof(int32)). */ #endif #define BIG_BIN (NUM_BINS - 1) /* Index for the big bin, (NUM_BINS - 1) */ #define SMALLEST_BIN (1UL << SMALLEST_BIN_LOG) /* Size of the smallest bin. */ #define PAGE_SIZE 0x1000 /* Size of a page (in bytes), should be 4KB */ #define PAGE_MASK (PAGE_SIZE - 1) /* Block mask, size of a page * number of pages - 1. */ #define SKIP_P INT32_MAX /* INT32_MAX is half of UINT32_MAX; this gives us a 50% marker for skip lists. */ #define SKIP_MAX_LEVEL 6 /* We have a maximum of 6 levels in our skip lists. */ #define BIN_MAGIC 0xDEFAD00D /* }}} */ /* * Internal functions. */ static void * __attribute__ ((malloc)) klmalloc(uintptr_t size); static void * __attribute__ ((malloc)) klrealloc(void * ptr, uintptr_t size); static void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size); static void * __attribute__ ((malloc)) klvalloc(uintptr_t size); static void klfree(void * ptr); static int volatile mem_lock = 0; static const char * _lock_holder; #ifdef assert #undef assert #define assert(statement) ((statement) ? (void)0 : _malloc_assert(__FILE__, __LINE__, __FUNCTION__, #statement)) #endif #define WRITE(x) syscall_write(2, (char*)x, sizeof(x)) #define WRITEV(x) syscall_write(2, (char*)x, strlen(x)) static void _malloc_assert(const char * file, int line, const char * func, const char *x) { WRITEV(func); WRITE(" in "); WRITEV(file); WRITE(" failed assertion: "); WRITEV(x); WRITE("\n"); exit(1); } extern int __libc_is_multicore; static inline void _yield(void) { if (!__libc_is_multicore) syscall_yield(); } static void spin_lock(int volatile * lock, const char * caller) { while(__sync_lock_test_and_set(lock, 0x01)) { _yield(); } _lock_holder = caller; } static void spin_unlock(int volatile * lock) { __sync_lock_release(lock); } void * __attribute__ ((malloc)) malloc(uintptr_t size) { spin_lock(&mem_lock, __FUNCTION__); void * ret = klmalloc(size); spin_unlock(&mem_lock); return ret; } void * __attribute__ ((malloc)) realloc(void * ptr, uintptr_t size) { spin_lock(&mem_lock, __FUNCTION__); void * ret = klrealloc(ptr, size); spin_unlock(&mem_lock); return ret; } void * __attribute__ ((malloc)) calloc(uintptr_t nmemb, uintptr_t size) { spin_lock(&mem_lock, __FUNCTION__); void * ret = klcalloc(nmemb, size); spin_unlock(&mem_lock); return ret; } void * __attribute__ ((malloc)) valloc(uintptr_t size) { spin_lock(&mem_lock, __FUNCTION__); void * ret = klvalloc(size); spin_unlock(&mem_lock); return ret; } void free(void * ptr) { spin_lock(&mem_lock, __FUNCTION__); klfree(ptr); spin_unlock(&mem_lock); } /* Bin management {{{ */ /* * Adjust bin size in bin_size call to proper bounds. */ static inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_adjust_bin(uintptr_t bin) { if (bin <= (uintptr_t)SMALLEST_BIN_LOG) { return 0; } bin -= SMALLEST_BIN_LOG + 1; if (bin > (uintptr_t)BIG_BIN) { return BIG_BIN; } return bin; } /* * Given a size value, find the correct bin * to place the requested allocation in. */ static inline uintptr_t __attribute__ ((always_inline, pure)) klmalloc_bin_size(uintptr_t size) { uintptr_t bin = sizeof(size) * CHAR_BIT - __builtin_clzl(size); bin += !!(size & (size - 1)); return klmalloc_adjust_bin(bin); } /* * Bin header - One page of memory. * Appears at the front of a bin to point to the * previous bin (or NULL if the first), the next bin * (or NULL if the last) and the head of the bin, which * is a stack of cells of data. */ typedef struct _klmalloc_bin_header { struct _klmalloc_bin_header * next; /* Pointer to the next node. */ void * head; /* Head of this bin. */ uintptr_t size; /* Size of this bin, if big; otherwise bin index. */ uint32_t bin_magic; } klmalloc_bin_header; /* * A big bin header is basically the same as a regular bin header * only with a pointer to the previous (physically) instead of * a "next" and with a list of forward headers. */ typedef struct _klmalloc_big_bin_header { struct _klmalloc_big_bin_header * next; void * head; uintptr_t size; uint32_t bin_magic; } klmalloc_big_bin_header; /* * List of pages in a bin. */ typedef struct _klmalloc_bin_header_head { klmalloc_bin_header * first; } klmalloc_bin_header_head; /* * Array of available bins. */ static klmalloc_bin_header_head klmalloc_bin_head[NUM_BINS - 1]; /* Small bins */ /* }}} Bin management */ /* Doubly-Linked List {{{ */ /* * Remove an entry from a page list. * Decouples the element from its * position in the list by linking * its neighbors to eachother. */ static inline void __attribute__ ((always_inline)) klmalloc_list_decouple(klmalloc_bin_header_head *head, klmalloc_bin_header *node) { klmalloc_bin_header *next = node->next; head->first = next; node->next = NULL; } /* * Insert an entry into a page list. * The new entry is placed at the front * of the list and the existing border * elements are updated to point back * to it (our list is doubly linked). */ static inline void __attribute__ ((always_inline)) klmalloc_list_insert(klmalloc_bin_header_head *head, klmalloc_bin_header *node) { node->next = head->first; head->first = node; } /* * Get the head of a page list. * Because redundant function calls * are really great, and just in case * we change the list implementation. */ static inline klmalloc_bin_header * __attribute__ ((always_inline)) klmalloc_list_head(klmalloc_bin_header_head *head) { return head->first; } /* }}} Lists */ /* Stack {{{ */ /* * Pop an item from a block. * Free space is stored as a stack, * so we get a free space for a bin * by popping a free node from the * top of the stack. */ static void * klmalloc_stack_pop(klmalloc_bin_header *header) { assert(header); assert(header->head != NULL); assert((uintptr_t)header->head > (uintptr_t)header); if (header->size > NUM_BINS) { assert((uintptr_t)header->head < (uintptr_t)header + header->size); } else { assert((uintptr_t)header->head < (uintptr_t)header + PAGE_SIZE); assert((uintptr_t)header->head > (uintptr_t)header + sizeof(klmalloc_bin_header) - 1); } /* * Remove the current head and point * the head to where the old head pointed. */ void *item = header->head; uintptr_t **head = header->head; uintptr_t *next = *head; header->head = next; return item; } /* * Push an item into a block. * When we free memory, we need * to add the freed cell back * into the stack of free spaces * for the block. */ static void klmalloc_stack_push(klmalloc_bin_header *header, void *ptr) { assert(ptr != NULL); assert((uintptr_t)ptr > (uintptr_t)header); if (header->size > NUM_BINS) { assert((uintptr_t)ptr < (uintptr_t)header + header->size); } else { assert((uintptr_t)ptr < (uintptr_t)header + PAGE_SIZE); } uintptr_t **item = (uintptr_t **)ptr; *item = (uintptr_t *)header->head; header->head = item; } /* * Is this cell stack empty? * If the head of the stack points * to NULL, we have exhausted the * stack, so there is no more free * space available in the block. */ static inline int __attribute__ ((always_inline)) klmalloc_stack_empty(klmalloc_bin_header *header) { return header->head == NULL; } /* }}} Stack */ /* malloc() {{{ */ static void * __attribute__ ((malloc)) klmalloc(uintptr_t size) { /* * C standard implementation: * If size is zero, we can choose do a number of things. * This implementation will return a NULL pointer. */ if (__builtin_expect(size == 0, 0)) return NULL; /* * Find the appropriate bin for the requested * allocation and start looking through that list. */ unsigned int bucket_id = klmalloc_bin_size(size); if (bucket_id < BIG_BIN) { /* * Small bins. */ klmalloc_bin_header * bin_header = klmalloc_list_head(&klmalloc_bin_head[bucket_id]); if (!bin_header) { /* * Grow the heap for the new bin. */ bin_header = (klmalloc_bin_header*)sbrk(PAGE_SIZE); bin_header->bin_magic = BIN_MAGIC; assert((uintptr_t)bin_header % PAGE_SIZE == 0); /* * Set the head of the stack. */ bin_header->head = (void*)((uintptr_t)bin_header + sizeof(klmalloc_bin_header)); /* * Insert the new bin at the front of * the list of bins for this size. */ klmalloc_list_insert(&klmalloc_bin_head[bucket_id], bin_header); /* * Initialize the stack inside the bin. * The stack is initially full, with each * entry pointing to the next until the end * which points to NULL. */ uintptr_t adj = SMALLEST_BIN_LOG + bucket_id; uintptr_t i, available = ((PAGE_SIZE - sizeof(klmalloc_bin_header)) >> adj) - 1; uintptr_t **base = bin_header->head; for (i = 0; i < available; ++i) { /* * Our available memory is made into a stack, with each * piece of memory turned into a pointer to the next * available piece. When we want to get a new piece * of memory from this block, we just pop off a free * spot and give its address. */ base[i << bucket_id] = (uintptr_t *)&base[(i + 1) << bucket_id]; } base[available << bucket_id] = NULL; bin_header->size = bucket_id; } uintptr_t ** item = klmalloc_stack_pop(bin_header); if (klmalloc_stack_empty(bin_header)) { klmalloc_list_decouple(&(klmalloc_bin_head[bucket_id]),bin_header); } return item; } else { /* * Round requested size to a set of pages, plus the header size. */ uintptr_t pages = (size + sizeof(klmalloc_big_bin_header)) / PAGE_SIZE + 1; klmalloc_big_bin_header * bin_header = (klmalloc_big_bin_header*)sbrk(PAGE_SIZE * pages); bin_header->bin_magic = BIN_MAGIC; assert((uintptr_t)bin_header % PAGE_SIZE == 0); /* * Give the header the remaining space. */ bin_header->size = pages * PAGE_SIZE - sizeof(klmalloc_big_bin_header); assert((bin_header->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); /* * Return the head of the block. */ bin_header->head = NULL; return (void*)((uintptr_t)bin_header + sizeof(klmalloc_big_bin_header)); } } /* }}} */ /* free() {{{ */ static void klfree(void *ptr) { /* * C standard implementation: Do nothing when NULL is passed to free. */ if (__builtin_expect(ptr == NULL, 0)) { return; } /* * Woah, woah, hold on, was this a page-aligned block? */ if ((uintptr_t)ptr % PAGE_SIZE == 0) { /* * Well howdy-do, it was. */ ptr = (void *)((uintptr_t)ptr - 1); } /* * Get our pointer to the head of this block by * page aligning it. */ klmalloc_bin_header * header = (klmalloc_bin_header *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK); assert((uintptr_t)header % PAGE_SIZE == 0); if (header->bin_magic != BIN_MAGIC) return; /* * For small bins, the bin number is stored in the size * field of the header. For large bins, the actual size * available in the bin is stored in this field. It's * easy to tell which is which, though. */ uintptr_t bucket_id = header->size; if (bucket_id > (uintptr_t)NUM_BINS) { bucket_id = BIG_BIN; klmalloc_big_bin_header *bheader = (klmalloc_big_bin_header*)header; assert(bheader); assert(bheader->head == NULL); assert((bheader->size + sizeof(klmalloc_big_bin_header)) % PAGE_SIZE == 0); char * args[] = {(char*)header, (char*)(bheader->size + sizeof(klmalloc_big_bin_header))}; syscall_sysfunc(43, args); } else { /* * If the stack is empty, we are freeing * a block from a previously full bin. * Return it to the busy bins list. */ if (klmalloc_stack_empty(header)) { klmalloc_list_insert(&klmalloc_bin_head[bucket_id], header); } /* * Push new space back into the stack. */ klmalloc_stack_push(header, ptr); } } /* }}} */ /* valloc() {{{ */ static void * __attribute__ ((malloc)) klvalloc(uintptr_t size) { /* * Allocate a page-aligned block. * XXX: THIS IS HORRIBLY, HORRIBLY WASTEFUL!! ONLY USE THIS * IF YOU KNOW WHAT YOU ARE DOING! */ uintptr_t true_size = size + PAGE_SIZE - sizeof(klmalloc_big_bin_header); /* Here we go... */ void * result = klmalloc(true_size); void * out = (void *)((uintptr_t)result + (PAGE_SIZE - sizeof(klmalloc_big_bin_header))); assert((uintptr_t)out % PAGE_SIZE == 0); return out; } /* }}} */ /* realloc() {{{ */ static void * __attribute__ ((malloc)) klrealloc(void *ptr, uintptr_t size) { /* * C standard implementation: When NULL is passed to realloc, * simply malloc the requested size and return a pointer to that. */ if (__builtin_expect(ptr == NULL, 0)) return klmalloc(size); /* * C standard implementation: For a size of zero, free the * pointer and return NULL, allocating no new memory. */ if (__builtin_expect(size == 0, 0)) { free(ptr); return NULL; } /* * Find the bin for the given pointer * by aligning it to a page. */ klmalloc_bin_header * header_old = (void *)((uintptr_t)ptr & (uintptr_t)~PAGE_MASK); if (header_old->bin_magic != BIN_MAGIC) { assert(0 && "Bad magic on realloc."); return NULL; } uintptr_t old_size = header_old->size; if (old_size < (uintptr_t)BIG_BIN) { /* * If we are copying from a small bin, * we need to get the size of the bin * from its id. */ old_size = (1UL << (SMALLEST_BIN_LOG + old_size)); } if (old_size == size) return ptr; /* * Reallocate more memory. */ void * newptr = klmalloc(size); if (__builtin_expect(newptr != NULL, 1)) { /* * Copy the old value into the new value. * Be sure to only copy as much as was in * the old block. */ memcpy(newptr, ptr, (old_size < size) ? old_size : size); klfree(ptr); return newptr; } /* * We failed to allocate more memory, * which means we're probably out. * * Bail and return NULL. */ return NULL; } /* }}} */ /* calloc() {{{ */ static void * __attribute__ ((malloc)) klcalloc(uintptr_t nmemb, uintptr_t size) { /* * Allocate memory and zero it before returning * a pointer to the newly allocated memory. * * Implemented by way of a simple malloc followed * by a memset to 0x00 across the length of the * requested memory chunk. */ void *ptr = klmalloc(nmemb * size); if (ptr) memset(ptr,0x00,nmemb * size); return ptr; } /* }}} */ ================================================ FILE: libc/stdlib/mbstowcs.c ================================================ #include #include #include #include #include static int to_eight(uint32_t codepoint, char * out) { memset(out, 0x00, 7); if (codepoint < 0x0080) { out[0] = (char)codepoint; } else if (codepoint < 0x0800) { out[0] = 0xC0 | (codepoint >> 6); out[1] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x10000) { out[0] = 0xE0 | (codepoint >> 12); out[1] = 0x80 | ((codepoint >> 6) & 0x3F); out[2] = 0x80 | (codepoint & 0x3F); } else if (codepoint < 0x200000) { out[0] = 0xF0 | (codepoint >> 18); out[1] = 0x80 | ((codepoint >> 12) & 0x3F); out[2] = 0x80 | ((codepoint >> 6) & 0x3F); out[3] = 0x80 | ((codepoint) & 0x3F); } else if (codepoint < 0x4000000) { out[0] = 0xF8 | (codepoint >> 24); out[1] = 0x80 | (codepoint >> 18); out[2] = 0x80 | ((codepoint >> 12) & 0x3F); out[3] = 0x80 | ((codepoint >> 6) & 0x3F); out[4] = 0x80 | ((codepoint) & 0x3F); } else { out[0] = 0xF8 | (codepoint >> 30); out[1] = 0x80 | ((codepoint >> 24) & 0x3F); out[2] = 0x80 | ((codepoint >> 18) & 0x3F); out[3] = 0x80 | ((codepoint >> 12) & 0x3F); out[4] = 0x80 | ((codepoint >> 6) & 0x3F); out[5] = 0x80 | ((codepoint) & 0x3F); } return strlen(out); } size_t mbstowcs(wchar_t *dest, const char *src, size_t n) { size_t count = 0; uint32_t state = 0; uint32_t codepoint = 0; while ((!dest || count < n) && *src) { if (!decode(&state, &codepoint, *(unsigned char *)src)) { if (dest) { dest[count] = codepoint; } count++; codepoint = 0; } else if (state == UTF8_REJECT) { return (size_t)-1; } src++; } if (dest && !*src && count < n) { dest[count] = L'\0'; } return count; } size_t wcstombs(char * dest, const wchar_t *src, size_t n) { size_t count = 0; while ((!dest || count < n) && *src) { char tmp[7]; int size = to_eight(*src, tmp); if (count + size > n) return n; memcpy(&dest[count], tmp, size); count += size; src++; } if (dest && !*src && count < n) { dest[count] = '\0'; } return count; } ================================================ FILE: libc/stdlib/mktemp.c ================================================ #include #include #include #include #include #include char * mktemp(char * template) { if (strstr(template + strlen(template)-6, "XXXXXX") != template + strlen(template) - 6) { errno = EINVAL; return NULL; } static int _i = 0; char tmp[7] = {0}; sprintf(tmp,"%04d%02d", getpid(), _i++); memcpy(template + strlen(template) - 6, tmp, 6); return template; } int mkstemp(char * template) { mktemp(template); return open(template, O_RDWR | O_CREAT, 0600); } ================================================ FILE: libc/stdlib/putenv.c ================================================ #include #include #include #include extern char ** environ; extern int _environ_size; static int why_no_strnstr(char * a, char * b, int n) { for (int i = 0; (i < n) && (a[i]) && (b[i]); ++i) { if (a[i] != b[i]) { return 1; } } return 0; } int unsetenv(const char * str) { int last_index = -1; int found_index = -1; int len = strlen(str); if (!len) { errno = EINVAL; return -1; } char *c = strchrnul(str,'='); if (*c) { errno = EINVAL; return -1; } for (int i = 0; environ[i]; ++i) { if (found_index == -1 && (strstr(environ[i], str) == environ[i] && environ[i][len] == '=')) { found_index = i; } last_index = i; } if (found_index == -1) { /* not found = success */ return 0; } if (last_index == found_index) { /* Was last element */ environ[last_index] = NULL; return 0; } /* Was not last element, swap ordering */ environ[found_index] = environ[last_index]; environ[last_index] = NULL; return 0; } int putenv(char * string) { char * c = strchrnul(string, '='); if (c == string) { errno = EINVAL; return -1; } if (!*c) { return unsetenv(string); } int s = c - string; int i; for (i = 0; i < (_environ_size - 1) && environ[i]; ++i) { if (!why_no_strnstr(string, environ[i], s) && environ[i][s] == '=') { environ[i] = string; return 0; } } /* Not found */ if (i == _environ_size - 1) { int _new_environ_size = _environ_size * 2; char ** new_environ = malloc(sizeof(char*) * _new_environ_size); int j = 0; while (j < _new_environ_size && environ[j]) { new_environ[j] = environ[j]; j++; } while (j < _new_environ_size) { new_environ[j] = NULL; j++; } _environ_size = _new_environ_size; environ = new_environ; } environ[i] = string; return 0; } ================================================ FILE: libc/stdlib/qsort.c ================================================ #include #include #include #include #include #include extern char * _argv_0; extern int __libc_debug; struct SortableArray { void * data; size_t size; void * arg; int (*func)(const void *, const void *, void *); }; static ssize_t partition(struct SortableArray * array, ssize_t lo, ssize_t hi) { char pivot[array->size]; memcpy(pivot, (char *)array->data + array->size * hi, array->size); ssize_t i = lo - 1; for (ssize_t j = lo; j <= hi; ++j) { uint8_t * obj_j = (uint8_t *)array->data + array->size * j; if (array->func(obj_j, pivot, array->arg) <= 0) { i++; if (j != i) { uint8_t * obj_i = (uint8_t *)array->data + array->size * i; for (size_t x = 0; x < array->size; ++x) { uint8_t tmp = obj_i[x]; obj_i[x] = obj_j[x]; obj_j[x] = tmp; } } } } return i; } static void quicksort(struct SortableArray * array, ssize_t lo, ssize_t hi) { if (lo >= 0 && hi >= 0) { if (lo < hi) { ssize_t pivot = partition(array, lo, hi); quicksort(array, lo, pivot - 1); quicksort(array, pivot + 1, hi); } } } static int sort_caller(const void * a, const void * b, void * arg) { int (*func)(const void *, const void *) = arg; return func(a,b); } void qsort_r(void * base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void * arg) { if (nmemb < 2) return; if (!size) return; struct SortableArray array = {base,size,arg,compar}; quicksort(&array, 0, nmemb - 1); } void qsort(void * base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { qsort_r(base,nmemb,size,sort_caller,compar); } ================================================ FILE: libc/stdlib/rand.c ================================================ #include #include #include static uint32_t x = 123456789; static uint32_t y = 362436069; static uint32_t z = 521288629; static uint32_t w = 88675123; int rand(void) { uint32_t t; t = x ^ (x << 11); x = y; y = z; z = w; w = w ^ (w >> 19) ^ t ^ (t >> 8); return (w & RAND_MAX); } void srand(unsigned int seed) { x = 123456789 ^ (seed << 16) ^ (seed >> 16); y = 362436069; z = 521288629; w = 88675123; } ================================================ FILE: libc/stdlib/realpath.c ================================================ #include #include #include #include #include #include #ifndef __toaru__ #undef realpath #define realpath _realpath_toaru #endif #define SYMLINK_MAX 5 static void _append_dir(char *out, char *element) { strcat(out,"/"); strcat(out,element); } static void _remove_last(char * out) { char * last = strrchr(out,'/'); if (last) { *last = '\0'; } } /** * This is accurate to how we handle paths in ToaruOS. * It's not correct for real symbolic link handling, * so it needs some work for that. */ char *realpath(const char *path, char *resolved_path) { /* * Basically the same as what we do in the kernel for canonicalize_path * but slightly more complicated because of the requirement to check * symlinks... this is going to get interesting. */ if (!path) { errno = -EINVAL; return NULL; } if (!resolved_path) { resolved_path = malloc(PATH_MAX+1); } /* If we're lucky, we can do this with no allocations, so let's start here... */ char working_path[PATH_MAX+1]; memcpy(working_path, path, strlen(path)+1); *resolved_path = 0; if (working_path[0] != '/') { /* Begin by retreiving the current working directory */ char cwd[PATH_MAX+1]; if (!getcwd(cwd, PATH_MAX)) { /* Not actually sure if this is the right choice for this, but whatever. */ errno = -ENOTDIR; return NULL; } char *save; char *tok = strtok_r(cwd,"/",&save); do { _append_dir(resolved_path, tok); } while ((tok = strtok_r(NULL,"/",&save))); } char *save; char *tok = strtok_r(working_path,"/",&save); if (tok) { do { if (!strcmp(tok,".")) continue; if (!strcmp(tok,"..")) { _remove_last(resolved_path); continue; } else { _append_dir(resolved_path, tok); } } while ((tok = strtok_r(NULL,"/",&save))); } if (resolved_path[0] == '\0') { strcat(resolved_path,"/"); } return resolved_path; } #ifndef __toaru__ int main(int argc, char * argv[]) { char tmp[PATH_MAX+1]; if (!realpath(argv[1], tmp)) { fprintf(stderr, "invalid path, errno=%d\n", errno); return 1; } fprintf(stderr, "%s=%s\n", argv[1], tmp); return 0; } #endif ================================================ FILE: libc/stdlib/setenv.c ================================================ #include #include #include int setenv(const char *name, const char *value, int overwrite) { if (!overwrite && getenv(name)) return 0; if (!name || !*name) { errno = EINVAL; return -1; } char * c = strchrnul(name, '='); if (*c) { errno = EINVAL; return -1; } char * tmp = malloc(strlen(name) + strlen(value) + 2); *tmp = '\0'; strcat(tmp, name); strcat(tmp, "="); strcat(tmp, value); return putenv(tmp); } ================================================ FILE: libc/stdlib/strtod.c ================================================ #include #include #include double strtod(const char *nptr, char **endptr) { int sign = 1; if (*nptr == '-') { sign = -1; nptr++; } long long decimal_part = 0; while (*nptr && *nptr != '.') { if (*nptr < '0' || *nptr > '9') { break; } decimal_part *= 10LL; decimal_part += (long long)(*nptr - '0'); nptr++; } double sub_part = 0; double multiplier = 0.1; if (*nptr == '.') { nptr++; while (*nptr) { if (*nptr < '0' || *nptr > '9') { break; } sub_part += multiplier * (*nptr - '0'); multiplier *= 0.1; nptr++; } } double expn = (double)sign; if (*nptr == 'e' || *nptr == 'E') { nptr++; int exponent_sign = 1; if (*nptr == '+') { nptr++; } else if (*nptr == '-') { exponent_sign = -1; nptr++; } int exponent = 0; while (*nptr) { if (*nptr < '0' || *nptr > '9') { break; } exponent *= 10; exponent += (*nptr - '0'); nptr++; } expn = pow(10.0,(double)(exponent * exponent_sign)); } if (endptr) { *endptr = (char *)nptr; } double result = ((double)decimal_part + sub_part) * expn; return result; } float strtof(const char *nptr, char **endptr) { return strtod(nptr,endptr); } ================================================ FILE: libc/stdlib/strtoul.c ================================================ #include #include #include #include static int is_valid(int base, char c) { if (c < '0') return 0; if (base <= 10) { return c < ('0' + base); } if (c >= 'a' && c < 'a' + (base - 10)) return 1; if (c >= 'A' && c < 'A' + (base - 10)) return 1; if (c >= '0' && c <= '9') return 1; return 0; } static int convert_digit(char c) { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'z') { return c - 'a' + 0xa; } if (c >= 'A' && c <= 'Z') { return c - 'A' + 0xa; } return 0; } #define strtox(max, type) \ if (base < 0 || base == 1 || base > 36) { \ errno = EINVAL; \ return max; \ } \ while (*nptr && isspace(*nptr)) nptr++; \ int sign = 1; \ if (*nptr == '-') { \ sign = -1; \ nptr++; \ } else if (*nptr == '+') { \ nptr++; \ } \ if (base == 16) { \ if (*nptr == '0') { \ nptr++; \ if (*nptr == 'x') { \ nptr++; \ } \ } \ } \ if (base == 0) { \ if (*nptr == '0') { \ base = 8; \ nptr++; \ if (*nptr == 'x') { \ base = 16; \ nptr++; \ } \ } else { \ base = 10; \ } \ } \ type val = 0; \ while (is_valid(base, *nptr)) { \ val *= base; \ val += convert_digit(*nptr); \ nptr++; \ } \ if (endptr) { \ *endptr = (char *)nptr; \ } \ if (sign == -1) { \ return -val; \ } else { \ return val; \ } unsigned long int strtoul(const char *nptr, char **endptr, int base) { strtox(ULONG_MAX, unsigned long int); } unsigned long long int strtoull(const char *nptr, char **endptr, int base) { strtox(ULLONG_MAX, unsigned long int); } long int strtol(const char *nptr, char **endptr, int base) { strtox(LONG_MAX, unsigned long int); } long long int strtoll(const char *nptr, char **endptr, int base) { strtox(LLONG_MAX, unsigned long long int); } ================================================ FILE: libc/stdlib/system.c ================================================ #include #include #include #include #include int system(const char * command) { char * args[] = { "/bin/sh", "-c", (char *)command, NULL, }; pid_t pid = fork(); if (!pid) { execvp(args[0], args); exit(1); __builtin_unreachable(); /* With -ffreestanding, gcc doesn't realize exit() doesn't return. */ } else { int status; waitpid(pid, &status, 0); return WEXITSTATUS(status); } } ================================================ FILE: libc/string/memmove.c ================================================ #include #include #include void * memmove(void * dest, const void * src, size_t n) { char * d = dest; const char * s = src; if (d==s) { return d; } if (s+n <= d || d+n <= s) { return memcpy(d, s, n); } if (d= sizeof(size_t); n -= sizeof(size_t), d += sizeof(size_t), s += sizeof(size_t)) { *(size_t *)d = *(size_t *)s; } } for (; n; n--) { *d++ = *s++; } } else { if ((uintptr_t)s % sizeof(size_t) == (uintptr_t)d % sizeof(size_t)) { while ((uintptr_t)(d+n) % sizeof(size_t)) { if (!n--) { return dest; } d[n] = s[n]; } while (n >= sizeof(size_t)) { n -= sizeof(size_t); *(size_t *)(d+n) = *(size_t *)(s+n); } } while (n) { n--; d[n] = s[n]; } } return dest; } ================================================ FILE: libc/string/str.c ================================================ #include #include #include #include #include #include #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define ALIGN (sizeof(size_t)) #define ONES ((size_t)-1/UCHAR_MAX) #define HIGHS (ONES * (UCHAR_MAX/2+1)) #define HASZERO(X) (((X)-ONES) & ~(X) & HIGHS) #define BITOP(A, B, OP) \ ((A)[(size_t)(B)/(8*sizeof *(A))] OP (size_t)1<<((size_t)(B)%(8*sizeof *(A)))) int memcmp(const void * vl, const void * vr, size_t n) { const unsigned char *l = vl; const unsigned char *r = vr; for (; n && *l == *r; n--, l++, r++); return n ? *l-*r : 0; } void * memchr(const void * src, int c, size_t n) { const unsigned char * s = src; c = (unsigned char)c; for (; ((uintptr_t)s & (ALIGN - 1)) && n && *s != c; s++, n--); if (n && *s != c) { const size_t * w; size_t k = ONES * c; for (w = (const void *)s; n >= sizeof(size_t) && !HASZERO(*w^k); w++, n -= sizeof(size_t)); for (s = (const void *)w; n && *s != c; s++, n--); } return n ? (void *)s : 0; } void * memrchr(const void * m, int c, size_t n) { const unsigned char * s = m; c = (unsigned char)c; while (n--) { if (s[n] == c) { return (void*)(s+n); } } return 0; } int strcmp(const char * l, const char * r) { for (; *l == *r && *l; l++, r++); return *(unsigned char *)l - *(unsigned char *)r; } int strcoll(const char * s1, const char * s2) { return strcmp(s1,s2); /* TODO locales */ } size_t strlen(const char * s) { const char * a = s; const size_t * w; for (; (uintptr_t)s % ALIGN; s++) { if (!*s) { return s-a; } } for (w = (const void *)s; !HASZERO(*w); w++); for (s = (const void *)w; *s; s++); return s-a; } char * strdup(const char * s) { size_t l = strlen(s); return memcpy(malloc(l+1), s, l+1); } char * stpcpy(char * restrict d, const char * restrict s) { size_t * wd; const size_t * ws; if ((uintptr_t)s % ALIGN == (uintptr_t)d % ALIGN) { for (; (uintptr_t)s % ALIGN; s++, d++) { if (!(*d = *s)) { return d; } } wd = (void *)d; ws = (const void *)s; for (; !HASZERO(*ws); *wd++ = *ws++); d = (void *)wd; s = (const void *)ws; } for (; (*d=*s); s++, d++); return d; } char * strcpy(char * restrict dest, const char * restrict src) { char * out = dest; for (; (*dest=*src); src++, dest++); return out; } size_t strspn(const char * s, const char * c) { const char * a = s; size_t byteset[32/sizeof(size_t)] = { 0 }; if (!c[0]) { return 0; } if (!c[1]) { for (; *s == *c; s++); return s-a; } for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); for (; *s && BITOP(byteset, *(unsigned char *)s, &); s++); return s-a; } char * strchrnul(const char * s, int c) { size_t * w; size_t k; c = (unsigned char)c; if (!c) { return (char *)s + strlen(s); } for (; (uintptr_t)s % ALIGN; s++) { if (!*s || *(unsigned char *)s == c) { return (char *)s; } } k = ONES * c; for (w = (void *)s; !HASZERO(*w) && !HASZERO(*w^k); w++); for (s = (void *)w; *s && *(unsigned char *)s != c; s++); return (char *)s; } char * strchr(const char * s, int c) { char *r = strchrnul(s, c); return *(unsigned char *)r == (unsigned char)c ? r : 0; } char * strrchr(const char * s, int c) { return memrchr(s, c, strlen(s) + 1); } size_t strcspn(const char * s, const char * c) { const char *a = s; if (c[0] && c[1]) { size_t byteset[32/sizeof(size_t)] = { 0 }; for (; *c && BITOP(byteset, *(unsigned char *)c, |=); c++); for (; *s && !BITOP(byteset, *(unsigned char *)s, &); s++); return s-a; } return strchrnul(s, *c)-a; } char * strpbrk(const char * s, const char * b) { s += strcspn(s, b); return *s ? (char *)s : 0; } static char *strstr_2b(const unsigned char * h, const unsigned char * n) { uint16_t nw = n[0] << 8 | n[1]; uint16_t hw = h[0] << 8 | h[1]; for (h++; *h && hw != nw; hw = hw << 8 | *++h); return *h ? (char *)h-1 : 0; } static char *strstr_3b(const unsigned char * h, const unsigned char * n) { uint32_t nw = n[0] << 24 | n[1] << 16 | n[2] << 8; uint32_t hw = h[0] << 24 | h[1] << 16 | h[2] << 8; for (h += 2; *h && hw != nw; hw = (hw|*++h) << 8); return *h ? (char *)h-2 : 0; } static char *strstr_4b(const unsigned char * h, const unsigned char * n) { uint32_t nw = n[0] << 24 | n[1] << 16 | n[2] << 8 | n[3]; uint32_t hw = h[0] << 24 | h[1] << 16 | h[2] << 8 | h[3]; for (h += 3; *h && hw != nw; hw = hw << 8 | *++h); return *h ? (char *)h-3 : 0; } static char *strstr_twoway(const unsigned char * h, const unsigned char * n) { size_t mem; size_t mem0; size_t byteset[32 / sizeof(size_t)] = { 0 }; size_t shift[256]; size_t l; /* Computing length of needle and fill shift table */ for (l = 0; n[l] && h[l]; l++) { BITOP(byteset, n[l], |=); shift[n[l]] = l+1; } if (n[l]) { return 0; /* hit the end of h */ } /* Compute maximal suffix */ size_t ip = -1; size_t jp = 0; size_t k = 1; size_t p = 1; while (jp+k n[jp+k]) { jp += k; k = 1; p = jp - ip; } else { ip = jp++; k = p = 1; } } size_t ms = ip; size_t p0 = p; /* And with the opposite comparison */ ip = -1; jp = 0; k = p = 1; while (jp+k ms+1) { ms = ip; } else { p = p0; } /* Periodic needle? */ if (memcmp(n, n+p, ms+1)) { mem0 = 0; p = MAX(ms, l-ms-1) + 1; } else { mem0 = l-p; } mem = 0; /* Initialize incremental end-of-haystack pointer */ const unsigned char * z = h; /* Search loop */ for (;;) { /* Update incremental end-of-haystack pointer */ if ((size_t)(z-h) < l) { /* Fast estimate for MIN(l,63) */ size_t grow = l | 63; const unsigned char *z2 = memchr(z, 0, grow); if (z2) { z = z2; if ((size_t)(z-h) < l) { return 0; } } else { z += grow; } } /* Check last byte first; advance by shift on mismatch */ if (BITOP(byteset, h[l-1], &)) { k = l-shift[h[l-1]]; if (k) { if (mem0 && mem && k < p) k = l-p; h += k; mem = 0; continue; } } else { h += l; mem = 0; continue; } /* Compare right half */ for (k=MAX(ms+1,mem); n[k] && n[k] == h[k]; k++); if (n[k]) { h += k-ms; mem = 0; continue; } /* Compare left half */ for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--); if (k <= mem) { return (char *)h; } h += p; mem = mem0; } } char *strstr(const char * h, const char * n) { /* Return immediately on empty needle */ if (!n[0]) { return (char *)h; } /* Use faster algorithms for short needles */ h = strchr(h, *n); if (!h || !n[1]) { return (char *)h; } if (!h[1]) return 0; if (!n[2]) return strstr_2b((void *)h, (void *)n); if (!h[2]) return 0; if (!n[3]) return strstr_3b((void *)h, (void *)n); if (!h[3]) return 0; if (!n[4]) return strstr_4b((void *)h, (void *)n); /* Two-way on large needles */ return strstr_twoway((void *)h, (void *)n); } long atol(const char * s) { int n = 0; int neg = 0; while (isspace(*s)) { s++; } switch (*s) { case '-': neg = 1; /* fallthrough */ case '+': s++; } while (isdigit(*s)) { n = 10*n - (*s++ - '0'); } /* The sign order may look incorrect here but this is correct as n is calculated * as a negative number to avoid overflow on INT_MAX. */ return neg ? n : -n; } int atoi(const char * s) { int n = 0; int neg = 0; while (isspace(*s)) { s++; } switch (*s) { case '-': neg = 1; /* fallthrough */ case '+': s++; } while (isdigit(*s)) { n = 10*n - (*s++ - '0'); } /* The sign order may look incorrect here but this is correct as n is calculated * as a negative number to avoid overflow on INT_MAX. */ return neg ? n : -n; } size_t lfind(const char * str, const char accept) { return (size_t)strchr(str, accept); } size_t rfind(const char * str, const char accept) { return (size_t)strrchr(str, accept); } char * strtok_r(char * str, const char * delim, char ** saveptr) { char * token; if (str == NULL) { str = *saveptr; } str += strspn(str, delim); if (*str == '\0') { *saveptr = str; return NULL; } token = str; str = strpbrk(token, delim); if (str == NULL) { *saveptr = (char *)lfind(token, '\0'); } else { *str = '\0'; *saveptr = str + 1; } return token; } char * strtok(char * str, const char * delim) { static char * saveptr = NULL; if (str) { saveptr = NULL; } return strtok_r(str, delim, &saveptr); } char * strcat(char *dest, const char *src) { char * end = dest; while (*end != '\0') { ++end; } while (*src) { *end = *src; end++; src++; } *end = '\0'; return dest; } char * strncat(char *dest, const char *src, size_t n) { char * end = dest; while (*end != '\0') { ++end; } size_t i = 0; while (*src && i < n) { *end = *src; end++; src++; i++; } *end = '\0'; return dest; } ================================================ FILE: libc/string/strerror.c ================================================ #include #include #include static char * _error_strings[] = { [EPERM] = "Operation not permitted", [ENOENT] = "No such file or directory", [ESRCH] = "No such process", [EINTR] = "Interrupted system call", [EIO] = "Input/Output error", [ENXIO] = "No such device or address", [E2BIG] = "Argument list too long", [ENOEXEC] = "Exec format error", [EBADF] = "Bad file descriptor", [ECHILD] = "No child processes", [EAGAIN] = "Resource temporarily unavailable", [ENOMEM] = "Cannot allocate memory", [EACCES] = "Permission denied", [EFAULT] = "Bad address", [ENOTBLK] = "Block device required", [EBUSY] = "Device or resource busy", [EEXIST] = "File exists", [EXDEV] = "Invalid cross-device link", [ENODEV] = "No such device", [ENOTDIR] = "Not a directory", [EISDIR] = "Is a directory", [EINVAL] = "Invalid argument", [ENFILE] = "Too many open files in system", [EMFILE] = "Too many open files", [ENOTTY] = "Inappropriate ioctl for device", [ETXTBSY] = "Text file busy", [EFBIG] = "File too large", [ENOSPC] = "No space left on device", [ESPIPE] = "Illegal seek", [EROFS] = "Read only file system", [EMLINK] = "Too many links", [EPIPE] = "Broken pipe", [EDOM] = "Math arg out of domain of func", [ERANGE] = "Math result not representable", [ENOMSG] = "No message of desired type", [EIDRM] = "Identifier removed", [ECHRNG] = "Channel number out of range", [EL2NSYNC] = "Level 2 not synchronized", [EL3HLT] = "Level 3 halted", [EL3RST] = "Level 3 reset", [ELNRNG] = "Link number out of range", [EUNATCH] = "Protocol driver not attached", [ENOCSI] = "No CSI structure available", [EL2HLT] = "Level 2 halted", [EDEADLK] = "Deadlock condition", [ENOLCK] = "No record locks available", [EBADE] = "Invalid exchange", [EBADR] = "Invalid request descriptor", [EXFULL] = "Exchange full", [ENOANO] = "No anode", [EBADRQC] = "Invalid request code", [EBADSLT] = "Invalid slot", [EDEADLOCK] = "File locking deadlock error", [EBFONT] = "Bad font file format", [ENOSTR] = "Device not a stream", [ENODATA] = "No data available", [ETIME] = "Timer expired", [ENOSR] = "Out of streams resources", [ENONET] = "Machine is not on the network", [ENOPKG] = "Package not installed", [EREMOTE] = "The object is remote", [ENOLINK] = "The link has been severed", [EADV] = "Advertise error", [ESRMNT] = "Srmount error", [ECOMM] = "Communication error on send", [EPROTO] = "Protocol error", [EMULTIHOP] = "Multihop attempted", [ELBIN] = "Inode is remote", [EDOTDOT] = "Cross mount point", [EBADMSG] = "Bad message", [EFTYPE] = "Inappropriate file type or format", [ENOTUNIQ] = "Name not unique", [EBADFD] = "File descriptor in bad state", [EREMCHG] = "Remote address changed", [ELIBACC] = "Can not access a needed shared library", [ELIBBAD] = "Accessing a corrupted shared library", [ELIBSCN] = ".lib section in a.out corrupted", [ELIBMAX] = "Attempting to link in too many libraries", [ELIBEXEC] = "Attempting to exec a shared library", [ENOSYS] = "Function not implemented", [ENOTEMPTY] = "Directory not empty", [ENAMETOOLONG] = "File or path name too long", [ELOOP] = "Too many symbolic links", [EOPNOTSUPP] = "Operation not supported on transport endpoint", [EPFNOSUPPORT] = "Protocol family not supported", [ECONNRESET] = "Connection reset by peer", [ENOBUFS] = "No buffer space available", [EAFNOSUPPORT] = "Address family not supported by protocol family", [EPROTOTYPE] = "Protocol wrong type for socket", [ENOTSOCK] = "Socket operation on non-socket", [ENOPROTOOPT] = "Protocol not available", [ESHUTDOWN] = "Can't send after socket shutdown", [ECONNREFUSED] = "Connection refused", [EADDRINUSE] = "Address already in use", [ECONNABORTED] = "Connection aborted", [ENETUNREACH] = "Network is unreachable", [ENETDOWN] = "Network interface is not configured", [ETIMEDOUT] = "Connection timed out", [EHOSTDOWN] = "Host is down", [EHOSTUNREACH] = "No route to host", [EINPROGRESS] = "Connection already in progress", [EALREADY] = "Socket already connected", [EDESTADDRREQ] = "Destination address required", [EMSGSIZE] = "Message too long", [EPROTONOSUPPORT] = "Unknown protocol", [ESOCKTNOSUPPORT] = "Socket type not supported", [EADDRNOTAVAIL] = "Address not available", [EISCONN] = "Socket is already connected", [ENOTCONN] = "Socket is not connected", [ENOTSUP] = "Operation not supported", [EOVERFLOW] = "Value too large for defined data type", [ECANCELED] = "Operation canceled", [ENOTRECOVERABLE] = "State not recoverable", [EOWNERDEAD] = "Previous owner died", [ESTRPIPE] = "Streams pipe error", [ERESTARTSYS] = "Restartable system call was interrupted", }; static char _error_string[100]; char * strerror(int errnum) { char * str = (errnum >= 0 && (size_t)errnum < sizeof(_error_strings) / sizeof(*_error_strings)) ? _error_strings[errnum] : NULL; if (!str) { snprintf(_error_string, 100, "%d", errnum); return _error_string; } return str; } ================================================ FILE: libc/string/strncmp.c ================================================ #include int strncmp(const char *s1, const char *s2, size_t n) { if (n == 0) return 0; while (n-- && *s1 == *s2) { if (!n || !*s1) break; s1++; s2++; } return (*(unsigned char *)s1) - (*(unsigned char *)s2); } ================================================ FILE: libc/string/strncpy.c ================================================ #include char * strncpy(char * dest, const char * src, size_t n) { char * out = dest; while (n > 0) { if (!*src) break; *out = *src; ++out; ++src; --n; } for (int i = 0; i < (int)n; ++i) { *out = '\0'; ++out; } return out; } ================================================ FILE: libc/string/strsignal.c ================================================ #include #include #include const char * const sys_siglist[] = { [SIGHUP] = "Hangup", [SIGINT] = "Interrupt", [SIGQUIT] = "Quit", [SIGILL] = "Illegal instruction", [SIGTRAP] = "Trace/breakpoint trap", [SIGABRT] = "Aborted", [SIGEMT] = "Emulation trap", [SIGFPE] = "Arithmetic exception", [SIGKILL] = "Killed", [SIGBUS] = "Bus error", [SIGSEGV] = "Segmentation fault", [SIGSYS] = "Bad system call", [SIGPIPE] = "Broken pipe", [SIGALRM] = "Alarm clock", [SIGTERM] = "Terminated", [SIGUSR1] = "User defined signal 1", [SIGUSR2] = "User defined signal 2", [SIGCHLD] = "Child process status", [SIGPWR] = "Power failure", [SIGWINCH] = "Window changed", [SIGURG] = "Urgent I/O condition", [SIGPOLL] = "Pollable event", [SIGSTOP] = "Stopped", [SIGTSTP] = "Stopped", [SIGCONT] = "Continued", [SIGTTIN] = "Stopped (tty input)", [SIGTTOU] = "Stopped (tty output)", [SIGTTOUT] = "Stopped (tty output)", [SIGVTALRM] = "Virtual timer expired", [SIGPROF] = "Profiling timer expired", [SIGXCPU] = "CPU time limit exceeded", [SIGXFSZ] = "File size limit exceeded", /* silly stuff */ [SIGWAITING] = "Waiting", [SIGDIAF] = "Died in a fire", [SIGHATE] = "Hated", [SIGWINEVENT] = "Window event", [SIGCAT] = "Meow", }; char * strsignal(int sig) { static char _signal_description[256]; if (sig > 0 && sig < NUMSIGNALS) { snprintf(_signal_description, 256, "%s", sys_siglist[sig]); } else { snprintf(_signal_description, 256, "Killed by signal %d", sig); } return _signal_description; } ================================================ FILE: libc/string/strxfrm.c ================================================ #include /** * We only really support the "C" locale, so this is always * just a dumb memcpy. */ size_t strxfrm(char *dest, const char *src, size_t n) { size_t i = 0; while (*src && i < n) { *dest = *src; dest++; src++; i++; } if (i < n) { *dest = '\0'; } return i; } ================================================ FILE: libc/strings/strcasecmp.c ================================================ #include #include int strcasecmp(const char * s1, const char * s2) { for (; tolower(*s1) == tolower(*s2) && *s1; s1++, s2++); return *(unsigned char *)s1 - *(unsigned char *)s2; } int strncasecmp(const char *s1, const char *s2, size_t n) { if (n == 0) return 0; while (n-- && tolower(*s1) == tolower(*s2)) { if (!n || !*s1) break; s1++; s2++; } return (unsigned int)tolower(*s1) - (unsigned int)tolower(*s2); } ================================================ FILE: libc/sys/fswait.c ================================================ #include #include #include #include DEFN_SYSCALL2(fswait, SYS_FSWAIT, int, int *); DEFN_SYSCALL3(fswait2, SYS_FSWAIT2, int, int *,int); DEFN_SYSCALL4(fswait3, SYS_FSWAIT3, int, int *, int, int *); int fswait(int count, int * fds) { __sets_errno(syscall_fswait(count, fds)); } int fswait2(int count, int * fds, int timeout) { __sets_errno(syscall_fswait2(count, fds, timeout)); } int fswait3(int count, int * fds, int timeout, int * out) { __sets_errno(syscall_fswait3(count, fds, timeout, out)); } ================================================ FILE: libc/sys/mount.c ================================================ #include #include #include DEFN_SYSCALL5(mount, SYS_MOUNT, char *, char *, char *, unsigned long, void *); int mount(char * source, char * target, char * type, unsigned long flags, void * data) { __sets_errno(syscall_mount(source, target, type, flags, data)); } ================================================ FILE: libc/sys/network.c ================================================ /* * socket methods (mostly unimplemented) */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DEFN_SYSCALL3(socket, SYS_SOCKET, int, int, int); DEFN_SYSCALL5(setsockopt, SYS_SETSOCKOPT, int,int,int,const void*,size_t); DEFN_SYSCALL3(bind, SYS_BIND, int,const void*,size_t); DEFN_SYSCALL4(accept, SYS_ACCEPT, int,void*,size_t*,int); DEFN_SYSCALL2(listen, SYS_LISTEN, int,int); DEFN_SYSCALL3(connect, SYS_CONNECT, int,const void*,size_t); DEFN_SYSCALL5(getsockopt, SYS_GETSOCKOPT, int,int,int,void*,size_t*); DEFN_SYSCALL3(recv, SYS_RECV, int,void*,int); DEFN_SYSCALL3(send, SYS_SEND, int,const void*,int); DEFN_SYSCALL2(shutdown, SYS_SHUTDOWN, int, int); DEFN_SYSCALL3(getsockname, SYS_GETSOCKNAME, int,void*,size_t*); DEFN_SYSCALL3(getpeername, SYS_GETPEERNAME, int,void*,size_t*); int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { __sets_errno(syscall_connect(sockfd,addr,addrlen)); } /* All of these should just be reads. */ ssize_t recv(int sockfd, void *buf, size_t len, int flags) { struct iovec _iovec = { buf, len }; struct msghdr _header = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &_iovec, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; return recvmsg(sockfd, &_header, flags); } ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) { struct iovec _iovec = { buf, len }; struct msghdr _header = { .msg_name = src_addr, .msg_namelen = addrlen ? *addrlen : 0, .msg_iov = &_iovec, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; ssize_t result = recvmsg(sockfd, &_header, flags); if (addrlen) *addrlen = _header.msg_namelen; return result; } ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags) { __sets_errno(syscall_recv(sockfd,msg,flags)); } ssize_t send(int sockfd, const void *buf, size_t len, int flags) { struct iovec _iovec = { (void*)buf, len }; struct msghdr _header = { .msg_name = NULL, .msg_namelen = 0, .msg_iov = &_iovec, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; return sendmsg(sockfd, &_header, flags); } ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { struct iovec _iovec = { (void*)buf, len }; struct msghdr _header = { .msg_name = (void*)dest_addr, .msg_namelen = addrlen, .msg_iov = &_iovec, .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, }; return sendmsg(sockfd, &_header, flags); } ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) { __sets_errno(syscall_send(sockfd,msg,flags)); } int socket(int domain, int type, int protocol) { /* Thin wrapper around a new system call, I guess. */ __sets_errno(syscall_socket(domain,type,protocol)); } uint32_t htonl(uint32_t hostlong) { return ( (((hostlong) & 0xFF) << 24) | (((hostlong) & 0xFF00) << 8) | (((hostlong) & 0xFF0000) >> 8) | (((hostlong) & 0xFF000000) >> 24)); } uint16_t htons(uint16_t hostshort) { return ( (((hostshort) & 0xFF) << 8) | (((hostshort) & 0xFF00) >> 8) ); } uint32_t ntohl(uint32_t netlong) { return htonl(netlong); } uint16_t ntohs(uint16_t netshort) { return htons(netshort); } int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { __sets_errno(syscall_bind(sockfd,addr,addrlen)); } int accept(int sockfd, struct sockaddr * addr, socklen_t * addrlen) { __sets_errno(syscall_accept(sockfd,addr,addrlen,0)); } int accept4(int sockfd, struct sockaddr * addr, socklen_t * addrlen, int flags) { __sets_errno(syscall_accept(sockfd,addr,addrlen,flags)); } int listen(int sockfd, int backlog) { __sets_errno(syscall_listen(sockfd,backlog)); } int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen) { __sets_errno(syscall_getsockopt(sockfd,level,optname,optval,optlen)); } int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen) { __sets_errno(syscall_setsockopt(sockfd,level,optname,optval,optlen)); } int shutdown(int sockfd, int how) { __sets_errno(syscall_shutdown(sockfd,how)); } #define UNIMPLEMENTED fprintf(stderr, "[libnetwork] Unimplemented: %s\n", __FUNCTION__) int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { __sets_errno(syscall_getsockname(sockfd, addr, addrlen)); } int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { __sets_errno(syscall_getpeername(sockfd, addr, addrlen)); } in_addr_t inet_addr(const char * in) { char ip[16]; char * c = ip; uint32_t out[4]; char * i; memcpy(ip, in, strlen(in) < 15 ? strlen(in) + 1 : 15); ip[15] = '\0'; i = (char *)strchr(c, '.'); *i = '\0'; out[0] = atoi(c); c += strlen(c) + 1; i = (char *)strchr(c, '.'); *i = '\0'; out[1] = atoi(c); c += strlen(c) + 1; i = (char *)strchr(c, '.'); *i = '\0'; out[2] = atoi(c); c += strlen(c) + 1; out[3] = atoi(c); return htonl((out[0] << 24) | (out[1] << 16) | (out[2] << 8) | (out[3])); } char * inet_ntoa(struct in_addr in) { static char buf[17]; uint32_t hostOrder = ntohl(in.s_addr); snprintf(buf,17,"%d.%d.%d.%d", (hostOrder >> 24) & 0xFF, (hostOrder >> 16) & 0xFF, (hostOrder >> 8) & 0xFF, (hostOrder >> 0) & 0xFF); return buf; } static struct hostent _hostent = {0}; static uint32_t _hostent_addr = 0; static char * _host_entry_list[2] = {0}; struct dns_packet { uint16_t qid; uint16_t flags; uint16_t questions; uint16_t answers; uint16_t authorities; uint16_t additional; uint8_t data[]; } __attribute__((packed)) __attribute__((aligned(2))); struct hostent * gethostbyname(const char * name) { /* Is name just an IP address? */ int maybe_ip = 1; int dots = 0; for (const char * c = name; *c; ++c) { if ((*c < '0' || *c > '9') && *c != '.') { maybe_ip = 0; break; } if (*c == '.') { dots++; if (dots > 3) { maybe_ip = 0; break; } } } if (maybe_ip && dots == 3) { _hostent.h_name = (char*)name; _hostent.h_aliases = NULL; _hostent.h_addrtype = AF_INET; _hostent.h_length = sizeof(uint32_t); _hostent.h_addr_list = _host_entry_list; _host_entry_list[0] = (char*)&_hostent_addr; _hostent_addr = inet_addr(name); return &_hostent; } if (!strcmp(name,"localhost")) { _hostent.h_name = (char*)name; _hostent.h_aliases = NULL; _hostent.h_addrtype = AF_INET; _hostent.h_length = sizeof(uint32_t); _hostent.h_addr_list = _host_entry_list; _host_entry_list[0] = (char*)&_hostent_addr; _hostent_addr = inet_addr("127.0.0.1"); return &_hostent; } /* Try to open /etc/resolv.conf */ FILE * resolv = fopen("/etc/resolv.conf","r"); if (!resolv) resolv = fopen("/var/resolv.conf","r"); if (!resolv) { fprintf(stderr, "gethostbyname: no resolver\n"); return NULL; } /* Try to get a udp socket */ int sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) { fprintf(stderr, "gethostbyname: could not get a socket\n"); return NULL; } /* Try to send something to the name server */ char tmp[256]; fread(tmp, 256, 1, resolv); if (strncmp(tmp,"nameserver ",strlen("nameserver "))) { fprintf(stderr, "gethostbyname: resolv doesn't look right?\n"); } /* Try to convert so we can connect... */ uint32_t ns_addr = inet_addr(tmp + strlen("nameserver ")); /* Form a DNS request */ char dat[256]; struct dns_packet * req = (struct dns_packet*)&dat; uint16_t qid = rand() & 0xFFFF; req->qid = htons(qid); req->flags = htons(0x0100); req->questions = htons(1); req->answers = htons(0); req->authorities = htons(0); req->additional = htons(0); /* Turn requested name into DNS request */ ssize_t i = 0; const char * c = name; while (*c) { const char * n = strchr(c,'.'); if (!n) n = c + strlen(c); ssize_t len = n - c; req->data[i++] = len; for (; c < n; ++c, ++i) { req->data[i] = *c; } if (!*c) break; c++; } req->data[i++] = 0x00; req->data[i++] = 0x00; req->data[i++] = 0x01; /* A */ req->data[i++] = 0x00; req->data[i++] = 0x01; /* IN */ struct sockaddr_in dest; dest.sin_family = AF_INET; dest.sin_port = htons(53); memcpy(&dest.sin_addr.s_addr, &ns_addr, sizeof(ns_addr)); if (sendto(sock, &dat, sizeof(struct dns_packet) + i, 0, (struct sockaddr*)&dest, sizeof(struct sockaddr_in)) < 0) { fprintf(stderr, "gethostbyname: failed to send\n"); return NULL; } /* Wait for a response, but don't wait too long. */ struct pollfd fds[1]; fds[0].fd = sock; fds[0].events = POLLIN; int ret = poll(fds,1,2000); /* Two seconds? Is that okay? */ if (ret <= 0) { fprintf(stderr, "gethostbyname: timed out\n"); return NULL; } char buf[1550]; ssize_t len = recv(sock, buf, 1550, 0); close(sock); if (len < 0) { fprintf(stderr, "gethostbyname: failed to recv\n"); return NULL; } /* Now examine the response */ struct dns_packet * response = (struct dns_packet *)&buf; if (ntohs(response->answers) == 0) { fprintf(stderr, "gethostbyname: no answer\n"); return NULL; } uint16_t answers = ntohs(response->answers); uint16_t queries = ntohs(response->questions); const unsigned char * d = response->data; for (uint16_t i = 0; i < queries; ++i) { while (1) { if (d - response->data >= len) goto _nope; int l = *d++; if ((l & 0xc0) == 0xc0) { d++; break; } if (!l) break; d += l; } d += 4; } for (uint16_t i = 0; i < answers; ++i) { while (1) { if (d - response->data >= len) goto _nope; int l = *d++; if ((l & 0xc0) == 0xc0) { d++; break; } if (!l) break; d += l; } if (d - response->data > len + 10) goto _nope; d += 2; /* skip type */ uint16_t cls = d[0] * 256 + d[1]; d += 2; d += 4; /* skip ttl */ uint16_t dlen = d[0] * 256 + d[1]; d += 2; if (dlen == 4 && cls == 1) { if (d - response->data > len + dlen) goto _nope; /* Get a return value */ _hostent.h_name = (char*)name; _hostent.h_aliases = NULL; _hostent.h_addrtype = AF_INET; _hostent.h_length = sizeof(uint32_t); _hostent.h_addr_list = _host_entry_list; _host_entry_list[0] = (char*)&_hostent_addr; _hostent_addr = *(uint32_t*)(d); return &_hostent; } d += dlen; } _nope: fprintf(stderr, "gethostbyname: no viable answer\n"); return NULL; } int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) { return -ENOSYS; } int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) { struct hostent * ent = gethostbyname(node); if (!ent) return -EINVAL; /* EAI_FAIL */ *res = malloc(sizeof(struct addrinfo)); (*res)->ai_flags = 0; (*res)->ai_family = AF_INET; (*res)->ai_socktype = 0; (*res)->ai_protocol = 0; (*res)->ai_addrlen = sizeof(struct sockaddr_in); struct sockaddr_in * addr = malloc(sizeof(struct sockaddr_in)); addr->sin_family = AF_INET; memcpy(&addr->sin_addr.s_addr, ent->h_addr, ent->h_length); (*res)->ai_addr = (struct sockaddr *)addr; (*res)->ai_canonname = NULL; (*res)->ai_next = NULL; return 0; } void freeaddrinfo(struct addrinfo *res) { if (res->ai_addr) free(res->ai_addr); free(res); } ================================================ FILE: libc/sys/ptrace.c ================================================ #include #include #include #include DEFN_SYSCALL4(ptrace, SYS_PTRACE, int, int, void *, void *); long ptrace(enum __ptrace_request request, pid_t pid, void * addr, void * data) { __sets_errno(syscall_ptrace(request,pid,addr,data)); } ================================================ FILE: libc/sys/reboot.c ================================================ #include #include #include DEFN_SYSCALL0(reboot, SYS_REBOOT); /* TODO: define reboot() */ ================================================ FILE: libc/sys/shm.c ================================================ #include #include DEFN_SYSCALL2(shm_obtain, SYS_SHM_OBTAIN, const char *, size_t *); DEFN_SYSCALL1(shm_release, SYS_SHM_RELEASE, const char *); void * shm_obtain(const char * path, size_t * size) { return (void *)syscall_shm_obtain(path, size); } int shm_release(const char * path) { return syscall_shm_release(path); } ================================================ FILE: libc/sys/sysfunc.c ================================================ #include #include #include #include DEFN_SYSCALL2(sysfunc, SYS_SYSFUNC, int, char **); extern int sysfunc(int command, char ** args) { __sets_errno(syscall_sysfunc(command, args)); } ================================================ FILE: libc/sys/uname.c ================================================ #include #include #include DEFN_SYSCALL1(uname, SYS_UNAME, void *); int uname(struct utsname *__name) { return syscall_uname((void *)__name); } ================================================ FILE: libc/sys/wait.c ================================================ #include #include #include DEFN_SYSCALL3(waitpid, SYS_WAITPID, int, int *, int); int waitpid(int pid, int *status, int options) { /* XXX: status, options? */ __sets_errno(syscall_waitpid(pid, status, options)); } int wait(int *status) { return waitpid(-1, status, 0); } ================================================ FILE: libc/time/clock.c ================================================ #include #include clock_t clock(void) { struct tms timeValues; times(&timeValues); return timeValues.tms_utime; } ================================================ FILE: libc/time/clock_gettime.c ================================================ #include #include #include int clock_getres(clockid_t clk_id, struct timespec *res) { if (clk_id < 0 || clk_id > 1) { errno = EINVAL; return -1; } res->tv_sec = 0; res->tv_nsec = 1000; return 0; } int clock_gettime(clockid_t clk_id, struct timespec *tp) { if (clk_id < 0 || clk_id > 1) { errno = EINVAL; return -1; } struct timeval t; gettimeofday(&t, NULL); tp->tv_sec = t.tv_sec; tp->tv_nsec = t.tv_usec * 1000; return 0; } ================================================ FILE: libc/time/ctime.c ================================================ #include #include /* * TODO: Also supposed to set tz values... */ char * ctime(const time_t * timep) { return asctime(localtime(timep)); } ================================================ FILE: libc/time/gettimeofday.c ================================================ #include #include #include DEFN_SYSCALL2(gettimeofday, SYS_GETTIMEOFDAY, void *, void *); int gettimeofday(struct timeval *p, void *z){ return syscall_gettimeofday(p,z); } ================================================ FILE: libc/time/localtime.c ================================================ #include #include #include #include #include #include #define SEC_DAY 86400 #define fprintf(...) static struct tm _timevalue; static int year_is_leap(int year) { return ((year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0))); } // 0 was a Thursday static int day_of_week(long seconds) { long day = seconds / SEC_DAY; day += 4; return day % 7; } static long days_in_month(int month, int year) { switch(month) { case 12: return 31; case 11: return 30; case 10: return 31; case 9: return 30; case 8: return 31; case 7: return 31; case 6: return 30; case 5: return 31; case 4: return 30; case 3: return 31; case 2: return year_is_leap(year) ? 29 : 28; case 1: return 31; } return 0; } static struct tm * fill_time(const time_t * timep, struct tm * _timevalue, const char * tzName, int tzOffset) { time_t timeVal = *timep + tzOffset; _timevalue->_tm_zone_name = tzName; _timevalue->_tm_zone_offset = tzOffset; long seconds = timeVal < 0 ? -2208988800L : 0; long year_sec = 0; int startYear = timeVal < 0 ? 1900 : 1970; for (int year = startYear; year < 2100; ++year) { long added = year_is_leap(year) ? 366 : 365; long secs = added * 86400; if (seconds + secs > timeVal) { _timevalue->tm_year = year - 1900; year_sec = seconds; for (int month = 1; month <= 12; ++month) { secs = days_in_month(month, year) * SEC_DAY; if (seconds + secs > timeVal) { _timevalue->tm_mon = month - 1; for (int day = 1; day <= days_in_month(month, year); ++day) { secs = 60 * 60 * 24; if (seconds + secs > timeVal) { _timevalue->tm_mday = day; for (int hour = 1; hour <= 24; ++hour) { secs = 60 * 60; if (seconds + secs > timeVal) { long remaining = timeVal - seconds; _timevalue->tm_hour = hour - 1; _timevalue->tm_min = remaining / 60; _timevalue->tm_sec = remaining % 60; _timevalue->tm_wday = day_of_week(timeVal); _timevalue->tm_yday = (timeVal - year_sec) / SEC_DAY; _timevalue->tm_isdst = 0; return _timevalue; } else { seconds += secs; } } return NULL; } else { seconds += secs; } } return NULL; } else { seconds += secs; } } return NULL; } else { seconds += secs; } } return (void *)0; } #define HOURS 3600 #define MINUTES 60 static int get_timezone_offset(void) { char * tzOff = getenv("TZ_OFFSET"); if (!tzOff) return 0; char * endptr; int out = strtol(tzOff,&endptr,10); if (*endptr) return 0; return out; } struct timezone_offset_db { int offset; const char * abbrev; }; static struct timezone_offset_db common_offsets[] = { {0, "UTC"}, {1 * HOURS, "CEST"}, /* Central Europe Standard Time */ {8 * HOURS, "SST"}, /* Singapore Standard Time */ {9 * HOURS, "JST"}, /* Japan Standard Time */ {-5 * HOURS, "EST"}, /* US Eastern Standard */ {-6 * HOURS, "CST"}, /* US Central Standard */ {-7 * HOURS, "MST"}, /* US Mountain Standard */ {-8 * HOURS, "PST"}, /* US Pacific Standard */ {0, NULL}, }; static char * get_timezone(void) { static char buf[20]; char * tzEnv = getenv("TZ"); if (!tzEnv) { /* Is there an offset? */ int offset = get_timezone_offset(); for (struct timezone_offset_db * db = common_offsets; db->abbrev; db++) { if (offset == db->offset) return (char*)db->abbrev; } /* Is it some number of hours? */ if (offset % HOURS == 0) { if (offset > 0) { snprintf(buf, 20, "UTC+%d", offset / HOURS); } else { snprintf(buf, 20, "UTC-%d", -offset / HOURS); } return buf; } return "???"; } return tzEnv; } struct tm *localtime_r(const time_t *timep, struct tm * _timevalue) { return fill_time(timep, _timevalue, get_timezone(), get_timezone_offset()); } struct tm * gmtime_r(const time_t * timep, struct tm * tm) { return fill_time(timep, tm, "UTC", 0); } struct tm * localtime(const time_t *timep) { return fill_time(timep, &_timevalue, get_timezone(), get_timezone_offset()); } struct tm *gmtime(const time_t *timep) { return fill_time(timep, &_timevalue, "UTC", 0); } static unsigned int secs_of_years(int years) { unsigned int days = 0; while (years > 1969) { days += 365; if (year_is_leap(years)) { days++; } years--; } return days * 86400; } static long secs_of_month(int months, int year) { long days = 0; for (int i = 1; i < months; ++i) { days += days_in_month(i, year); } return days * SEC_DAY; } time_t mktime(struct tm *tm) { return secs_of_years(tm->tm_year + 1899) + secs_of_month(tm->tm_mon + 1, tm->tm_year + 1900) + (tm->tm_mday - 1) * 86400 + (tm->tm_hour) * 3600 + (tm->tm_min) * 60 + (tm->tm_sec) - tm->_tm_zone_offset; } ================================================ FILE: libc/time/settimeofday.c ================================================ #include #include #include DEFN_SYSCALL2(settimeofday, SYS_SETTIMEOFDAY, void *, void *); int settimeofday(struct timeval *p, void *z){ return syscall_settimeofday(p,z); } ================================================ FILE: libc/time/strftime.c ================================================ #include #include #include #include static char * weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; static char * weekdays_short[] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; static char * months[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static char * months_short[] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; size_t strftime(char *s, size_t max, const char *fmt, const struct tm *tm) { if (!tm) { if (max < sizeof("[tm is null]")) return 0; return sprintf(s, "[tm is null]"); } char * b = s; for (const char *f = fmt; *f; f++) { if (*f != '%') { if (max == 0) return 0; max--; *b++ = *f; continue; } ++f; int _alte = 0; int _alto = 0; if (*f == 'E') { _alte = 1; ++f; } else if (*f == '0') { _alto = 1; ++f; } (void)_alte; /* TODO: Implement these */ (void)_alto; int w = 0; switch (*f) { case 'a': w = snprintf(b, max, "%s", weekdays_short[tm->tm_wday]); break; case 'A': w = snprintf(b, max, "%s", weekdays[tm->tm_wday]); break; case 'h': case 'b': w = snprintf(b, max, "%s", months_short[tm->tm_mon]); break; case 'B': w = snprintf(b, max, "%s", months[tm->tm_mon]); break; case 'c': w = snprintf(b, max, "%s %s %02d %02d:%02d:%02d %04d", weekdays_short[tm->tm_wday], months_short[tm->tm_mon], tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tm->tm_year + 1900); break; case 'C': w = snprintf(b, max, "%02d", (tm->tm_year + 1900) / 100); break; case 'd': w = snprintf(b, max, "%02d", tm->tm_mday); break; case 'D': w = snprintf(b, max, "%02d/%02d/%02d", tm->tm_mon+1, tm->tm_mday, tm->tm_year % 100); break; case 'e': w = snprintf(b, max, "%2d", tm->tm_mday); break; case 'F': w = snprintf(b, max, "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday); break; case 'H': w = snprintf(b, max, "%02d", tm->tm_hour); break; case 'I': w = snprintf(b, max, "%02d", tm->tm_hour == 0 ? 12 : (tm->tm_hour == 12 ? 12 : (tm->tm_hour % 12))); break; case 'j': w = snprintf(b, max, "%03d", tm->tm_yday); break; case 'k': w = snprintf(b, max, "%2d", tm->tm_hour); break; case 'l': w = snprintf(b, max, "%2d", tm->tm_hour == 0 ? 12 : (tm->tm_hour == 12 ? 12 : (tm->tm_hour % 12))); break; case 'm': w = snprintf(b, max, "%02d", tm->tm_mon+1); break; case 'M': w = snprintf(b, max, "%02d", tm->tm_min); break; case 'n': w = snprintf(b, max, "\n"); break; case 'p': w = snprintf(b, max, "%s", tm->tm_hour < 12 ? "AM" : "PM"); break; case 'P': w = snprintf(b, max, "%s", tm->tm_hour < 12 ? "am" : "pm"); break; case 'r': w = snprintf(b, max, "%02d:%02d:%02d %s", tm->tm_hour == 0 ? 12 : (tm->tm_hour == 12 ? 12 : (tm->tm_hour % 12)), tm->tm_min, tm->tm_sec, tm->tm_hour < 12 ? "AM" : "PM"); break; case 'R': w = snprintf(b, max, "%02d:%02d", tm->tm_hour, tm->tm_min); break; case 's': w = snprintf(b, max, "%ld", mktime((struct tm*)tm)); break; case 'S': w = snprintf(b, max, "%02d", tm->tm_sec); break; case 't': w = snprintf(b, max, "\t"); break; case 'T': w = snprintf(b, max, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); break; case 'u': w = snprintf(b, max, "%d", tm->tm_wday == 0 ? 7 : tm->tm_wday); break; case 'w': w = snprintf(b, max, "%d", tm->tm_wday); break; case 'x': w = snprintf(b, max, "%02d/%02d/%02d", tm->tm_mon+1, tm->tm_mday, tm->tm_year % 100); break; case 'X': w = snprintf(b, max, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); break; case 'y': w = snprintf(b, max, "%02d", tm->tm_year % 100); break; case 'Y': w = snprintf(b, max, "%04d", tm->tm_year + 1900); break; case 'z': { int zone_offset = tm->_tm_zone_offset >= 0 ? tm->_tm_zone_offset : -tm->_tm_zone_offset; char sign = tm->_tm_zone_offset >= 0 ? '+' : '-'; int hour = zone_offset / 3600; int mins = (zone_offset / 60) % 60; w = snprintf(b, max, "%c%02d%02d", sign, hour, mins); break; } case 'Z': w = snprintf(b, max, tm->_tm_zone_name); break; case '%': w = snprintf(b, max, "%c", '%'); break; case 'V': case 'W': case 'U': case 'G': case 'g': w = snprintf(b, max, "<%c unsupported>", *f); break; } if (w < 0) return 0; /* error while formatting */ if ((size_t)w >= max) return 0; /* output was truncated */ max -= w; b += w; } /* Ensure the buffer ends in a null */ *b = '\0'; return b - s; } static char output[26]; char * asctime(const struct tm *tm) { strftime(output, 26, "%a %b %d %T %Y\n", tm); return output; } ================================================ FILE: libc/time/time.c ================================================ #include #include time_t time(time_t * out) { struct timeval p; gettimeofday(&p, NULL); if (out) { *out = p.tv_sec; } return p.tv_sec; } double difftime(time_t a, time_t b) { return (double)(a - b); } ================================================ FILE: libc/time/times.c ================================================ #include #include #include #include DEFN_SYSCALL1(times, SYS_TIMES, struct tms *); clock_t times(struct tms * buf) { __sets_errno(syscall_times(buf)); } ================================================ FILE: libc/unistd/access.c ================================================ #include #include #include #include DEFN_SYSCALL2(access, SYS_ACCESS, char *, int); int access(const char *pathname, int mode) { int result = syscall_access((char *)pathname, mode); if (result < 0) { errno = ENOENT; /* XXX */ return -1; } return result; } ================================================ FILE: libc/unistd/alarm.c ================================================ #include extern char * _argv_0; unsigned int alarm(unsigned int seconds) { fprintf(stderr, "%s: alarm requested (%d seconds)\n", _argv_0, seconds); return 0; } ================================================ FILE: libc/unistd/chdir.c ================================================ #include #include #include #include DEFN_SYSCALL1(chdir, SYS_CHDIR, char *); int chdir(const char *path) { __sets_errno(syscall_chdir((char*)path)); } ================================================ FILE: libc/unistd/chmod.c ================================================ #include #include #include #include DEFN_SYSCALL2(chmod, SYS_CHMOD, char *, int); int chmod(const char *path, mode_t mode) { __sets_errno(syscall_chmod((char *)path, mode)); } DEFN_SYSCALL2(fchmod, SYS_FCHMOD, int, int); int fchmod(int fd, mode_t mode) { __sets_errno(syscall_fchmod(fd, mode)); } ================================================ FILE: libc/unistd/chown.c ================================================ #include #include #include #include DEFN_SYSCALL3(chown, SYS_CHOWN, char *, int, int); int chown(const char * pathname, uid_t owner, gid_t group) { __sets_errno(syscall_chown((char*)pathname,owner,group)); } DEFN_SYSCALL3(fchown, SYS_FCHOWN, int, int, int); int fchown(int fd, uid_t owner, gid_t group) { __sets_errno(syscall_fchown(fd,owner,group)); } ================================================ FILE: libc/unistd/close.c ================================================ #include #include #include DEFN_SYSCALL1(close, SYS_CLOSE, int); int close(int file) { return syscall_close(file); } ================================================ FILE: libc/unistd/creat.c ================================================ #include #include int creat(const char *path, mode_t mode) { return open(path, O_WRONLY|O_CREAT|O_TRUNC, mode); } ================================================ FILE: libc/unistd/dup2.c ================================================ #include #include #include DEFN_SYSCALL2(dup2, SYS_DUP2, int, int); int dup2(int oldfd, int newfd) { return syscall_dup2(oldfd, newfd); } int dup(int oldfd) { return dup2(oldfd, -1); } ================================================ FILE: libc/unistd/execvp.c ================================================ #include #include #include #include #include #include #include #include DEFN_SYSCALL3(execve, SYS_EXECVE, char *, char **, char **); extern char ** environ; #define DEFAULT_PATH "/bin:/usr/bin" int execve(const char *name, char * const argv[], char * const envp[]) { __sets_errno(syscall_execve((char*)name,(char**)argv,(char**)envp)); } int execvpe(const char *file, char *const argv[], char *const envp[]) { if (file && (!strstr(file, "/"))) { /* We don't quite understand "$PATH", so... */ char * path = getenv("PATH"); if (!path) { path = DEFAULT_PATH; } char * xpath = strdup(path); char * p, * last; for ((p = strtok_r(xpath, ":", &last)); p; p = strtok_r(NULL, ":", &last)) { int r; struct stat stat_buf; char * exe = malloc(strlen(p) + strlen(file) + 2); strcpy(exe, p); strcat(exe, "/"); strcat(exe, file); r = stat(exe, &stat_buf); if (r != 0) { continue; } if (!(stat_buf.st_mode & 0111)) { continue; /* XXX not technically correct; need to test perms */ } return execve(exe, argv, envp); } free(xpath); errno = ENOENT; return -1; } else if (file) { return execve(file, argv, envp); } errno = ENOENT; return -1; } int execvp(const char *file, char *const argv[]) { return execvpe(file, argv, environ); } int execv(const char * file, char * const argv[]) { return execve(file, argv, environ); } int execl(const char *path, const char *arg, ...) { int argc = 1; /* Count */ va_list ap; /* Count */ va_start(ap, arg); while (va_arg(ap, char *)) argc++; va_end(ap); /* Copy */ char * argv[argc+1]; va_start(ap, arg); argv[0] = (char*)arg; for (int i = 1; i <= argc; ++i) argv[i] = va_arg(ap, char*); va_end(ap); /* Exec */ return execv(path, argv); } int execlp(const char *path, const char *arg, ...) { int argc = 1; /* Count */ va_list ap; /* Count */ va_start(ap, arg); while (va_arg(ap, char *)) argc++; va_end(ap); /* Copy */ char * argv[argc+1]; va_start(ap, arg); argv[0] = (char*)arg; for (int i = 1; i <= argc; ++i) argv[i] = va_arg(ap, char*); va_end(ap); /* Exec */ return execvp(path, argv); } int execle(const char *path, const char *arg, ...) { int argc = 1; /* Count */ va_list ap; /* Count */ va_start(ap, arg); while (va_arg(ap, char *)) argc++; va_end(ap); /* Copy */ char * argv[argc+1]; va_start(ap, arg); argv[0] = (char*)arg; for (int i = 1; i <= argc; ++i) argv[i] = va_arg(ap, char*); char ** envp = va_arg(ap, char**); va_end(ap); /* Exec */ return execve(path, argv, envp); } ================================================ FILE: libc/unistd/exit.c ================================================ #include #include void exit(int val) { _handle_atexit(); _exit(val); } ================================================ FILE: libc/unistd/fcntl.c ================================================ #include #include #include #include #include #include DEFN_SYSCALL3(fcntl, SYS_FCNTL, int, int, long); int fcntl(int fd, int cmd, ...) { va_list ap; va_start(ap, cmd); long arg = 0; switch (cmd) { case F_SETFD: case F_SETFL: case F_DUPFD: arg = va_arg(ap, int); /* "taken as an integer of type int" */ break; case F_GETLK: case F_SETLK: case F_SETLKW: arg = (long)(uintptr_t)va_arg(ap, struct flock *); break; } va_end(ap); __sets_errno(syscall_fcntl(fd, cmd, arg)); } ================================================ FILE: libc/unistd/fork.c ================================================ #include #include #include DEFN_SYSCALL0(fork, SYS_FORK); pid_t fork(void) { return syscall_fork(); } ================================================ FILE: libc/unistd/fstat.c ================================================ #include #include #include #include #include DEFN_SYSCALL2(stat, SYS_STAT, int, void *); int fstat(int file, struct stat *st) { __sets_errno(syscall_stat(file, st)); } ================================================ FILE: libc/unistd/getcwd.c ================================================ #include #include #include #include #include DEFN_SYSCALL2(getcwd, SYS_GETCWD, char *, size_t); char *getcwd(char *buf, size_t size) { if (!buf) buf = malloc(size); return (char *)syscall_getcwd(buf, size); } ================================================ FILE: libc/unistd/getegid.c ================================================ #include #include #include DEFN_SYSCALL0(getegid, SYS_GETEGID); gid_t getegid(void) { return syscall_getegid(); } ================================================ FILE: libc/unistd/geteuid.c ================================================ #include #include #include DEFN_SYSCALL0(geteuid, SYS_GETEUID); uid_t geteuid(void) { return syscall_geteuid(); } ================================================ FILE: libc/unistd/getgid.c ================================================ #include #include #include DEFN_SYSCALL0(getgid, SYS_GETGID); gid_t getgid(void) { return syscall_getgid(); } ================================================ FILE: libc/unistd/getgroups.c ================================================ #include #include #include #include DEFN_SYSCALL2(getgroups, SYS_GETGROUPS, int, gid_t *); int getgroups(int size, gid_t list[]) { __sets_errno(syscall_getgroups(size, list)); } ================================================ FILE: libc/unistd/getlogin.c ================================================ #include #include #include #include #include static char _name[64]; /* NAME_MAX ? */ char * getlogin(void) { int tty = STDIN_FILENO; if (!isatty(tty)) { tty = STDOUT_FILENO; if (!isatty(tty)) { tty = STDERR_FILENO; if (!isatty(tty)) { errno = ENOTTY; return NULL; } } } char * name = ttyname(tty); if (!name) return NULL; /* Get the owner */ struct stat statbuf; stat(name, &statbuf); struct passwd * passwd = getpwuid(statbuf.st_uid); if (!passwd) return NULL; if (!passwd->pw_name) return NULL; memcpy(_name, passwd->pw_name, strlen(passwd->pw_name)); return _name; } ================================================ FILE: libc/unistd/getopt.c ================================================ #include #include char * optarg = NULL; int optind = 1; int opterr = 1; int optopt = 0; int getopt(int argc, char * const argv[], const char * optstring) { return getopt_long(argc, argv, optstring, NULL, 0); } ================================================ FILE: libc/unistd/getopt_long.c ================================================ #include #include #include #include #include /** * getopt / getopt_long */ int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex) { static char * nextchar = NULL; if (optind >= argc) { return -1; } int print_errors = !!opterr; int was_colon = 0; if (*optstring == ':') { print_errors = 0; optstring++; was_colon = 1; } do { if (!nextchar) { nextchar = argv[optind]; if (*nextchar != '-') { return -1; } else { nextchar++; if (*nextchar == '\0') { /* Special case - is a non-option argument */ return -1; } if (*nextchar == '-') { if (nextchar[1] == '\0') { /* End of arguments */ optind++; return -1; } else if (longopts) { /* Scan through options */ nextchar++; char tmp[strlen(nextchar)+1]; strcpy(tmp, nextchar); char * eq = strchr(tmp, '='); if (eq) { *eq = '\0'; optarg = nextchar + (eq - tmp + 1); } else { optarg = NULL; } int found = -1; for (int index = 0; longopts[index].name; ++index) { if (!strcmp(longopts[index].name, tmp)) { found = index; } } if (found == -1) { if (longindex) { *longindex = -1; } if (print_errors) { fprintf(stderr, "%s: Unknown long argument: %s\n", argv[0], tmp); } nextchar = NULL; optind++; optopt = '\0'; return '?'; } else { if (longindex) { *longindex = found; } if (longopts[found].has_arg == required_argument) { if (!optarg) { optarg = argv[optind+1]; optind++; } } nextchar = NULL; optind++; if (!longopts[found].flag) { return longopts[found].val; } else { *longopts[found].flag = longopts[found].val; return 0; } } } /* else: --foo but not long, see if -: is set, otherwise continue as if - was an option */ } } } if (*nextchar == '\0') { nextchar = NULL; optind++; continue; } if (!isalnum(*nextchar) && *nextchar != '?' && *nextchar != '-') { if (print_errors) { fprintf(stderr, "%s: Invalid option character: %c\n", argv[0], *nextchar); } optopt = *nextchar; nextchar++; return '?'; } char * opt = strchr(optstring, *nextchar); if (!opt) { if (print_errors) { fprintf(stderr, "%s: Invalid option character: %c\n", argv[0], *nextchar); } optopt = *nextchar; nextchar++; return '?'; } int optout = *nextchar; if (opt[1] == ':') { if (nextchar[1] != '\0') { optarg = &nextchar[1]; nextchar = NULL; optind++; } else { if (optind + 1 == argc) { if (print_errors) { fprintf(stderr, "%s: Option requires an argument: '%c'\n", argv[0], *nextchar); } optopt = *nextchar; nextchar++; return was_colon ? ':' : '?'; } optarg = argv[optind+1]; optind += 2; nextchar = NULL; } } else { nextchar++; } return optout; } while (optind < argc); return -1; } ================================================ FILE: libc/unistd/getpgrp.c ================================================ #include int getpgrp() { return getpgid(0); } ================================================ FILE: libc/unistd/getpid.c ================================================ #include #include #include #include DEFN_SYSCALL0(getpid, SYS_GETPID); pid_t getpid(void) { return syscall_getpid(); } pid_t getppid(void) { errno = ENOTSUP; return -1; } ================================================ FILE: libc/unistd/getuid.c ================================================ #include #include #include DEFN_SYSCALL0(getuid, SYS_GETUID); uid_t getuid(void) { return syscall_getuid(); } ================================================ FILE: libc/unistd/getwd.c ================================================ #include char *getwd(char *buf) { return getcwd(buf, 256); } ================================================ FILE: libc/unistd/hostname.c ================================================ #include #include #include #include DEFN_SYSCALL1(sethostname, SYS_SETHOSTNAME, char *); DEFN_SYSCALL1(gethostname, SYS_GETHOSTNAME, char *); int gethostname(char * name, size_t len) { (void)len; /* TODO */ __sets_errno(syscall_gethostname(name)); } int sethostname(const char * name, size_t len) { (void)len; /* TODO */ __sets_errno(syscall_sethostname((char*)name)); } ================================================ FILE: libc/unistd/isatty.c ================================================ #include #include #include int isatty(int fd) { int dtype = ioctl(fd, IOCTLDTYPE, NULL); if (dtype == IOCTL_DTYPE_TTY) return 1; errno = EINVAL; return 0; } ================================================ FILE: libc/unistd/link.c ================================================ #include #include // TODO: // We have a system call for this? int link(const char *old, const char *new) { errno = ENOTSUP; return -1; } ================================================ FILE: libc/unistd/lseek.c ================================================ #include #include #include #include DEFN_SYSCALL3(seek, SYS_SEEK, int, long, int); off_t lseek(int file, off_t ptr, int dir) { __sets_errno(syscall_seek(file,ptr,dir)); } ================================================ FILE: libc/unistd/open.c ================================================ #include #include #include #include #include #include DEFN_SYSCALL3(open, SYS_OPEN, const char *, int, int); int open(const char *name, int flags, ...) { va_list argp; int mode = 0; int result; va_start(argp, flags); if (flags & O_CREAT) mode = va_arg(argp, int); va_end(argp); result = syscall_open(name, flags, mode); if (result == -1) { /* Not sure this is necessary */ if (flags & O_CREAT) { errno = EACCES; } else { errno = ENOENT; } } else if (result < 0) { errno = -result; result = -1; } return result; } ================================================ FILE: libc/unistd/pathconf.c ================================================ #include #include #include #include long pathconf(const char *path, int name) { switch (name) { case _PC_PATH_MAX: return PATH_MAX; default: errno = EINVAL; return -1; } } ================================================ FILE: libc/unistd/pipe.c ================================================ #include #include #include #include DEFN_SYSCALL1(pipe, SYS_PIPE, int *); int pipe(int fildes[2]) { __sets_errno(syscall_pipe((int *)fildes)); } ================================================ FILE: libc/unistd/pread.c ================================================ #include #include #include #include DEFN_SYSCALL4(pread, SYS_PREAD, int, void *, size_t, off_t); ssize_t pread(int fd, void *buf, size_t count, off_t offset) { __sets_errno(syscall_pread(fd,buf,count,offset)); } ================================================ FILE: libc/unistd/pwrite.c ================================================ #include #include #include #include DEFN_SYSCALL4(pwrite, SYS_PWRITE, int, const void *, size_t, off_t); ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { __sets_errno(syscall_pwrite(fd,buf,count,offset)); } ================================================ FILE: libc/unistd/read.c ================================================ #include #include #include #include DEFN_SYSCALL3(read, SYS_READ, int, char *, size_t); ssize_t read(int file, void *ptr, size_t len) { __sets_errno(syscall_read(file,ptr,len)); } ================================================ FILE: libc/unistd/readlink.c ================================================ #include #include #include #include DEFN_SYSCALL3(readlink, SYS_READLINK, char *, char *, int); ssize_t readlink(const char * name, char * buf, size_t len) { __sets_errno(syscall_readlink((char*)name, buf, len)); } ================================================ FILE: libc/unistd/rmdir.c ================================================ #include #include #include int rmdir(const char *pathname) { /* XXX: This is subject to TOCTOU issues, but whatever. */ struct stat st; /* pathname must directly name a directory, not a symlink */ if (lstat(pathname, &st) < 0) return -1; if (!S_ISDIR(st.st_mode)) { errno = ENOTDIR; return -1; } /* our unlink can remove empty directories */ return unlink(pathname); } ================================================ FILE: libc/unistd/sbrk.c ================================================ #include #include #include DEFN_SYSCALL1(sbrk, SYS_SBRK, int); void *sbrk(intptr_t increment) { return (void *)syscall_sbrk(increment); } ================================================ FILE: libc/unistd/setgid.c ================================================ #include #include #include #include DEFN_SYSCALL1(setgid, SYS_SETGID, unsigned int); int setgid(gid_t uid) { __sets_errno(syscall_setgid(uid)); } ================================================ FILE: libc/unistd/setgroups.c ================================================ #include #include #include #include DEFN_SYSCALL2(setgroups, SYS_SETGROUPS, int, const gid_t *); int setgroups(int size, const gid_t list[]) { __sets_errno(syscall_setgroups(size, list)); } ================================================ FILE: libc/unistd/setpgid.c ================================================ #include #include #include #include DEFN_SYSCALL2(setpgid, SYS_SETPGID, int, int); DEFN_SYSCALL1(getpgid, SYS_GETPGID, int); int setpgid(pid_t pid, pid_t pgid) { __sets_errno(syscall_setpgid((int)pid,(int)pgid)); } pid_t getpgid(pid_t pid) { __sets_errno(syscall_getpgid((int)pid)); } ================================================ FILE: libc/unistd/setsid.c ================================================ #include #include #include #include DEFN_SYSCALL0(setsid, SYS_SETSID); pid_t setsid(void) { __sets_errno(syscall_setsid()); } ================================================ FILE: libc/unistd/setuid.c ================================================ #include #include #include #include DEFN_SYSCALL1(setuid, SYS_SETUID, unsigned int); int setuid(uid_t uid) { __sets_errno(syscall_setuid(uid)); } ================================================ FILE: libc/unistd/sleep.c ================================================ #include unsigned int sleep(unsigned int seconds) { syscall_sleep(seconds, 0); return 0; } ================================================ FILE: libc/unistd/stat.c ================================================ #include #include #include #include #include DEFN_SYSCALL2(statf, SYS_STATF, char *, void *); DEFN_SYSCALL2(lstat, SYS_LSTAT, char *, void *); int stat(const char *file, struct stat *st){ int ret = syscall_statf((char *)file, (void *)st); if (ret >= 0) { return ret; } else { errno = -ret; memset(st, 0x00, sizeof(struct stat)); return -1; } } int lstat(const char *path, struct stat *st) { int ret = syscall_lstat((char *)path, (void *)st); if (ret >= 0) { return ret; } else { errno = -ret; memset(st, 0x00, sizeof(struct stat)); return -1; } } ================================================ FILE: libc/unistd/statcompat.c ================================================ #include #include struct stat_compat { int st_dev; int st_ino; mode_t st_mode; nlink_t st_nlink; uid_t st_uid; gid_t st_gid; dev_t st_rdev; off_t st_size; time_t __st_atime; time_t __st_mtime; time_t __st_ctime; blksize_t st_blksize; blkcnt_t st_blocks; }; static void convert(const struct stat *nst, struct stat_compat *ost) { ost->st_dev = nst->st_dev; ost->st_ino = nst->st_ino; ost->st_mode = nst->st_mode; ost->st_nlink = nst->st_nlink; ost->st_uid = nst->st_uid; ost->st_gid = nst->st_gid; ost->st_rdev = nst->st_rdev; ost->st_size = nst->st_size; ost->__st_atime = nst->st_atim.tv_sec; ost->__st_mtime = nst->st_mtim.tv_sec; ost->__st_ctime = nst->st_ctim.tv_sec; ost->st_blksize = nst->st_blksize; ost->st_blocks = nst->st_blocks; } int __stat_compat(const char *, struct stat_compat *) __asm__("stat"); int __stat_compat(const char *path, struct stat_compat *st) { struct stat nst; int ret = stat(path, &nst); if (ret < 0) return ret; convert(&nst,st); return ret; } int __lstat_compat(const char *, struct stat_compat *) __asm__("lstat"); int __lstat_compat(const char *path, struct stat_compat *st) { struct stat nst; int ret = lstat(path, &nst); if (ret < 0) return ret; convert(&nst,st); return ret; } int __fstat_compat(int, struct stat_compat *) __asm__("fstat"); int __fstat_compat(int fd, struct stat_compat *st) { struct stat nst; int ret = fstat(fd, &nst); if (ret < 0) return ret; convert(&nst,st); return ret; } ================================================ FILE: libc/unistd/symlink.c ================================================ #include #include #include #include DEFN_SYSCALL2(symlink, SYS_SYMLINK, const char *, const char *); int symlink(const char *target, const char *name) { __sets_errno(syscall_symlink(target, name)); } ================================================ FILE: libc/unistd/sync.c ================================================ #include #include #include void sync(void) { /* TODO */ } ================================================ FILE: libc/unistd/truncate.c ================================================ #include #include #include #include #include DEFN_SYSCALL2(truncate, SYS_TRUNCATE, char *, off_t); int truncate(const char * path, off_t length) { __sets_errno(syscall_truncate((char*)path, length)); } DEFN_SYSCALL2(ftruncate, SYS_FTRUNCATE, int, off_t); int ftruncate(int fd, off_t length) { __sets_errno(syscall_ftruncate(fd, length)); } ================================================ FILE: libc/unistd/ttyname.c ================================================ #include #include #include #include static char _tty_name[30]; /* only needs to hold /dev/pty/ttyXXXXXXX */ char * ttyname(int fd) { if (!isatty(fd)) { errno = ENOTTY; return NULL; } ioctl(fd, IOCTLTTYNAME, _tty_name); return _tty_name; } int ttyname_r(int fd, char * buf, size_t buflen) { if (!isatty(fd)) return ENOTTY; if (buflen < 30) return ERANGE; ioctl(fd, IOCTLTTYNAME, buf); return 0; } ================================================ FILE: libc/unistd/umask.c ================================================ #include #include #include DEFN_SYSCALL1(umask, SYS_UMASK, int); mode_t umask(mode_t mask) { return syscall_umask(mask); } ================================================ FILE: libc/unistd/unlink.c ================================================ #include #include #include #include DEFN_SYSCALL1(unlink, SYS_UNLINK, char *); int unlink(const char * pathname) { __sets_errno(syscall_unlink((char *)pathname)); } ================================================ FILE: libc/unistd/usleep.c ================================================ #include #include #include DEFN_SYSCALL2(sleep, SYS_SLEEP, unsigned long, unsigned long); int usleep(useconds_t usec) { syscall_sleep((usec / 10000) / 1000, (usec / 10000) % 1000); return 0; } ================================================ FILE: libc/unistd/write.c ================================================ #include #include #include #include DEFN_SYSCALL3(write, SYS_WRITE, int, char *, size_t); ssize_t write(int file, const void *ptr, size_t len) { __sets_errno(syscall_write(file,(char *)ptr,len)); } ================================================ FILE: libc/utime/utime.c ================================================ #include #include int utime(const char *filename, const struct utimbuf *times) { /* Unimplemented */ errno = ENOTSUP; return -1; } ================================================ FILE: libc/wchar/wcscat.c ================================================ #include #include wchar_t * wcscat(wchar_t *dest, const wchar_t *src) { wchar_t * end = dest; while (*end != 0) { ++end; } while (*src) { *end = *src; end++; src++; } *end = 0; return dest; } wchar_t * wcsncat(wchar_t *dest, const wchar_t * src, size_t n) { wchar_t * end = dest; size_t c = 0; while (*end != 0) { ++end; } while (*src && c < n) { *end = *src; end++; src++; c++; } *end = 0; return dest; } ================================================ FILE: libc/wchar/wcscmp.c ================================================ #include int wcscmp(const wchar_t *l, const wchar_t *r) { for (; *l == *r && *l; l++, r++); return *(unsigned int *)l - *(unsigned int *)r; } int wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n) { if (n == 0) return 0; while (n-- && *s1 == *s2) { if (!n || !*s1) break; s1++; s2++; } return (*s1) - (*s2); } ================================================ FILE: libc/wchar/wcscpy.c ================================================ #include wchar_t * wcscpy(wchar_t * restrict dest, const wchar_t * restrict src) { wchar_t * out = dest; for (; (*dest=*src); src++, dest++); return out; } ================================================ FILE: libc/wchar/wcslen.c ================================================ #include size_t wcslen(const wchar_t * s) { size_t out = 0; while (*s) { out++; s++; } return out; } ================================================ FILE: libc/wchar/wcsncpy.c ================================================ #include wchar_t * wcsncpy(wchar_t * dest, const wchar_t * src, size_t n) { wchar_t * out = dest; while (n > 0) { *dest = *src; if (!*src) break; dest++; src++; n--; } return out; } ================================================ FILE: libc/wchar/wcstok.c ================================================ #include size_t wcsspn(const wchar_t * wcs, const wchar_t * accept) { size_t out = 0; while (*wcs) { int good = 0; for (const wchar_t * a = accept; *a; ++a) { if (*wcs == *a) { good = 1; break; } } if (!good) break; out++; wcs++; } return out; } wchar_t *wcspbrk(const wchar_t *wcs, const wchar_t *accept) { while (*wcs) { for (const wchar_t * a = accept; *a; ++a) { if (*wcs == *a) { return (wchar_t *)wcs; } } wcs++; } return NULL; } wchar_t * wcschr(const wchar_t *wcs, wchar_t wc) { while (*wcs != wc && *wcs != 0) { wcs++; } if (!*wcs) return NULL; return (wchar_t *)wcs; } wchar_t * wcsrchr(const wchar_t *wcs, wchar_t wc) { wchar_t * last = NULL; while (*wcs != 0) { if (*wcs == wc) { last = (wchar_t *)wcs; } wcs++; } return last; } wchar_t * wcstok(wchar_t * str, const wchar_t * delim, wchar_t ** saveptr) { wchar_t * token; if (str == NULL) { str = *saveptr; } str += wcsspn(str, delim); if (*str == '\0') { *saveptr = str; return NULL; } token = str; str = wcspbrk(token, delim); if (str == NULL) { *saveptr = (wchar_t *)wcschr(token, '\0'); } else { *str = '\0'; *saveptr = str + 1; } return token; } ================================================ FILE: libc/wchar/wcstol.c ================================================ #include #include #include #include #include static int is_valid(int base, wchar_t c) { if (c < '0') return 0; if (base <= 10) { return c < ('0' + (wchar_t)base); } if (c >= 'a' && c < 'a' + ((wchar_t)base - 10)) return 1; if (c >= 'A' && c < 'A' + ((wchar_t)base - 10)) return 1; if (c >= '0' && c <= '9') return 1; return 0; } static int convert_digit(wchar_t c) { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'z') { return c - 'a' + 0xa; } if (c >= 'A' && c <= 'Z') { return c - 'A' + 0xa; } return 0; } #define strtox(max, type) \ if (base < 0 || base == 1 || base > 36) { \ errno = EINVAL; \ return max; \ } \ while (*nptr && isspace(*nptr)) nptr++; \ int sign = 1; \ if (*nptr == '-') { \ sign = -1; \ nptr++; \ } else if (*nptr == '+') { \ nptr++; \ } \ if (base == 16) { \ if (*nptr == '0') { \ nptr++; \ if (*nptr == 'x') { \ nptr++; \ } \ } \ } \ if (base == 0) { \ if (*nptr == '0') { \ base = 8; \ nptr++; \ if (*nptr == 'x') { \ base = 16; \ nptr++; \ } \ } else { \ base = 10; \ } \ } \ type val = 0; \ while (is_valid(base, *nptr)) { \ val *= base; \ val += convert_digit(*nptr); \ nptr++; \ } \ if (endptr) { \ *endptr = (wchar_t *)nptr; \ } \ if (sign == -1) { \ return -val; \ } else { \ return val; \ } unsigned long int wcstoul(const wchar_t *nptr, wchar_t **endptr, int base) { strtox(ULONG_MAX, unsigned long int); } unsigned long long int wcstoull(const char *nptr, wchar_t **endptr, int base) { strtox(ULLONG_MAX, unsigned long int); } long int wcstol(const wchar_t *nptr, wchar_t **endptr, int base) { strtox(LONG_MAX, unsigned long int); } long long int wcstoll(const wchar_t *nptr, wchar_t **endptr, int base) { strtox(LLONG_MAX, unsigned long long int); } ================================================ FILE: libc/wchar/wcwidth.c ================================================ /* Generated by util/gen_wcwidth.krk */ #include int wcwidth(wchar_t wc) { if (wc == 0) return 0; else if (wc < 0x20) return -1; else if (wc < 0x7f) return 1; else if (wc < 0xa0) return -1; else if (wc < 0x300) return 1; else if (wc < 0x370) return 0; else if (wc < 0x378) return 1; else if (wc < 0x37a) return -1; else if (wc < 0x380) return 1; else if (wc < 0x384) return -1; else if (wc < 0x38b) return 1; else if (wc < 0x38c) return -1; else if (wc < 0x38d) return 1; else if (wc < 0x38e) return -1; else if (wc < 0x3a2) return 1; else if (wc < 0x3a3) return -1; else if (wc < 0x483) return 1; else if (wc < 0x48a) return 0; else if (wc < 0x530) return 1; else if (wc < 0x531) return -1; else if (wc < 0x557) return 1; else if (wc < 0x559) return -1; else if (wc < 0x58b) return 1; else if (wc < 0x58d) return -1; else if (wc < 0x590) return 1; else if (wc < 0x591) return -1; else if (wc < 0x5be) return 0; else if (wc < 0x5bf) return 1; else if (wc < 0x5c0) return 0; else if (wc < 0x5c1) return 1; else if (wc < 0x5c3) return 0; else if (wc < 0x5c4) return 1; else if (wc < 0x5c6) return 0; else if (wc < 0x5c7) return 1; else if (wc < 0x5c8) return 0; else if (wc < 0x5d0) return -1; else if (wc < 0x5eb) return 1; else if (wc < 0x5ef) return -1; else if (wc < 0x5f5) return 1; else if (wc < 0x600) return -1; else if (wc < 0x606) return 0; else if (wc < 0x610) return 1; else if (wc < 0x61b) return 0; else if (wc < 0x61c) return 1; else if (wc < 0x61d) return 0; else if (wc < 0x64b) return 1; else if (wc < 0x660) return 0; else if (wc < 0x670) return 1; else if (wc < 0x671) return 0; else if (wc < 0x6d6) return 1; else if (wc < 0x6de) return 0; else if (wc < 0x6df) return 1; else if (wc < 0x6e5) return 0; else if (wc < 0x6e7) return 1; else if (wc < 0x6e9) return 0; else if (wc < 0x6ea) return 1; else if (wc < 0x6ee) return 0; else if (wc < 0x70e) return 1; else if (wc < 0x70f) return -1; else if (wc < 0x710) return 0; else if (wc < 0x711) return 1; else if (wc < 0x712) return 0; else if (wc < 0x730) return 1; else if (wc < 0x74b) return 0; else if (wc < 0x74d) return -1; else if (wc < 0x7a6) return 1; else if (wc < 0x7b1) return 0; else if (wc < 0x7b2) return 1; else if (wc < 0x7c0) return -1; else if (wc < 0x7eb) return 1; else if (wc < 0x7f4) return 0; else if (wc < 0x7fb) return 1; else if (wc < 0x7fd) return -1; else if (wc < 0x7fe) return 0; else if (wc < 0x816) return 1; else if (wc < 0x81a) return 0; else if (wc < 0x81b) return 1; else if (wc < 0x824) return 0; else if (wc < 0x825) return 1; else if (wc < 0x828) return 0; else if (wc < 0x829) return 1; else if (wc < 0x82e) return 0; else if (wc < 0x830) return -1; else if (wc < 0x83f) return 1; else if (wc < 0x840) return -1; else if (wc < 0x859) return 1; else if (wc < 0x85c) return 0; else if (wc < 0x85e) return -1; else if (wc < 0x85f) return 1; else if (wc < 0x860) return -1; else if (wc < 0x86b) return 1; else if (wc < 0x870) return -1; else if (wc < 0x88f) return 1; else if (wc < 0x890) return -1; else if (wc < 0x892) return 0; else if (wc < 0x898) return -1; else if (wc < 0x8a0) return 0; else if (wc < 0x8ca) return 1; else if (wc < 0x903) return 0; else if (wc < 0x93a) return 1; else if (wc < 0x93b) return 0; else if (wc < 0x93c) return 1; else if (wc < 0x93d) return 0; else if (wc < 0x941) return 1; else if (wc < 0x949) return 0; else if (wc < 0x94d) return 1; else if (wc < 0x94e) return 0; else if (wc < 0x951) return 1; else if (wc < 0x958) return 0; else if (wc < 0x962) return 1; else if (wc < 0x964) return 0; else if (wc < 0x981) return 1; else if (wc < 0x982) return 0; else if (wc < 0x984) return 1; else if (wc < 0x985) return -1; else if (wc < 0x98d) return 1; else if (wc < 0x98f) return -1; else if (wc < 0x991) return 1; else if (wc < 0x993) return -1; else if (wc < 0x9a9) return 1; else if (wc < 0x9aa) return -1; else if (wc < 0x9b1) return 1; else if (wc < 0x9b2) return -1; else if (wc < 0x9b3) return 1; else if (wc < 0x9b6) return -1; else if (wc < 0x9ba) return 1; else if (wc < 0x9bc) return -1; else if (wc < 0x9bd) return 0; else if (wc < 0x9c1) return 1; else if (wc < 0x9c5) return 0; else if (wc < 0x9c7) return -1; else if (wc < 0x9c9) return 1; else if (wc < 0x9cb) return -1; else if (wc < 0x9cd) return 1; else if (wc < 0x9ce) return 0; else if (wc < 0x9cf) return 1; else if (wc < 0x9d7) return -1; else if (wc < 0x9d8) return 1; else if (wc < 0x9dc) return -1; else if (wc < 0x9de) return 1; else if (wc < 0x9df) return -1; else if (wc < 0x9e2) return 1; else if (wc < 0x9e4) return 0; else if (wc < 0x9e6) return -1; else if (wc < 0x9fe) return 1; else if (wc < 0x9ff) return 0; else if (wc < 0xa01) return -1; else if (wc < 0xa03) return 0; else if (wc < 0xa04) return 1; else if (wc < 0xa05) return -1; else if (wc < 0xa0b) return 1; else if (wc < 0xa0f) return -1; else if (wc < 0xa11) return 1; else if (wc < 0xa13) return -1; else if (wc < 0xa29) return 1; else if (wc < 0xa2a) return -1; else if (wc < 0xa31) return 1; else if (wc < 0xa32) return -1; else if (wc < 0xa34) return 1; else if (wc < 0xa35) return -1; else if (wc < 0xa37) return 1; else if (wc < 0xa38) return -1; else if (wc < 0xa3a) return 1; else if (wc < 0xa3c) return -1; else if (wc < 0xa3d) return 0; else if (wc < 0xa3e) return -1; else if (wc < 0xa41) return 1; else if (wc < 0xa43) return 0; else if (wc < 0xa47) return -1; else if (wc < 0xa49) return 0; else if (wc < 0xa4b) return -1; else if (wc < 0xa4e) return 0; else if (wc < 0xa51) return -1; else if (wc < 0xa52) return 0; else if (wc < 0xa59) return -1; else if (wc < 0xa5d) return 1; else if (wc < 0xa5e) return -1; else if (wc < 0xa5f) return 1; else if (wc < 0xa66) return -1; else if (wc < 0xa70) return 1; else if (wc < 0xa72) return 0; else if (wc < 0xa75) return 1; else if (wc < 0xa76) return 0; else if (wc < 0xa77) return 1; else if (wc < 0xa81) return -1; else if (wc < 0xa83) return 0; else if (wc < 0xa84) return 1; else if (wc < 0xa85) return -1; else if (wc < 0xa8e) return 1; else if (wc < 0xa8f) return -1; else if (wc < 0xa92) return 1; else if (wc < 0xa93) return -1; else if (wc < 0xaa9) return 1; else if (wc < 0xaaa) return -1; else if (wc < 0xab1) return 1; else if (wc < 0xab2) return -1; else if (wc < 0xab4) return 1; else if (wc < 0xab5) return -1; else if (wc < 0xaba) return 1; else if (wc < 0xabc) return -1; else if (wc < 0xabd) return 0; else if (wc < 0xac1) return 1; else if (wc < 0xac6) return 0; else if (wc < 0xac7) return -1; else if (wc < 0xac9) return 0; else if (wc < 0xaca) return 1; else if (wc < 0xacb) return -1; else if (wc < 0xacd) return 1; else if (wc < 0xace) return 0; else if (wc < 0xad0) return -1; else if (wc < 0xad1) return 1; else if (wc < 0xae0) return -1; else if (wc < 0xae2) return 1; else if (wc < 0xae4) return 0; else if (wc < 0xae6) return -1; else if (wc < 0xaf2) return 1; else if (wc < 0xaf9) return -1; else if (wc < 0xafa) return 1; else if (wc < 0xb00) return 0; else if (wc < 0xb01) return -1; else if (wc < 0xb02) return 0; else if (wc < 0xb04) return 1; else if (wc < 0xb05) return -1; else if (wc < 0xb0d) return 1; else if (wc < 0xb0f) return -1; else if (wc < 0xb11) return 1; else if (wc < 0xb13) return -1; else if (wc < 0xb29) return 1; else if (wc < 0xb2a) return -1; else if (wc < 0xb31) return 1; else if (wc < 0xb32) return -1; else if (wc < 0xb34) return 1; else if (wc < 0xb35) return -1; else if (wc < 0xb3a) return 1; else if (wc < 0xb3c) return -1; else if (wc < 0xb3d) return 0; else if (wc < 0xb3f) return 1; else if (wc < 0xb40) return 0; else if (wc < 0xb41) return 1; else if (wc < 0xb45) return 0; else if (wc < 0xb47) return -1; else if (wc < 0xb49) return 1; else if (wc < 0xb4b) return -1; else if (wc < 0xb4d) return 1; else if (wc < 0xb4e) return 0; else if (wc < 0xb55) return -1; else if (wc < 0xb57) return 0; else if (wc < 0xb58) return 1; else if (wc < 0xb5c) return -1; else if (wc < 0xb5e) return 1; else if (wc < 0xb5f) return -1; else if (wc < 0xb62) return 1; else if (wc < 0xb64) return 0; else if (wc < 0xb66) return -1; else if (wc < 0xb78) return 1; else if (wc < 0xb82) return -1; else if (wc < 0xb83) return 0; else if (wc < 0xb84) return 1; else if (wc < 0xb85) return -1; else if (wc < 0xb8b) return 1; else if (wc < 0xb8e) return -1; else if (wc < 0xb91) return 1; else if (wc < 0xb92) return -1; else if (wc < 0xb96) return 1; else if (wc < 0xb99) return -1; else if (wc < 0xb9b) return 1; else if (wc < 0xb9c) return -1; else if (wc < 0xb9d) return 1; else if (wc < 0xb9e) return -1; else if (wc < 0xba0) return 1; else if (wc < 0xba3) return -1; else if (wc < 0xba5) return 1; else if (wc < 0xba8) return -1; else if (wc < 0xbab) return 1; else if (wc < 0xbae) return -1; else if (wc < 0xbba) return 1; else if (wc < 0xbbe) return -1; else if (wc < 0xbc0) return 1; else if (wc < 0xbc1) return 0; else if (wc < 0xbc3) return 1; else if (wc < 0xbc6) return -1; else if (wc < 0xbc9) return 1; else if (wc < 0xbca) return -1; else if (wc < 0xbcd) return 1; else if (wc < 0xbce) return 0; else if (wc < 0xbd0) return -1; else if (wc < 0xbd1) return 1; else if (wc < 0xbd7) return -1; else if (wc < 0xbd8) return 1; else if (wc < 0xbe6) return -1; else if (wc < 0xbfb) return 1; else if (wc < 0xc00) return -1; else if (wc < 0xc01) return 0; else if (wc < 0xc04) return 1; else if (wc < 0xc05) return 0; else if (wc < 0xc0d) return 1; else if (wc < 0xc0e) return -1; else if (wc < 0xc11) return 1; else if (wc < 0xc12) return -1; else if (wc < 0xc29) return 1; else if (wc < 0xc2a) return -1; else if (wc < 0xc3a) return 1; else if (wc < 0xc3c) return -1; else if (wc < 0xc3d) return 0; else if (wc < 0xc3e) return 1; else if (wc < 0xc41) return 0; else if (wc < 0xc45) return 1; else if (wc < 0xc46) return -1; else if (wc < 0xc49) return 0; else if (wc < 0xc4a) return -1; else if (wc < 0xc4e) return 0; else if (wc < 0xc55) return -1; else if (wc < 0xc57) return 0; else if (wc < 0xc58) return -1; else if (wc < 0xc5b) return 1; else if (wc < 0xc5d) return -1; else if (wc < 0xc5e) return 1; else if (wc < 0xc60) return -1; else if (wc < 0xc62) return 1; else if (wc < 0xc64) return 0; else if (wc < 0xc66) return -1; else if (wc < 0xc70) return 1; else if (wc < 0xc77) return -1; else if (wc < 0xc81) return 1; else if (wc < 0xc82) return 0; else if (wc < 0xc8d) return 1; else if (wc < 0xc8e) return -1; else if (wc < 0xc91) return 1; else if (wc < 0xc92) return -1; else if (wc < 0xca9) return 1; else if (wc < 0xcaa) return -1; else if (wc < 0xcb4) return 1; else if (wc < 0xcb5) return -1; else if (wc < 0xcba) return 1; else if (wc < 0xcbc) return -1; else if (wc < 0xcbd) return 0; else if (wc < 0xcbf) return 1; else if (wc < 0xcc0) return 0; else if (wc < 0xcc5) return 1; else if (wc < 0xcc6) return -1; else if (wc < 0xcc7) return 0; else if (wc < 0xcc9) return 1; else if (wc < 0xcca) return -1; else if (wc < 0xccc) return 1; else if (wc < 0xcce) return 0; else if (wc < 0xcd5) return -1; else if (wc < 0xcd7) return 1; else if (wc < 0xcdd) return -1; else if (wc < 0xcdf) return 1; else if (wc < 0xce0) return -1; else if (wc < 0xce2) return 1; else if (wc < 0xce4) return 0; else if (wc < 0xce6) return -1; else if (wc < 0xcf0) return 1; else if (wc < 0xcf1) return -1; else if (wc < 0xcf4) return 1; else if (wc < 0xd00) return -1; else if (wc < 0xd02) return 0; else if (wc < 0xd0d) return 1; else if (wc < 0xd0e) return -1; else if (wc < 0xd11) return 1; else if (wc < 0xd12) return -1; else if (wc < 0xd3b) return 1; else if (wc < 0xd3d) return 0; else if (wc < 0xd41) return 1; else if (wc < 0xd45) return 0; else if (wc < 0xd46) return -1; else if (wc < 0xd49) return 1; else if (wc < 0xd4a) return -1; else if (wc < 0xd4d) return 1; else if (wc < 0xd4e) return 0; else if (wc < 0xd50) return 1; else if (wc < 0xd54) return -1; else if (wc < 0xd62) return 1; else if (wc < 0xd64) return 0; else if (wc < 0xd66) return -1; else if (wc < 0xd80) return 1; else if (wc < 0xd81) return -1; else if (wc < 0xd82) return 0; else if (wc < 0xd84) return 1; else if (wc < 0xd85) return -1; else if (wc < 0xd97) return 1; else if (wc < 0xd9a) return -1; else if (wc < 0xdb2) return 1; else if (wc < 0xdb3) return -1; else if (wc < 0xdbc) return 1; else if (wc < 0xdbd) return -1; else if (wc < 0xdbe) return 1; else if (wc < 0xdc0) return -1; else if (wc < 0xdc7) return 1; else if (wc < 0xdca) return -1; else if (wc < 0xdcb) return 0; else if (wc < 0xdcf) return -1; else if (wc < 0xdd2) return 1; else if (wc < 0xdd5) return 0; else if (wc < 0xdd6) return -1; else if (wc < 0xdd7) return 0; else if (wc < 0xdd8) return -1; else if (wc < 0xde0) return 1; else if (wc < 0xde6) return -1; else if (wc < 0xdf0) return 1; else if (wc < 0xdf2) return -1; else if (wc < 0xdf5) return 1; else if (wc < 0xe01) return -1; else if (wc < 0xe31) return 1; else if (wc < 0xe32) return 0; else if (wc < 0xe34) return 1; else if (wc < 0xe3b) return 0; else if (wc < 0xe3f) return -1; else if (wc < 0xe47) return 1; else if (wc < 0xe4f) return 0; else if (wc < 0xe5c) return 1; else if (wc < 0xe81) return -1; else if (wc < 0xe83) return 1; else if (wc < 0xe84) return -1; else if (wc < 0xe85) return 1; else if (wc < 0xe86) return -1; else if (wc < 0xe8b) return 1; else if (wc < 0xe8c) return -1; else if (wc < 0xea4) return 1; else if (wc < 0xea5) return -1; else if (wc < 0xea6) return 1; else if (wc < 0xea7) return -1; else if (wc < 0xeb1) return 1; else if (wc < 0xeb2) return 0; else if (wc < 0xeb4) return 1; else if (wc < 0xebd) return 0; else if (wc < 0xebe) return 1; else if (wc < 0xec0) return -1; else if (wc < 0xec5) return 1; else if (wc < 0xec6) return -1; else if (wc < 0xec7) return 1; else if (wc < 0xec8) return -1; else if (wc < 0xecf) return 0; else if (wc < 0xed0) return -1; else if (wc < 0xeda) return 1; else if (wc < 0xedc) return -1; else if (wc < 0xee0) return 1; else if (wc < 0xf00) return -1; else if (wc < 0xf18) return 1; else if (wc < 0xf1a) return 0; else if (wc < 0xf35) return 1; else if (wc < 0xf36) return 0; else if (wc < 0xf37) return 1; else if (wc < 0xf38) return 0; else if (wc < 0xf39) return 1; else if (wc < 0xf3a) return 0; else if (wc < 0xf48) return 1; else if (wc < 0xf49) return -1; else if (wc < 0xf6d) return 1; else if (wc < 0xf71) return -1; else if (wc < 0xf7f) return 0; else if (wc < 0xf80) return 1; else if (wc < 0xf85) return 0; else if (wc < 0xf86) return 1; else if (wc < 0xf88) return 0; else if (wc < 0xf8d) return 1; else if (wc < 0xf98) return 0; else if (wc < 0xf99) return -1; else if (wc < 0xfbd) return 0; else if (wc < 0xfbe) return -1; else if (wc < 0xfc6) return 1; else if (wc < 0xfc7) return 0; else if (wc < 0xfcd) return 1; else if (wc < 0xfce) return -1; else if (wc < 0xfdb) return 1; else if (wc < 0x1000) return -1; else if (wc < 0x102d) return 1; else if (wc < 0x1031) return 0; else if (wc < 0x1032) return 1; else if (wc < 0x1038) return 0; else if (wc < 0x1039) return 1; else if (wc < 0x103b) return 0; else if (wc < 0x103d) return 1; else if (wc < 0x103f) return 0; else if (wc < 0x1058) return 1; else if (wc < 0x105a) return 0; else if (wc < 0x105e) return 1; else if (wc < 0x1061) return 0; else if (wc < 0x1071) return 1; else if (wc < 0x1075) return 0; else if (wc < 0x1082) return 1; else if (wc < 0x1083) return 0; else if (wc < 0x1085) return 1; else if (wc < 0x1087) return 0; else if (wc < 0x108d) return 1; else if (wc < 0x108e) return 0; else if (wc < 0x109d) return 1; else if (wc < 0x109e) return 0; else if (wc < 0x10c6) return 1; else if (wc < 0x10c7) return -1; else if (wc < 0x10c8) return 1; else if (wc < 0x10cd) return -1; else if (wc < 0x10ce) return 1; else if (wc < 0x10d0) return -1; else if (wc < 0x1100) return 1; else if (wc < 0x1160) return 2; else if (wc < 0x1200) return 0; else if (wc < 0x1249) return 1; else if (wc < 0x124a) return -1; else if (wc < 0x124e) return 1; else if (wc < 0x1250) return -1; else if (wc < 0x1257) return 1; else if (wc < 0x1258) return -1; else if (wc < 0x1259) return 1; else if (wc < 0x125a) return -1; else if (wc < 0x125e) return 1; else if (wc < 0x1260) return -1; else if (wc < 0x1289) return 1; else if (wc < 0x128a) return -1; else if (wc < 0x128e) return 1; else if (wc < 0x1290) return -1; else if (wc < 0x12b1) return 1; else if (wc < 0x12b2) return -1; else if (wc < 0x12b6) return 1; else if (wc < 0x12b8) return -1; else if (wc < 0x12bf) return 1; else if (wc < 0x12c0) return -1; else if (wc < 0x12c1) return 1; else if (wc < 0x12c2) return -1; else if (wc < 0x12c6) return 1; else if (wc < 0x12c8) return -1; else if (wc < 0x12d7) return 1; else if (wc < 0x12d8) return -1; else if (wc < 0x1311) return 1; else if (wc < 0x1312) return -1; else if (wc < 0x1316) return 1; else if (wc < 0x1318) return -1; else if (wc < 0x135b) return 1; else if (wc < 0x135d) return -1; else if (wc < 0x1360) return 0; else if (wc < 0x137d) return 1; else if (wc < 0x1380) return -1; else if (wc < 0x139a) return 1; else if (wc < 0x13a0) return -1; else if (wc < 0x13f6) return 1; else if (wc < 0x13f8) return -1; else if (wc < 0x13fe) return 1; else if (wc < 0x1400) return -1; else if (wc < 0x169d) return 1; else if (wc < 0x16a0) return -1; else if (wc < 0x16f9) return 1; else if (wc < 0x1700) return -1; else if (wc < 0x1712) return 1; else if (wc < 0x1715) return 0; else if (wc < 0x1716) return 1; else if (wc < 0x171f) return -1; else if (wc < 0x1732) return 1; else if (wc < 0x1734) return 0; else if (wc < 0x1737) return 1; else if (wc < 0x1740) return -1; else if (wc < 0x1752) return 1; else if (wc < 0x1754) return 0; else if (wc < 0x1760) return -1; else if (wc < 0x176d) return 1; else if (wc < 0x176e) return -1; else if (wc < 0x1771) return 1; else if (wc < 0x1772) return -1; else if (wc < 0x1774) return 0; else if (wc < 0x1780) return -1; else if (wc < 0x17b4) return 1; else if (wc < 0x17b6) return 0; else if (wc < 0x17b7) return 1; else if (wc < 0x17be) return 0; else if (wc < 0x17c6) return 1; else if (wc < 0x17c7) return 0; else if (wc < 0x17c9) return 1; else if (wc < 0x17d4) return 0; else if (wc < 0x17dd) return 1; else if (wc < 0x17de) return 0; else if (wc < 0x17e0) return -1; else if (wc < 0x17ea) return 1; else if (wc < 0x17f0) return -1; else if (wc < 0x17fa) return 1; else if (wc < 0x1800) return -1; else if (wc < 0x180b) return 1; else if (wc < 0x1810) return 0; else if (wc < 0x181a) return 1; else if (wc < 0x1820) return -1; else if (wc < 0x1879) return 1; else if (wc < 0x1880) return -1; else if (wc < 0x1885) return 1; else if (wc < 0x1887) return 0; else if (wc < 0x18a9) return 1; else if (wc < 0x18aa) return 0; else if (wc < 0x18ab) return 1; else if (wc < 0x18b0) return -1; else if (wc < 0x18f6) return 1; else if (wc < 0x1900) return -1; else if (wc < 0x191f) return 1; else if (wc < 0x1920) return -1; else if (wc < 0x1923) return 0; else if (wc < 0x1927) return 1; else if (wc < 0x1929) return 0; else if (wc < 0x192c) return 1; else if (wc < 0x1930) return -1; else if (wc < 0x1932) return 1; else if (wc < 0x1933) return 0; else if (wc < 0x1939) return 1; else if (wc < 0x193c) return 0; else if (wc < 0x1940) return -1; else if (wc < 0x1941) return 1; else if (wc < 0x1944) return -1; else if (wc < 0x196e) return 1; else if (wc < 0x1970) return -1; else if (wc < 0x1975) return 1; else if (wc < 0x1980) return -1; else if (wc < 0x19ac) return 1; else if (wc < 0x19b0) return -1; else if (wc < 0x19ca) return 1; else if (wc < 0x19d0) return -1; else if (wc < 0x19db) return 1; else if (wc < 0x19de) return -1; else if (wc < 0x1a17) return 1; else if (wc < 0x1a19) return 0; else if (wc < 0x1a1b) return 1; else if (wc < 0x1a1c) return 0; else if (wc < 0x1a1e) return -1; else if (wc < 0x1a56) return 1; else if (wc < 0x1a57) return 0; else if (wc < 0x1a58) return 1; else if (wc < 0x1a5f) return 0; else if (wc < 0x1a60) return -1; else if (wc < 0x1a61) return 0; else if (wc < 0x1a62) return 1; else if (wc < 0x1a63) return 0; else if (wc < 0x1a65) return 1; else if (wc < 0x1a6d) return 0; else if (wc < 0x1a73) return 1; else if (wc < 0x1a7d) return 0; else if (wc < 0x1a7f) return -1; else if (wc < 0x1a80) return 0; else if (wc < 0x1a8a) return 1; else if (wc < 0x1a90) return -1; else if (wc < 0x1a9a) return 1; else if (wc < 0x1aa0) return -1; else if (wc < 0x1aae) return 1; else if (wc < 0x1ab0) return -1; else if (wc < 0x1acf) return 0; else if (wc < 0x1b00) return -1; else if (wc < 0x1b04) return 0; else if (wc < 0x1b34) return 1; else if (wc < 0x1b35) return 0; else if (wc < 0x1b36) return 1; else if (wc < 0x1b3b) return 0; else if (wc < 0x1b3c) return 1; else if (wc < 0x1b3d) return 0; else if (wc < 0x1b42) return 1; else if (wc < 0x1b43) return 0; else if (wc < 0x1b4d) return 1; else if (wc < 0x1b50) return -1; else if (wc < 0x1b6b) return 1; else if (wc < 0x1b74) return 0; else if (wc < 0x1b7f) return 1; else if (wc < 0x1b80) return -1; else if (wc < 0x1b82) return 0; else if (wc < 0x1ba2) return 1; else if (wc < 0x1ba6) return 0; else if (wc < 0x1ba8) return 1; else if (wc < 0x1baa) return 0; else if (wc < 0x1bab) return 1; else if (wc < 0x1bae) return 0; else if (wc < 0x1be6) return 1; else if (wc < 0x1be7) return 0; else if (wc < 0x1be8) return 1; else if (wc < 0x1bea) return 0; else if (wc < 0x1bed) return 1; else if (wc < 0x1bee) return 0; else if (wc < 0x1bef) return 1; else if (wc < 0x1bf2) return 0; else if (wc < 0x1bf4) return 1; else if (wc < 0x1bfc) return -1; else if (wc < 0x1c2c) return 1; else if (wc < 0x1c34) return 0; else if (wc < 0x1c36) return 1; else if (wc < 0x1c38) return 0; else if (wc < 0x1c3b) return -1; else if (wc < 0x1c4a) return 1; else if (wc < 0x1c4d) return -1; else if (wc < 0x1c89) return 1; else if (wc < 0x1c90) return -1; else if (wc < 0x1cbb) return 1; else if (wc < 0x1cbd) return -1; else if (wc < 0x1cc8) return 1; else if (wc < 0x1cd0) return -1; else if (wc < 0x1cd3) return 0; else if (wc < 0x1cd4) return 1; else if (wc < 0x1ce1) return 0; else if (wc < 0x1ce2) return 1; else if (wc < 0x1ce9) return 0; else if (wc < 0x1ced) return 1; else if (wc < 0x1cee) return 0; else if (wc < 0x1cf4) return 1; else if (wc < 0x1cf5) return 0; else if (wc < 0x1cf8) return 1; else if (wc < 0x1cfa) return 0; else if (wc < 0x1cfb) return 1; else if (wc < 0x1d00) return -1; else if (wc < 0x1dc0) return 1; else if (wc < 0x1e00) return 0; else if (wc < 0x1f16) return 1; else if (wc < 0x1f18) return -1; else if (wc < 0x1f1e) return 1; else if (wc < 0x1f20) return -1; else if (wc < 0x1f46) return 1; else if (wc < 0x1f48) return -1; else if (wc < 0x1f4e) return 1; else if (wc < 0x1f50) return -1; else if (wc < 0x1f58) return 1; else if (wc < 0x1f59) return -1; else if (wc < 0x1f5a) return 1; else if (wc < 0x1f5b) return -1; else if (wc < 0x1f5c) return 1; else if (wc < 0x1f5d) return -1; else if (wc < 0x1f5e) return 1; else if (wc < 0x1f5f) return -1; else if (wc < 0x1f7e) return 1; else if (wc < 0x1f80) return -1; else if (wc < 0x1fb5) return 1; else if (wc < 0x1fb6) return -1; else if (wc < 0x1fc5) return 1; else if (wc < 0x1fc6) return -1; else if (wc < 0x1fd4) return 1; else if (wc < 0x1fd6) return -1; else if (wc < 0x1fdc) return 1; else if (wc < 0x1fdd) return -1; else if (wc < 0x1ff0) return 1; else if (wc < 0x1ff2) return -1; else if (wc < 0x1ff5) return 1; else if (wc < 0x1ff6) return -1; else if (wc < 0x1fff) return 1; else if (wc < 0x2000) return -1; else if (wc < 0x200b) return 1; else if (wc < 0x2010) return 0; else if (wc < 0x202a) return 1; else if (wc < 0x202f) return 0; else if (wc < 0x2060) return 1; else if (wc < 0x2065) return 0; else if (wc < 0x2066) return -1; else if (wc < 0x2070) return 0; else if (wc < 0x2072) return 1; else if (wc < 0x2074) return -1; else if (wc < 0x208f) return 1; else if (wc < 0x2090) return -1; else if (wc < 0x209d) return 1; else if (wc < 0x20a0) return -1; else if (wc < 0x20c1) return 1; else if (wc < 0x20d0) return -1; else if (wc < 0x20f1) return 0; else if (wc < 0x2100) return -1; else if (wc < 0x218c) return 1; else if (wc < 0x2190) return -1; else if (wc < 0x231a) return 1; else if (wc < 0x231c) return 2; else if (wc < 0x2329) return 1; else if (wc < 0x232b) return 2; else if (wc < 0x23e9) return 1; else if (wc < 0x23ed) return 2; else if (wc < 0x23f0) return 1; else if (wc < 0x23f1) return 2; else if (wc < 0x23f3) return 1; else if (wc < 0x23f4) return 2; else if (wc < 0x2427) return 1; else if (wc < 0x2440) return -1; else if (wc < 0x244b) return 1; else if (wc < 0x2460) return -1; else if (wc < 0x25fd) return 1; else if (wc < 0x25ff) return 2; else if (wc < 0x2614) return 1; else if (wc < 0x2616) return 2; else if (wc < 0x2648) return 1; else if (wc < 0x2654) return 2; else if (wc < 0x267f) return 1; else if (wc < 0x2680) return 2; else if (wc < 0x2693) return 1; else if (wc < 0x2694) return 2; else if (wc < 0x26a1) return 1; else if (wc < 0x26a2) return 2; else if (wc < 0x26aa) return 1; else if (wc < 0x26ac) return 2; else if (wc < 0x26bd) return 1; else if (wc < 0x26bf) return 2; else if (wc < 0x26c4) return 1; else if (wc < 0x26c6) return 2; else if (wc < 0x26ce) return 1; else if (wc < 0x26cf) return 2; else if (wc < 0x26d4) return 1; else if (wc < 0x26d5) return 2; else if (wc < 0x26ea) return 1; else if (wc < 0x26eb) return 2; else if (wc < 0x26f2) return 1; else if (wc < 0x26f4) return 2; else if (wc < 0x26f5) return 1; else if (wc < 0x26f6) return 2; else if (wc < 0x26fa) return 1; else if (wc < 0x26fb) return 2; else if (wc < 0x26fd) return 1; else if (wc < 0x26fe) return 2; else if (wc < 0x2705) return 1; else if (wc < 0x2706) return 2; else if (wc < 0x270a) return 1; else if (wc < 0x270c) return 2; else if (wc < 0x2728) return 1; else if (wc < 0x2729) return 2; else if (wc < 0x274c) return 1; else if (wc < 0x274d) return 2; else if (wc < 0x274e) return 1; else if (wc < 0x274f) return 2; else if (wc < 0x2753) return 1; else if (wc < 0x2756) return 2; else if (wc < 0x2757) return 1; else if (wc < 0x2758) return 2; else if (wc < 0x2795) return 1; else if (wc < 0x2798) return 2; else if (wc < 0x27b0) return 1; else if (wc < 0x27b1) return 2; else if (wc < 0x27bf) return 1; else if (wc < 0x27c0) return 2; else if (wc < 0x2b1b) return 1; else if (wc < 0x2b1d) return 2; else if (wc < 0x2b50) return 1; else if (wc < 0x2b51) return 2; else if (wc < 0x2b55) return 1; else if (wc < 0x2b56) return 2; else if (wc < 0x2b74) return 1; else if (wc < 0x2b76) return -1; else if (wc < 0x2b96) return 1; else if (wc < 0x2b97) return -1; else if (wc < 0x2cef) return 1; else if (wc < 0x2cf2) return 0; else if (wc < 0x2cf4) return 1; else if (wc < 0x2cf9) return -1; else if (wc < 0x2d26) return 1; else if (wc < 0x2d27) return -1; else if (wc < 0x2d28) return 1; else if (wc < 0x2d2d) return -1; else if (wc < 0x2d2e) return 1; else if (wc < 0x2d30) return -1; else if (wc < 0x2d68) return 1; else if (wc < 0x2d6f) return -1; else if (wc < 0x2d71) return 1; else if (wc < 0x2d7f) return -1; else if (wc < 0x2d80) return 0; else if (wc < 0x2d97) return 1; else if (wc < 0x2da0) return -1; else if (wc < 0x2da7) return 1; else if (wc < 0x2da8) return -1; else if (wc < 0x2daf) return 1; else if (wc < 0x2db0) return -1; else if (wc < 0x2db7) return 1; else if (wc < 0x2db8) return -1; else if (wc < 0x2dbf) return 1; else if (wc < 0x2dc0) return -1; else if (wc < 0x2dc7) return 1; else if (wc < 0x2dc8) return -1; else if (wc < 0x2dcf) return 1; else if (wc < 0x2dd0) return -1; else if (wc < 0x2dd7) return 1; else if (wc < 0x2dd8) return -1; else if (wc < 0x2ddf) return 1; else if (wc < 0x2de0) return -1; else if (wc < 0x2e00) return 0; else if (wc < 0x2e5e) return 1; else if (wc < 0x2e80) return -1; else if (wc < 0x2e9a) return 2; else if (wc < 0x2e9b) return -1; else if (wc < 0x2ef4) return 2; else if (wc < 0x2f00) return -1; else if (wc < 0x2fd6) return 2; else if (wc < 0x2ff0) return -1; else if (wc < 0x302a) return 2; else if (wc < 0x302e) return 0; else if (wc < 0x303f) return 2; else if (wc < 0x3040) return 1; else if (wc < 0x3041) return -1; else if (wc < 0x3097) return 2; else if (wc < 0x3099) return -1; else if (wc < 0x309b) return 0; else if (wc < 0x3100) return 2; else if (wc < 0x3105) return -1; else if (wc < 0x3130) return 2; else if (wc < 0x3131) return -1; else if (wc < 0x318f) return 2; else if (wc < 0x3190) return -1; else if (wc < 0x31e4) return 2; else if (wc < 0x31ef) return -1; else if (wc < 0x321f) return 2; else if (wc < 0x3220) return -1; else if (wc < 0x3248) return 2; else if (wc < 0x3250) return 1; else if (wc < 0x4dc0) return 2; else if (wc < 0x4e00) return 1; else if (wc < 0xa48d) return 2; else if (wc < 0xa490) return -1; else if (wc < 0xa4c7) return 2; else if (wc < 0xa4d0) return -1; else if (wc < 0xa62c) return 1; else if (wc < 0xa640) return -1; else if (wc < 0xa66f) return 1; else if (wc < 0xa673) return 0; else if (wc < 0xa674) return 1; else if (wc < 0xa67e) return 0; else if (wc < 0xa69e) return 1; else if (wc < 0xa6a0) return 0; else if (wc < 0xa6f0) return 1; else if (wc < 0xa6f2) return 0; else if (wc < 0xa6f8) return 1; else if (wc < 0xa700) return -1; else if (wc < 0xa7cb) return 1; else if (wc < 0xa7d0) return -1; else if (wc < 0xa7d2) return 1; else if (wc < 0xa7d3) return -1; else if (wc < 0xa7d4) return 1; else if (wc < 0xa7d5) return -1; else if (wc < 0xa7da) return 1; else if (wc < 0xa7f2) return -1; else if (wc < 0xa802) return 1; else if (wc < 0xa803) return 0; else if (wc < 0xa806) return 1; else if (wc < 0xa807) return 0; else if (wc < 0xa80b) return 1; else if (wc < 0xa80c) return 0; else if (wc < 0xa825) return 1; else if (wc < 0xa827) return 0; else if (wc < 0xa82c) return 1; else if (wc < 0xa82d) return 0; else if (wc < 0xa830) return -1; else if (wc < 0xa83a) return 1; else if (wc < 0xa840) return -1; else if (wc < 0xa878) return 1; else if (wc < 0xa880) return -1; else if (wc < 0xa8c4) return 1; else if (wc < 0xa8c6) return 0; else if (wc < 0xa8ce) return -1; else if (wc < 0xa8da) return 1; else if (wc < 0xa8e0) return -1; else if (wc < 0xa8f2) return 0; else if (wc < 0xa8ff) return 1; else if (wc < 0xa900) return 0; else if (wc < 0xa926) return 1; else if (wc < 0xa92e) return 0; else if (wc < 0xa947) return 1; else if (wc < 0xa952) return 0; else if (wc < 0xa954) return 1; else if (wc < 0xa95f) return -1; else if (wc < 0xa960) return 1; else if (wc < 0xa97d) return 2; else if (wc < 0xa980) return -1; else if (wc < 0xa983) return 0; else if (wc < 0xa9b3) return 1; else if (wc < 0xa9b4) return 0; else if (wc < 0xa9b6) return 1; else if (wc < 0xa9ba) return 0; else if (wc < 0xa9bc) return 1; else if (wc < 0xa9be) return 0; else if (wc < 0xa9ce) return 1; else if (wc < 0xa9cf) return -1; else if (wc < 0xa9da) return 1; else if (wc < 0xa9de) return -1; else if (wc < 0xa9e5) return 1; else if (wc < 0xa9e6) return 0; else if (wc < 0xa9ff) return 1; else if (wc < 0xaa00) return -1; else if (wc < 0xaa29) return 1; else if (wc < 0xaa2f) return 0; else if (wc < 0xaa31) return 1; else if (wc < 0xaa33) return 0; else if (wc < 0xaa35) return 1; else if (wc < 0xaa37) return 0; else if (wc < 0xaa40) return -1; else if (wc < 0xaa43) return 1; else if (wc < 0xaa44) return 0; else if (wc < 0xaa4c) return 1; else if (wc < 0xaa4d) return 0; else if (wc < 0xaa4e) return 1; else if (wc < 0xaa50) return -1; else if (wc < 0xaa5a) return 1; else if (wc < 0xaa5c) return -1; else if (wc < 0xaa7c) return 1; else if (wc < 0xaa7d) return 0; else if (wc < 0xaab0) return 1; else if (wc < 0xaab1) return 0; else if (wc < 0xaab2) return 1; else if (wc < 0xaab5) return 0; else if (wc < 0xaab7) return 1; else if (wc < 0xaab9) return 0; else if (wc < 0xaabe) return 1; else if (wc < 0xaac0) return 0; else if (wc < 0xaac1) return 1; else if (wc < 0xaac2) return 0; else if (wc < 0xaac3) return 1; else if (wc < 0xaadb) return -1; else if (wc < 0xaaec) return 1; else if (wc < 0xaaee) return 0; else if (wc < 0xaaf6) return 1; else if (wc < 0xaaf7) return 0; else if (wc < 0xab01) return -1; else if (wc < 0xab07) return 1; else if (wc < 0xab09) return -1; else if (wc < 0xab0f) return 1; else if (wc < 0xab11) return -1; else if (wc < 0xab17) return 1; else if (wc < 0xab20) return -1; else if (wc < 0xab27) return 1; else if (wc < 0xab28) return -1; else if (wc < 0xab2f) return 1; else if (wc < 0xab30) return -1; else if (wc < 0xab6c) return 1; else if (wc < 0xab70) return -1; else if (wc < 0xabe5) return 1; else if (wc < 0xabe6) return 0; else if (wc < 0xabe8) return 1; else if (wc < 0xabe9) return 0; else if (wc < 0xabed) return 1; else if (wc < 0xabee) return 0; else if (wc < 0xabf0) return -1; else if (wc < 0xabfa) return 1; else if (wc < 0xac00) return -1; else if (wc < 0xd7a4) return 2; else if (wc < 0xd7b0) return -1; else if (wc < 0xd7c7) return 1; else if (wc < 0xd7cb) return -1; else if (wc < 0xd7fc) return 1; else if (wc < 0xe000) return -1; else if (wc < 0xf900) return 1; else if (wc < 0xfb00) return 2; else if (wc < 0xfb07) return 1; else if (wc < 0xfb13) return -1; else if (wc < 0xfb18) return 1; else if (wc < 0xfb1d) return -1; else if (wc < 0xfb1e) return 1; else if (wc < 0xfb1f) return 0; else if (wc < 0xfb37) return 1; else if (wc < 0xfb38) return -1; else if (wc < 0xfb3d) return 1; else if (wc < 0xfb3e) return -1; else if (wc < 0xfb3f) return 1; else if (wc < 0xfb40) return -1; else if (wc < 0xfb42) return 1; else if (wc < 0xfb43) return -1; else if (wc < 0xfb45) return 1; else if (wc < 0xfb46) return -1; else if (wc < 0xfbc3) return 1; else if (wc < 0xfbd3) return -1; else if (wc < 0xfd90) return 1; else if (wc < 0xfd92) return -1; else if (wc < 0xfdc8) return 1; else if (wc < 0xfdcf) return -1; else if (wc < 0xfdd0) return 1; else if (wc < 0xfdf0) return -1; else if (wc < 0xfe00) return 1; else if (wc < 0xfe10) return 0; else if (wc < 0xfe1a) return 2; else if (wc < 0xfe20) return -1; else if (wc < 0xfe30) return 0; else if (wc < 0xfe53) return 2; else if (wc < 0xfe54) return -1; else if (wc < 0xfe67) return 2; else if (wc < 0xfe68) return -1; else if (wc < 0xfe6c) return 2; else if (wc < 0xfe70) return -1; else if (wc < 0xfe75) return 1; else if (wc < 0xfe76) return -1; else if (wc < 0xfefd) return 1; else if (wc < 0xfeff) return -1; else if (wc < 0xff00) return 0; else if (wc < 0xff01) return -1; else if (wc < 0xff61) return 2; else if (wc < 0xffbf) return 1; else if (wc < 0xffc2) return -1; else if (wc < 0xffc8) return 1; else if (wc < 0xffca) return -1; else if (wc < 0xffd0) return 1; else if (wc < 0xffd2) return -1; else if (wc < 0xffd8) return 1; else if (wc < 0xffda) return -1; else if (wc < 0xffdd) return 1; else if (wc < 0xffe0) return -1; else if (wc < 0xffe7) return 2; else if (wc < 0xffe8) return -1; else if (wc < 0xffef) return 1; else if (wc < 0xfff9) return -1; else if (wc < 0xfffc) return 0; else if (wc < 0xfffe) return 1; else if (wc < 0x10000) return -1; else if (wc < 0x1000c) return 1; else if (wc < 0x1000d) return -1; else if (wc < 0x10027) return 1; else if (wc < 0x10028) return -1; else if (wc < 0x1003b) return 1; else if (wc < 0x1003c) return -1; else if (wc < 0x1003e) return 1; else if (wc < 0x1003f) return -1; else if (wc < 0x1004e) return 1; else if (wc < 0x10050) return -1; else if (wc < 0x1005e) return 1; else if (wc < 0x10080) return -1; else if (wc < 0x100fb) return 1; else if (wc < 0x10100) return -1; else if (wc < 0x10103) return 1; else if (wc < 0x10107) return -1; else if (wc < 0x10134) return 1; else if (wc < 0x10137) return -1; else if (wc < 0x1018f) return 1; else if (wc < 0x10190) return -1; else if (wc < 0x1019d) return 1; else if (wc < 0x101a0) return -1; else if (wc < 0x101a1) return 1; else if (wc < 0x101d0) return -1; else if (wc < 0x101fd) return 1; else if (wc < 0x101fe) return 0; else if (wc < 0x10280) return -1; else if (wc < 0x1029d) return 1; else if (wc < 0x102a0) return -1; else if (wc < 0x102d1) return 1; else if (wc < 0x102e0) return -1; else if (wc < 0x102e1) return 0; else if (wc < 0x102fc) return 1; else if (wc < 0x10300) return -1; else if (wc < 0x10324) return 1; else if (wc < 0x1032d) return -1; else if (wc < 0x1034b) return 1; else if (wc < 0x10350) return -1; else if (wc < 0x10376) return 1; else if (wc < 0x1037b) return 0; else if (wc < 0x10380) return -1; else if (wc < 0x1039e) return 1; else if (wc < 0x1039f) return -1; else if (wc < 0x103c4) return 1; else if (wc < 0x103c8) return -1; else if (wc < 0x103d6) return 1; else if (wc < 0x10400) return -1; else if (wc < 0x1049e) return 1; else if (wc < 0x104a0) return -1; else if (wc < 0x104aa) return 1; else if (wc < 0x104b0) return -1; else if (wc < 0x104d4) return 1; else if (wc < 0x104d8) return -1; else if (wc < 0x104fc) return 1; else if (wc < 0x10500) return -1; else if (wc < 0x10528) return 1; else if (wc < 0x10530) return -1; else if (wc < 0x10564) return 1; else if (wc < 0x1056f) return -1; else if (wc < 0x1057b) return 1; else if (wc < 0x1057c) return -1; else if (wc < 0x1058b) return 1; else if (wc < 0x1058c) return -1; else if (wc < 0x10593) return 1; else if (wc < 0x10594) return -1; else if (wc < 0x10596) return 1; else if (wc < 0x10597) return -1; else if (wc < 0x105a2) return 1; else if (wc < 0x105a3) return -1; else if (wc < 0x105b2) return 1; else if (wc < 0x105b3) return -1; else if (wc < 0x105ba) return 1; else if (wc < 0x105bb) return -1; else if (wc < 0x105bd) return 1; else if (wc < 0x10600) return -1; else if (wc < 0x10737) return 1; else if (wc < 0x10740) return -1; else if (wc < 0x10756) return 1; else if (wc < 0x10760) return -1; else if (wc < 0x10768) return 1; else if (wc < 0x10780) return -1; else if (wc < 0x10786) return 1; else if (wc < 0x10787) return -1; else if (wc < 0x107b1) return 1; else if (wc < 0x107b2) return -1; else if (wc < 0x107bb) return 1; else if (wc < 0x10800) return -1; else if (wc < 0x10806) return 1; else if (wc < 0x10808) return -1; else if (wc < 0x10809) return 1; else if (wc < 0x1080a) return -1; else if (wc < 0x10836) return 1; else if (wc < 0x10837) return -1; else if (wc < 0x10839) return 1; else if (wc < 0x1083c) return -1; else if (wc < 0x1083d) return 1; else if (wc < 0x1083f) return -1; else if (wc < 0x10856) return 1; else if (wc < 0x10857) return -1; else if (wc < 0x1089f) return 1; else if (wc < 0x108a7) return -1; else if (wc < 0x108b0) return 1; else if (wc < 0x108e0) return -1; else if (wc < 0x108f3) return 1; else if (wc < 0x108f4) return -1; else if (wc < 0x108f6) return 1; else if (wc < 0x108fb) return -1; else if (wc < 0x1091c) return 1; else if (wc < 0x1091f) return -1; else if (wc < 0x1093a) return 1; else if (wc < 0x1093f) return -1; else if (wc < 0x10940) return 1; else if (wc < 0x10980) return -1; else if (wc < 0x109b8) return 1; else if (wc < 0x109bc) return -1; else if (wc < 0x109d0) return 1; else if (wc < 0x109d2) return -1; else if (wc < 0x10a01) return 1; else if (wc < 0x10a04) return 0; else if (wc < 0x10a05) return -1; else if (wc < 0x10a07) return 0; else if (wc < 0x10a0c) return -1; else if (wc < 0x10a10) return 0; else if (wc < 0x10a14) return 1; else if (wc < 0x10a15) return -1; else if (wc < 0x10a18) return 1; else if (wc < 0x10a19) return -1; else if (wc < 0x10a36) return 1; else if (wc < 0x10a38) return -1; else if (wc < 0x10a3b) return 0; else if (wc < 0x10a3f) return -1; else if (wc < 0x10a40) return 0; else if (wc < 0x10a49) return 1; else if (wc < 0x10a50) return -1; else if (wc < 0x10a59) return 1; else if (wc < 0x10a60) return -1; else if (wc < 0x10aa0) return 1; else if (wc < 0x10ac0) return -1; else if (wc < 0x10ae5) return 1; else if (wc < 0x10ae7) return 0; else if (wc < 0x10aeb) return -1; else if (wc < 0x10af7) return 1; else if (wc < 0x10b00) return -1; else if (wc < 0x10b36) return 1; else if (wc < 0x10b39) return -1; else if (wc < 0x10b56) return 1; else if (wc < 0x10b58) return -1; else if (wc < 0x10b73) return 1; else if (wc < 0x10b78) return -1; else if (wc < 0x10b92) return 1; else if (wc < 0x10b99) return -1; else if (wc < 0x10b9d) return 1; else if (wc < 0x10ba9) return -1; else if (wc < 0x10bb0) return 1; else if (wc < 0x10c00) return -1; else if (wc < 0x10c49) return 1; else if (wc < 0x10c80) return -1; else if (wc < 0x10cb3) return 1; else if (wc < 0x10cc0) return -1; else if (wc < 0x10cf3) return 1; else if (wc < 0x10cfa) return -1; else if (wc < 0x10d24) return 1; else if (wc < 0x10d28) return 0; else if (wc < 0x10d30) return -1; else if (wc < 0x10d3a) return 1; else if (wc < 0x10e60) return -1; else if (wc < 0x10e7f) return 1; else if (wc < 0x10e80) return -1; else if (wc < 0x10eaa) return 1; else if (wc < 0x10eab) return -1; else if (wc < 0x10ead) return 0; else if (wc < 0x10eae) return 1; else if (wc < 0x10eb0) return -1; else if (wc < 0x10eb2) return 1; else if (wc < 0x10efd) return -1; else if (wc < 0x10f00) return 0; else if (wc < 0x10f28) return 1; else if (wc < 0x10f30) return -1; else if (wc < 0x10f46) return 1; else if (wc < 0x10f51) return 0; else if (wc < 0x10f5a) return 1; else if (wc < 0x10f70) return -1; else if (wc < 0x10f82) return 1; else if (wc < 0x10f86) return 0; else if (wc < 0x10f8a) return 1; else if (wc < 0x10fb0) return -1; else if (wc < 0x10fcc) return 1; else if (wc < 0x10fe0) return -1; else if (wc < 0x10ff7) return 1; else if (wc < 0x11000) return -1; else if (wc < 0x11001) return 1; else if (wc < 0x11002) return 0; else if (wc < 0x11038) return 1; else if (wc < 0x11047) return 0; else if (wc < 0x1104e) return 1; else if (wc < 0x11052) return -1; else if (wc < 0x11070) return 1; else if (wc < 0x11071) return 0; else if (wc < 0x11073) return 1; else if (wc < 0x11075) return 0; else if (wc < 0x11076) return 1; else if (wc < 0x1107f) return -1; else if (wc < 0x11082) return 0; else if (wc < 0x110b3) return 1; else if (wc < 0x110b7) return 0; else if (wc < 0x110b9) return 1; else if (wc < 0x110bb) return 0; else if (wc < 0x110bd) return 1; else if (wc < 0x110be) return 0; else if (wc < 0x110c2) return 1; else if (wc < 0x110c3) return 0; else if (wc < 0x110cd) return -1; else if (wc < 0x110ce) return 0; else if (wc < 0x110d0) return -1; else if (wc < 0x110e9) return 1; else if (wc < 0x110f0) return -1; else if (wc < 0x110fa) return 1; else if (wc < 0x11100) return -1; else if (wc < 0x11103) return 0; else if (wc < 0x11127) return 1; else if (wc < 0x1112c) return 0; else if (wc < 0x1112d) return 1; else if (wc < 0x11135) return 0; else if (wc < 0x11136) return -1; else if (wc < 0x11148) return 1; else if (wc < 0x11150) return -1; else if (wc < 0x11173) return 1; else if (wc < 0x11174) return 0; else if (wc < 0x11177) return 1; else if (wc < 0x11180) return -1; else if (wc < 0x11182) return 0; else if (wc < 0x111b6) return 1; else if (wc < 0x111bf) return 0; else if (wc < 0x111c9) return 1; else if (wc < 0x111cd) return 0; else if (wc < 0x111cf) return 1; else if (wc < 0x111d0) return 0; else if (wc < 0x111e0) return 1; else if (wc < 0x111e1) return -1; else if (wc < 0x111f5) return 1; else if (wc < 0x11200) return -1; else if (wc < 0x11212) return 1; else if (wc < 0x11213) return -1; else if (wc < 0x1122f) return 1; else if (wc < 0x11232) return 0; else if (wc < 0x11234) return 1; else if (wc < 0x11235) return 0; else if (wc < 0x11236) return 1; else if (wc < 0x11238) return 0; else if (wc < 0x1123e) return 1; else if (wc < 0x1123f) return 0; else if (wc < 0x11241) return 1; else if (wc < 0x11242) return 0; else if (wc < 0x11280) return -1; else if (wc < 0x11287) return 1; else if (wc < 0x11288) return -1; else if (wc < 0x11289) return 1; else if (wc < 0x1128a) return -1; else if (wc < 0x1128e) return 1; else if (wc < 0x1128f) return -1; else if (wc < 0x1129e) return 1; else if (wc < 0x1129f) return -1; else if (wc < 0x112aa) return 1; else if (wc < 0x112b0) return -1; else if (wc < 0x112df) return 1; else if (wc < 0x112e0) return 0; else if (wc < 0x112e3) return 1; else if (wc < 0x112eb) return 0; else if (wc < 0x112f0) return -1; else if (wc < 0x112fa) return 1; else if (wc < 0x11300) return -1; else if (wc < 0x11302) return 0; else if (wc < 0x11304) return 1; else if (wc < 0x11305) return -1; else if (wc < 0x1130d) return 1; else if (wc < 0x1130f) return -1; else if (wc < 0x11311) return 1; else if (wc < 0x11313) return -1; else if (wc < 0x11329) return 1; else if (wc < 0x1132a) return -1; else if (wc < 0x11331) return 1; else if (wc < 0x11332) return -1; else if (wc < 0x11334) return 1; else if (wc < 0x11335) return -1; else if (wc < 0x1133a) return 1; else if (wc < 0x1133b) return -1; else if (wc < 0x1133d) return 0; else if (wc < 0x11340) return 1; else if (wc < 0x11341) return 0; else if (wc < 0x11345) return 1; else if (wc < 0x11347) return -1; else if (wc < 0x11349) return 1; else if (wc < 0x1134b) return -1; else if (wc < 0x1134e) return 1; else if (wc < 0x11350) return -1; else if (wc < 0x11351) return 1; else if (wc < 0x11357) return -1; else if (wc < 0x11358) return 1; else if (wc < 0x1135d) return -1; else if (wc < 0x11364) return 1; else if (wc < 0x11366) return -1; else if (wc < 0x1136d) return 0; else if (wc < 0x11370) return -1; else if (wc < 0x11375) return 0; else if (wc < 0x11400) return -1; else if (wc < 0x11438) return 1; else if (wc < 0x11440) return 0; else if (wc < 0x11442) return 1; else if (wc < 0x11445) return 0; else if (wc < 0x11446) return 1; else if (wc < 0x11447) return 0; else if (wc < 0x1145c) return 1; else if (wc < 0x1145d) return -1; else if (wc < 0x1145e) return 1; else if (wc < 0x1145f) return 0; else if (wc < 0x11462) return 1; else if (wc < 0x11480) return -1; else if (wc < 0x114b3) return 1; else if (wc < 0x114b9) return 0; else if (wc < 0x114ba) return 1; else if (wc < 0x114bb) return 0; else if (wc < 0x114bf) return 1; else if (wc < 0x114c1) return 0; else if (wc < 0x114c2) return 1; else if (wc < 0x114c4) return 0; else if (wc < 0x114c8) return 1; else if (wc < 0x114d0) return -1; else if (wc < 0x114da) return 1; else if (wc < 0x11580) return -1; else if (wc < 0x115b2) return 1; else if (wc < 0x115b6) return 0; else if (wc < 0x115b8) return -1; else if (wc < 0x115bc) return 1; else if (wc < 0x115be) return 0; else if (wc < 0x115bf) return 1; else if (wc < 0x115c1) return 0; else if (wc < 0x115dc) return 1; else if (wc < 0x115de) return 0; else if (wc < 0x11600) return -1; else if (wc < 0x11633) return 1; else if (wc < 0x1163b) return 0; else if (wc < 0x1163d) return 1; else if (wc < 0x1163e) return 0; else if (wc < 0x1163f) return 1; else if (wc < 0x11641) return 0; else if (wc < 0x11645) return 1; else if (wc < 0x11650) return -1; else if (wc < 0x1165a) return 1; else if (wc < 0x11660) return -1; else if (wc < 0x1166d) return 1; else if (wc < 0x11680) return -1; else if (wc < 0x116ab) return 1; else if (wc < 0x116ac) return 0; else if (wc < 0x116ad) return 1; else if (wc < 0x116ae) return 0; else if (wc < 0x116b0) return 1; else if (wc < 0x116b6) return 0; else if (wc < 0x116b7) return 1; else if (wc < 0x116b8) return 0; else if (wc < 0x116ba) return 1; else if (wc < 0x116c0) return -1; else if (wc < 0x116ca) return 1; else if (wc < 0x11700) return -1; else if (wc < 0x1171b) return 1; else if (wc < 0x1171d) return -1; else if (wc < 0x11720) return 0; else if (wc < 0x11722) return 1; else if (wc < 0x11726) return 0; else if (wc < 0x11727) return 1; else if (wc < 0x1172c) return 0; else if (wc < 0x11730) return -1; else if (wc < 0x11747) return 1; else if (wc < 0x11800) return -1; else if (wc < 0x1182f) return 1; else if (wc < 0x11838) return 0; else if (wc < 0x11839) return 1; else if (wc < 0x1183b) return 0; else if (wc < 0x1183c) return 1; else if (wc < 0x118a0) return -1; else if (wc < 0x118f3) return 1; else if (wc < 0x118ff) return -1; else if (wc < 0x11907) return 1; else if (wc < 0x11909) return -1; else if (wc < 0x1190a) return 1; else if (wc < 0x1190c) return -1; else if (wc < 0x11914) return 1; else if (wc < 0x11915) return -1; else if (wc < 0x11917) return 1; else if (wc < 0x11918) return -1; else if (wc < 0x11936) return 1; else if (wc < 0x11937) return -1; else if (wc < 0x11939) return 1; else if (wc < 0x1193b) return -1; else if (wc < 0x1193d) return 0; else if (wc < 0x1193e) return 1; else if (wc < 0x1193f) return 0; else if (wc < 0x11943) return 1; else if (wc < 0x11944) return 0; else if (wc < 0x11947) return 1; else if (wc < 0x11950) return -1; else if (wc < 0x1195a) return 1; else if (wc < 0x119a0) return -1; else if (wc < 0x119a8) return 1; else if (wc < 0x119aa) return -1; else if (wc < 0x119d4) return 1; else if (wc < 0x119d8) return 0; else if (wc < 0x119da) return -1; else if (wc < 0x119dc) return 0; else if (wc < 0x119e0) return 1; else if (wc < 0x119e1) return 0; else if (wc < 0x119e5) return 1; else if (wc < 0x11a00) return -1; else if (wc < 0x11a01) return 1; else if (wc < 0x11a0b) return 0; else if (wc < 0x11a33) return 1; else if (wc < 0x11a39) return 0; else if (wc < 0x11a3b) return 1; else if (wc < 0x11a3f) return 0; else if (wc < 0x11a47) return 1; else if (wc < 0x11a48) return 0; else if (wc < 0x11a50) return -1; else if (wc < 0x11a51) return 1; else if (wc < 0x11a57) return 0; else if (wc < 0x11a59) return 1; else if (wc < 0x11a5c) return 0; else if (wc < 0x11a8a) return 1; else if (wc < 0x11a97) return 0; else if (wc < 0x11a98) return 1; else if (wc < 0x11a9a) return 0; else if (wc < 0x11aa3) return 1; else if (wc < 0x11ab0) return -1; else if (wc < 0x11af9) return 1; else if (wc < 0x11b00) return -1; else if (wc < 0x11b0a) return 1; else if (wc < 0x11c00) return -1; else if (wc < 0x11c09) return 1; else if (wc < 0x11c0a) return -1; else if (wc < 0x11c30) return 1; else if (wc < 0x11c37) return 0; else if (wc < 0x11c38) return -1; else if (wc < 0x11c3e) return 0; else if (wc < 0x11c3f) return 1; else if (wc < 0x11c40) return 0; else if (wc < 0x11c46) return 1; else if (wc < 0x11c50) return -1; else if (wc < 0x11c6d) return 1; else if (wc < 0x11c70) return -1; else if (wc < 0x11c90) return 1; else if (wc < 0x11c92) return -1; else if (wc < 0x11ca8) return 0; else if (wc < 0x11ca9) return -1; else if (wc < 0x11caa) return 1; else if (wc < 0x11cb1) return 0; else if (wc < 0x11cb2) return 1; else if (wc < 0x11cb4) return 0; else if (wc < 0x11cb5) return 1; else if (wc < 0x11cb7) return 0; else if (wc < 0x11d00) return -1; else if (wc < 0x11d07) return 1; else if (wc < 0x11d08) return -1; else if (wc < 0x11d0a) return 1; else if (wc < 0x11d0b) return -1; else if (wc < 0x11d31) return 1; else if (wc < 0x11d37) return 0; else if (wc < 0x11d3a) return -1; else if (wc < 0x11d3b) return 0; else if (wc < 0x11d3c) return -1; else if (wc < 0x11d3e) return 0; else if (wc < 0x11d3f) return -1; else if (wc < 0x11d46) return 0; else if (wc < 0x11d47) return 1; else if (wc < 0x11d48) return 0; else if (wc < 0x11d50) return -1; else if (wc < 0x11d5a) return 1; else if (wc < 0x11d60) return -1; else if (wc < 0x11d66) return 1; else if (wc < 0x11d67) return -1; else if (wc < 0x11d69) return 1; else if (wc < 0x11d6a) return -1; else if (wc < 0x11d8f) return 1; else if (wc < 0x11d90) return -1; else if (wc < 0x11d92) return 0; else if (wc < 0x11d93) return -1; else if (wc < 0x11d95) return 1; else if (wc < 0x11d96) return 0; else if (wc < 0x11d97) return 1; else if (wc < 0x11d98) return 0; else if (wc < 0x11d99) return 1; else if (wc < 0x11da0) return -1; else if (wc < 0x11daa) return 1; else if (wc < 0x11ee0) return -1; else if (wc < 0x11ef3) return 1; else if (wc < 0x11ef5) return 0; else if (wc < 0x11ef9) return 1; else if (wc < 0x11f00) return -1; else if (wc < 0x11f02) return 0; else if (wc < 0x11f11) return 1; else if (wc < 0x11f12) return -1; else if (wc < 0x11f36) return 1; else if (wc < 0x11f3b) return 0; else if (wc < 0x11f3e) return -1; else if (wc < 0x11f40) return 1; else if (wc < 0x11f41) return 0; else if (wc < 0x11f42) return 1; else if (wc < 0x11f43) return 0; else if (wc < 0x11f5a) return 1; else if (wc < 0x11fb0) return -1; else if (wc < 0x11fb1) return 1; else if (wc < 0x11fc0) return -1; else if (wc < 0x11ff2) return 1; else if (wc < 0x11fff) return -1; else if (wc < 0x1239a) return 1; else if (wc < 0x12400) return -1; else if (wc < 0x1246f) return 1; else if (wc < 0x12470) return -1; else if (wc < 0x12475) return 1; else if (wc < 0x12480) return -1; else if (wc < 0x12544) return 1; else if (wc < 0x12f90) return -1; else if (wc < 0x12ff3) return 1; else if (wc < 0x13000) return -1; else if (wc < 0x13430) return 1; else if (wc < 0x13441) return 0; else if (wc < 0x13447) return 1; else if (wc < 0x13456) return 0; else if (wc < 0x14400) return -1; else if (wc < 0x14647) return 1; else if (wc < 0x16800) return -1; else if (wc < 0x16a39) return 1; else if (wc < 0x16a40) return -1; else if (wc < 0x16a5f) return 1; else if (wc < 0x16a60) return -1; else if (wc < 0x16a6a) return 1; else if (wc < 0x16a6e) return -1; else if (wc < 0x16abf) return 1; else if (wc < 0x16ac0) return -1; else if (wc < 0x16aca) return 1; else if (wc < 0x16ad0) return -1; else if (wc < 0x16aee) return 1; else if (wc < 0x16af0) return -1; else if (wc < 0x16af5) return 0; else if (wc < 0x16af6) return 1; else if (wc < 0x16b00) return -1; else if (wc < 0x16b30) return 1; else if (wc < 0x16b37) return 0; else if (wc < 0x16b46) return 1; else if (wc < 0x16b50) return -1; else if (wc < 0x16b5a) return 1; else if (wc < 0x16b5b) return -1; else if (wc < 0x16b62) return 1; else if (wc < 0x16b63) return -1; else if (wc < 0x16b78) return 1; else if (wc < 0x16b7d) return -1; else if (wc < 0x16b90) return 1; else if (wc < 0x16e40) return -1; else if (wc < 0x16e9b) return 1; else if (wc < 0x16f00) return -1; else if (wc < 0x16f4b) return 1; else if (wc < 0x16f4f) return -1; else if (wc < 0x16f50) return 0; else if (wc < 0x16f88) return 1; else if (wc < 0x16f8f) return -1; else if (wc < 0x16f93) return 0; else if (wc < 0x16fa0) return 1; else if (wc < 0x16fe0) return -1; else if (wc < 0x16fe4) return 2; else if (wc < 0x16fe5) return 0; else if (wc < 0x16ff0) return -1; else if (wc < 0x16ff2) return 2; else if (wc < 0x17000) return -1; else if (wc < 0x187f8) return 2; else if (wc < 0x18800) return -1; else if (wc < 0x18cd6) return 2; else if (wc < 0x18d00) return -1; else if (wc < 0x18d09) return 2; else if (wc < 0x1aff0) return -1; else if (wc < 0x1aff4) return 2; else if (wc < 0x1aff5) return -1; else if (wc < 0x1affc) return 2; else if (wc < 0x1affd) return -1; else if (wc < 0x1afff) return 2; else if (wc < 0x1b000) return -1; else if (wc < 0x1b123) return 2; else if (wc < 0x1b132) return -1; else if (wc < 0x1b133) return 2; else if (wc < 0x1b150) return -1; else if (wc < 0x1b153) return 2; else if (wc < 0x1b155) return -1; else if (wc < 0x1b156) return 2; else if (wc < 0x1b164) return -1; else if (wc < 0x1b168) return 2; else if (wc < 0x1b170) return -1; else if (wc < 0x1b2fc) return 2; else if (wc < 0x1bc00) return -1; else if (wc < 0x1bc6b) return 1; else if (wc < 0x1bc70) return -1; else if (wc < 0x1bc7d) return 1; else if (wc < 0x1bc80) return -1; else if (wc < 0x1bc89) return 1; else if (wc < 0x1bc90) return -1; else if (wc < 0x1bc9a) return 1; else if (wc < 0x1bc9c) return -1; else if (wc < 0x1bc9d) return 1; else if (wc < 0x1bc9f) return 0; else if (wc < 0x1bca0) return 1; else if (wc < 0x1bca4) return 0; else if (wc < 0x1cf00) return -1; else if (wc < 0x1cf2e) return 0; else if (wc < 0x1cf30) return -1; else if (wc < 0x1cf47) return 0; else if (wc < 0x1cf50) return -1; else if (wc < 0x1cfc4) return 1; else if (wc < 0x1d000) return -1; else if (wc < 0x1d0f6) return 1; else if (wc < 0x1d100) return -1; else if (wc < 0x1d127) return 1; else if (wc < 0x1d129) return -1; else if (wc < 0x1d167) return 1; else if (wc < 0x1d16a) return 0; else if (wc < 0x1d173) return 1; else if (wc < 0x1d183) return 0; else if (wc < 0x1d185) return 1; else if (wc < 0x1d18c) return 0; else if (wc < 0x1d1aa) return 1; else if (wc < 0x1d1ae) return 0; else if (wc < 0x1d1eb) return 1; else if (wc < 0x1d200) return -1; else if (wc < 0x1d242) return 1; else if (wc < 0x1d245) return 0; else if (wc < 0x1d246) return 1; else if (wc < 0x1d2c0) return -1; else if (wc < 0x1d2d4) return 1; else if (wc < 0x1d2e0) return -1; else if (wc < 0x1d2f4) return 1; else if (wc < 0x1d300) return -1; else if (wc < 0x1d357) return 1; else if (wc < 0x1d360) return -1; else if (wc < 0x1d379) return 1; else if (wc < 0x1d400) return -1; else if (wc < 0x1d455) return 1; else if (wc < 0x1d456) return -1; else if (wc < 0x1d49d) return 1; else if (wc < 0x1d49e) return -1; else if (wc < 0x1d4a0) return 1; else if (wc < 0x1d4a2) return -1; else if (wc < 0x1d4a3) return 1; else if (wc < 0x1d4a5) return -1; else if (wc < 0x1d4a7) return 1; else if (wc < 0x1d4a9) return -1; else if (wc < 0x1d4ad) return 1; else if (wc < 0x1d4ae) return -1; else if (wc < 0x1d4ba) return 1; else if (wc < 0x1d4bb) return -1; else if (wc < 0x1d4bc) return 1; else if (wc < 0x1d4bd) return -1; else if (wc < 0x1d4c4) return 1; else if (wc < 0x1d4c5) return -1; else if (wc < 0x1d506) return 1; else if (wc < 0x1d507) return -1; else if (wc < 0x1d50b) return 1; else if (wc < 0x1d50d) return -1; else if (wc < 0x1d515) return 1; else if (wc < 0x1d516) return -1; else if (wc < 0x1d51d) return 1; else if (wc < 0x1d51e) return -1; else if (wc < 0x1d53a) return 1; else if (wc < 0x1d53b) return -1; else if (wc < 0x1d53f) return 1; else if (wc < 0x1d540) return -1; else if (wc < 0x1d545) return 1; else if (wc < 0x1d546) return -1; else if (wc < 0x1d547) return 1; else if (wc < 0x1d54a) return -1; else if (wc < 0x1d551) return 1; else if (wc < 0x1d552) return -1; else if (wc < 0x1d6a6) return 1; else if (wc < 0x1d6a8) return -1; else if (wc < 0x1d7cc) return 1; else if (wc < 0x1d7ce) return -1; else if (wc < 0x1da00) return 1; else if (wc < 0x1da37) return 0; else if (wc < 0x1da3b) return 1; else if (wc < 0x1da6d) return 0; else if (wc < 0x1da75) return 1; else if (wc < 0x1da76) return 0; else if (wc < 0x1da84) return 1; else if (wc < 0x1da85) return 0; else if (wc < 0x1da8c) return 1; else if (wc < 0x1da9b) return -1; else if (wc < 0x1daa0) return 0; else if (wc < 0x1daa1) return -1; else if (wc < 0x1dab0) return 0; else if (wc < 0x1df00) return -1; else if (wc < 0x1df1f) return 1; else if (wc < 0x1df25) return -1; else if (wc < 0x1df2b) return 1; else if (wc < 0x1e000) return -1; else if (wc < 0x1e007) return 0; else if (wc < 0x1e008) return -1; else if (wc < 0x1e019) return 0; else if (wc < 0x1e01b) return -1; else if (wc < 0x1e022) return 0; else if (wc < 0x1e023) return -1; else if (wc < 0x1e025) return 0; else if (wc < 0x1e026) return -1; else if (wc < 0x1e02b) return 0; else if (wc < 0x1e030) return -1; else if (wc < 0x1e06e) return 1; else if (wc < 0x1e08f) return -1; else if (wc < 0x1e090) return 0; else if (wc < 0x1e100) return -1; else if (wc < 0x1e12d) return 1; else if (wc < 0x1e130) return -1; else if (wc < 0x1e137) return 0; else if (wc < 0x1e13e) return 1; else if (wc < 0x1e140) return -1; else if (wc < 0x1e14a) return 1; else if (wc < 0x1e14e) return -1; else if (wc < 0x1e150) return 1; else if (wc < 0x1e290) return -1; else if (wc < 0x1e2ae) return 1; else if (wc < 0x1e2af) return 0; else if (wc < 0x1e2c0) return -1; else if (wc < 0x1e2ec) return 1; else if (wc < 0x1e2f0) return 0; else if (wc < 0x1e2fa) return 1; else if (wc < 0x1e2ff) return -1; else if (wc < 0x1e300) return 1; else if (wc < 0x1e4d0) return -1; else if (wc < 0x1e4ec) return 1; else if (wc < 0x1e4f0) return 0; else if (wc < 0x1e4fa) return 1; else if (wc < 0x1e7e0) return -1; else if (wc < 0x1e7e7) return 1; else if (wc < 0x1e7e8) return -1; else if (wc < 0x1e7ec) return 1; else if (wc < 0x1e7ed) return -1; else if (wc < 0x1e7ef) return 1; else if (wc < 0x1e7f0) return -1; else if (wc < 0x1e7ff) return 1; else if (wc < 0x1e800) return -1; else if (wc < 0x1e8c5) return 1; else if (wc < 0x1e8c7) return -1; else if (wc < 0x1e8d0) return 1; else if (wc < 0x1e8d7) return 0; else if (wc < 0x1e900) return -1; else if (wc < 0x1e944) return 1; else if (wc < 0x1e94b) return 0; else if (wc < 0x1e94c) return 1; else if (wc < 0x1e950) return -1; else if (wc < 0x1e95a) return 1; else if (wc < 0x1e95e) return -1; else if (wc < 0x1e960) return 1; else if (wc < 0x1ec71) return -1; else if (wc < 0x1ecb5) return 1; else if (wc < 0x1ed01) return -1; else if (wc < 0x1ed3e) return 1; else if (wc < 0x1ee00) return -1; else if (wc < 0x1ee04) return 1; else if (wc < 0x1ee05) return -1; else if (wc < 0x1ee20) return 1; else if (wc < 0x1ee21) return -1; else if (wc < 0x1ee23) return 1; else if (wc < 0x1ee24) return -1; else if (wc < 0x1ee25) return 1; else if (wc < 0x1ee27) return -1; else if (wc < 0x1ee28) return 1; else if (wc < 0x1ee29) return -1; else if (wc < 0x1ee33) return 1; else if (wc < 0x1ee34) return -1; else if (wc < 0x1ee38) return 1; else if (wc < 0x1ee39) return -1; else if (wc < 0x1ee3a) return 1; else if (wc < 0x1ee3b) return -1; else if (wc < 0x1ee3c) return 1; else if (wc < 0x1ee42) return -1; else if (wc < 0x1ee43) return 1; else if (wc < 0x1ee47) return -1; else if (wc < 0x1ee48) return 1; else if (wc < 0x1ee49) return -1; else if (wc < 0x1ee4a) return 1; else if (wc < 0x1ee4b) return -1; else if (wc < 0x1ee4c) return 1; else if (wc < 0x1ee4d) return -1; else if (wc < 0x1ee50) return 1; else if (wc < 0x1ee51) return -1; else if (wc < 0x1ee53) return 1; else if (wc < 0x1ee54) return -1; else if (wc < 0x1ee55) return 1; else if (wc < 0x1ee57) return -1; else if (wc < 0x1ee58) return 1; else if (wc < 0x1ee59) return -1; else if (wc < 0x1ee5a) return 1; else if (wc < 0x1ee5b) return -1; else if (wc < 0x1ee5c) return 1; else if (wc < 0x1ee5d) return -1; else if (wc < 0x1ee5e) return 1; else if (wc < 0x1ee5f) return -1; else if (wc < 0x1ee60) return 1; else if (wc < 0x1ee61) return -1; else if (wc < 0x1ee63) return 1; else if (wc < 0x1ee64) return -1; else if (wc < 0x1ee65) return 1; else if (wc < 0x1ee67) return -1; else if (wc < 0x1ee6b) return 1; else if (wc < 0x1ee6c) return -1; else if (wc < 0x1ee73) return 1; else if (wc < 0x1ee74) return -1; else if (wc < 0x1ee78) return 1; else if (wc < 0x1ee79) return -1; else if (wc < 0x1ee7d) return 1; else if (wc < 0x1ee7e) return -1; else if (wc < 0x1ee7f) return 1; else if (wc < 0x1ee80) return -1; else if (wc < 0x1ee8a) return 1; else if (wc < 0x1ee8b) return -1; else if (wc < 0x1ee9c) return 1; else if (wc < 0x1eea1) return -1; else if (wc < 0x1eea4) return 1; else if (wc < 0x1eea5) return -1; else if (wc < 0x1eeaa) return 1; else if (wc < 0x1eeab) return -1; else if (wc < 0x1eebc) return 1; else if (wc < 0x1eef0) return -1; else if (wc < 0x1eef2) return 1; else if (wc < 0x1f000) return -1; else if (wc < 0x1f004) return 1; else if (wc < 0x1f005) return 2; else if (wc < 0x1f02c) return 1; else if (wc < 0x1f030) return -1; else if (wc < 0x1f094) return 1; else if (wc < 0x1f0a0) return -1; else if (wc < 0x1f0af) return 1; else if (wc < 0x1f0b1) return -1; else if (wc < 0x1f0c0) return 1; else if (wc < 0x1f0c1) return -1; else if (wc < 0x1f0cf) return 1; else if (wc < 0x1f0d0) return 2; else if (wc < 0x1f0d1) return -1; else if (wc < 0x1f0f6) return 1; else if (wc < 0x1f100) return -1; else if (wc < 0x1f18e) return 1; else if (wc < 0x1f18f) return 2; else if (wc < 0x1f191) return 1; else if (wc < 0x1f19b) return 2; else if (wc < 0x1f1ae) return 1; else if (wc < 0x1f1e6) return -1; else if (wc < 0x1f200) return 1; else if (wc < 0x1f203) return 2; else if (wc < 0x1f210) return -1; else if (wc < 0x1f23c) return 2; else if (wc < 0x1f240) return -1; else if (wc < 0x1f249) return 2; else if (wc < 0x1f250) return -1; else if (wc < 0x1f252) return 2; else if (wc < 0x1f260) return -1; else if (wc < 0x1f266) return 2; else if (wc < 0x1f300) return -1; else if (wc < 0x1f321) return 2; else if (wc < 0x1f32d) return 1; else if (wc < 0x1f336) return 2; else if (wc < 0x1f337) return 1; else if (wc < 0x1f37d) return 2; else if (wc < 0x1f37e) return 1; else if (wc < 0x1f394) return 2; else if (wc < 0x1f3a0) return 1; else if (wc < 0x1f3cb) return 2; else if (wc < 0x1f3cf) return 1; else if (wc < 0x1f3d4) return 2; else if (wc < 0x1f3e0) return 1; else if (wc < 0x1f3f1) return 2; else if (wc < 0x1f3f4) return 1; else if (wc < 0x1f3f5) return 2; else if (wc < 0x1f3f8) return 1; else if (wc < 0x1f43f) return 2; else if (wc < 0x1f440) return 1; else if (wc < 0x1f441) return 2; else if (wc < 0x1f442) return 1; else if (wc < 0x1f4fd) return 2; else if (wc < 0x1f4ff) return 1; else if (wc < 0x1f53e) return 2; else if (wc < 0x1f54b) return 1; else if (wc < 0x1f54f) return 2; else if (wc < 0x1f550) return 1; else if (wc < 0x1f568) return 2; else if (wc < 0x1f57a) return 1; else if (wc < 0x1f57b) return 2; else if (wc < 0x1f595) return 1; else if (wc < 0x1f597) return 2; else if (wc < 0x1f5a4) return 1; else if (wc < 0x1f5a5) return 2; else if (wc < 0x1f5fb) return 1; else if (wc < 0x1f650) return 2; else if (wc < 0x1f680) return 1; else if (wc < 0x1f6c6) return 2; else if (wc < 0x1f6cc) return 1; else if (wc < 0x1f6cd) return 2; else if (wc < 0x1f6d0) return 1; else if (wc < 0x1f6d3) return 2; else if (wc < 0x1f6d5) return 1; else if (wc < 0x1f6d8) return 2; else if (wc < 0x1f6dc) return -1; else if (wc < 0x1f6e0) return 2; else if (wc < 0x1f6eb) return 1; else if (wc < 0x1f6ed) return 2; else if (wc < 0x1f6f0) return -1; else if (wc < 0x1f6f4) return 1; else if (wc < 0x1f6fd) return 2; else if (wc < 0x1f700) return -1; else if (wc < 0x1f777) return 1; else if (wc < 0x1f77b) return -1; else if (wc < 0x1f7da) return 1; else if (wc < 0x1f7e0) return -1; else if (wc < 0x1f7ec) return 2; else if (wc < 0x1f7f0) return -1; else if (wc < 0x1f7f1) return 2; else if (wc < 0x1f800) return -1; else if (wc < 0x1f80c) return 1; else if (wc < 0x1f810) return -1; else if (wc < 0x1f848) return 1; else if (wc < 0x1f850) return -1; else if (wc < 0x1f85a) return 1; else if (wc < 0x1f860) return -1; else if (wc < 0x1f888) return 1; else if (wc < 0x1f890) return -1; else if (wc < 0x1f8ae) return 1; else if (wc < 0x1f8b0) return -1; else if (wc < 0x1f8b2) return 1; else if (wc < 0x1f900) return -1; else if (wc < 0x1f90c) return 1; else if (wc < 0x1f93b) return 2; else if (wc < 0x1f93c) return 1; else if (wc < 0x1f946) return 2; else if (wc < 0x1f947) return 1; else if (wc < 0x1fa00) return 2; else if (wc < 0x1fa54) return 1; else if (wc < 0x1fa60) return -1; else if (wc < 0x1fa6e) return 1; else if (wc < 0x1fa70) return -1; else if (wc < 0x1fa7d) return 2; else if (wc < 0x1fa80) return -1; else if (wc < 0x1fa89) return 2; else if (wc < 0x1fa90) return -1; else if (wc < 0x1fabe) return 2; else if (wc < 0x1fabf) return -1; else if (wc < 0x1fac6) return 2; else if (wc < 0x1face) return -1; else if (wc < 0x1fadc) return 2; else if (wc < 0x1fae0) return -1; else if (wc < 0x1fae9) return 2; else if (wc < 0x1faf0) return -1; else if (wc < 0x1faf9) return 2; else if (wc < 0x1fb00) return -1; else if (wc < 0x1fb93) return 1; else if (wc < 0x1fb94) return -1; else if (wc < 0x1fbcb) return 1; else if (wc < 0x1fbf0) return -1; else if (wc < 0x1fbfa) return 1; else if (wc < 0x20000) return -1; else if (wc < 0x2fffe) return 2; else if (wc < 0x30000) return -1; else if (wc < 0x3fffe) return 2; else if (wc < 0xe0001) return -1; else if (wc < 0xe0002) return 0; else if (wc < 0xe0020) return -1; else if (wc < 0xe0080) return 0; else if (wc < 0xe0100) return -1; else if (wc < 0xe01f0) return 0; else if (wc < 0xf0000) return -1; else if (wc < 0xffffe) return 1; else if (wc < 0x100000) return -1; else if (wc < 0x10fffe) return 1; else if (wc < 0x110000) return -1; return -1; } ================================================ FILE: linker/README.md ================================================ # ELF Shared Library Dynamic Linker/Loader ToaruOS employs *shared objects* to allow for smaller on-disk binary sizes and provide runtime loading and linking. The linker here becomes `/lib/ld.so` and is called as the interpreter for dynamically-linked binaries in the OS. ## ld.so Implementation The 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. ## ld.so Debugging You 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. ================================================ FILE: linker/link.ld ================================================ ENTRY(_start) SECTIONS { . = 0x3F000000; phys = .; .text BLOCK(4K) : ALIGN(4K) { code = .; *(.text) } .rodata BLOCK(4K) : ALIGN(4K) { *(.rodata) } .data BLOCK(4K) : ALIGN(4K) { *(.data) } .bss BLOCK(4K) : ALIGN(4K) { *(COMMON) *(.bss) } .eh_frame BLOCK(4K) : ALIGN(4K) { *(.eh_frame) } .init_array : { PROVIDE_HIDDEN (__init_array_start = .); *(.init_array); PROVIDE_HIDDEN (__init_array_end = .); } end = .; /DISCARD/ : { *(.comment) *(.note.gnu.build-id) } } ================================================ FILE: linker/linker.c ================================================ /** * @file linker/linker.c * @brief ELF Dynamic Linker/Loader * * Loads ELF executables and links them at runtime to their * shared library dependencies. * * As of writing, this is a simplistic and not-fully-compliant * implementation of ELF dynamic linking. It suffers from a number * of issues, including not actually sharing libraries (there * isn't a sufficient mechanism in the kernel at the moment for * doing that - we need something with copy-on-write, preferably * an mmap-file mechanism), as well as not handling symbol * resolution correctly. * * However, it's sufficient for our purposes, and works well enough * to load Python C modules. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include void * (*_malloc)(size_t size) = malloc; void (*_free)(void * ptr) = free; #undef malloc #undef free #define malloc ld_x_malloc #define free ld_x_free uintptr_t _malloc_minimum = 0; static void * malloc(size_t size) { return _malloc(size); } static void free(void * ptr) { if ((uintptr_t)ptr < _malloc_minimum) return; _free(ptr); } /* * When the LD_DEBUG environment variable is set, TRACE_LD messages * will be printed to stderr */ #define TRACE_APP_NAME "ld.so" #define TRACE_LD(...) do { if (__trace_ld) { TRACE(__VA_ARGS__); } } while (0) static int __trace_ld = 0; #include /* * This libraries are included in source form to avoid having * to build separate objects for them and complicate linking, * since ld is specially built as a static object. */ #include "../lib/list.c" #include "../lib/hashmap.c" typedef int (*entry_point_t)(int, char *[], char**); /* Global linking state */ static hashmap_t * dumb_symbol_table; static hashmap_t * glob_dat; static hashmap_t * objects_map; static hashmap_t * tls_map; static size_t current_tls_offset = 16; /* Used for dlerror */ static char * last_error = NULL; static int _target_is_suid = 0; typedef struct elf_object { FILE * file; /* Full copy of the header. */ Elf64_Header header; char * dyn_string_table; size_t dyn_string_table_size; Elf64_Sym * dyn_symbol_table; size_t dyn_symbol_table_size; Elf64_Dyn * dynamic; Elf64_Word * dyn_hash; void (*init)(void); void (**init_array)(void); size_t init_array_size; uintptr_t base; list_t * dependencies; int loaded; } elf_t; static elf_t * _main_obj = NULL; static void clear_cache(uintptr_t start, uintptr_t end) { char * data[] = {(char*)start,(char*)end}; sysfunc(42, data); } /* Locate library for LD_LIBRARY PATH */ static char * find_lib(const char * file) { /* If it was an absolute path, there's no need to find it. */ if (strchr(file, '/')) return strdup(file); /* Collect the environment variable. */ char * path = _target_is_suid ? NULL : getenv("LD_LIBRARY_PATH"); if (!path) { /* Not set - this is the default state. Should probably read from config file? */ path = "/lib:/usr/lib"; } /* Duplicate so we can tokenize without editing */ char * xpath = strdup(path); char * p, * last; for ((p = strtok_r(xpath, ":", &last)); p; p = strtok_r(NULL, ":", &last)) { /* Go through each LD_LIBRARY_PATH entry */ int r; struct stat stat_buf; /* Append the requested file to that path */ char * exe = malloc(strlen(p) + strlen(file) + 2); *exe = '\0'; strcat(exe, p); strcat(exe, "/"); strcat(exe, file); /* See if it exists */ r = stat(exe, &stat_buf); if (r != 0) { /* Nope. */ free(exe); continue; } /* It exists, so this is what we want. */ return exe; } free(xpath); /* No match found. */ return NULL; } /* Open an object file */ static elf_t * open_object(const char * path) { /* If no path (eg. dlopen(NULL)), return the main object (the executable). */ if (!path) { return _main_obj; } /* If we've already opened a file with this name, return it - don't load things twice. */ if (hashmap_has(objects_map, (void*)path)) { elf_t * object = hashmap_get(objects_map, (void*)path); return object; } /* Locate the library */ char * file = find_lib(path); if (!file) { last_error = "Could not find library."; return NULL; } /* Open the library. */ FILE * f = fopen(file, "r"); /* Free the expanded path, we don't need it anymore. */ free(file); /* Failed to open? Unlikely, but could mean permissions problems. */ if (!f) { last_error = "Could not open library."; return NULL; } /* Initialize a fresh object object. */ elf_t * object = malloc(sizeof(elf_t)); /* Really unlikely... */ if (!object) { last_error = "Could not allocate space."; return NULL; } memset(object, 0, sizeof(elf_t)); hashmap_set(objects_map, (void*)path, object); object->file = f; /* Read the header */ size_t r = fread(&object->header, sizeof(Elf64_Header), 1, object->file); /* Header failed to read? */ if (!r) { last_error = "Failed to read object header."; free(object); return NULL; } /* Is this actually an ELF object? */ if (object->header.e_ident[0] != ELFMAG0 || object->header.e_ident[1] != ELFMAG1 || object->header.e_ident[2] != ELFMAG2 || object->header.e_ident[3] != ELFMAG3) { last_error = "Not an ELF object."; free(object); return NULL; } /* Prepare a list for tracking dependencies. */ object->dependencies = list_create(); return object; } /* Calculate the size of an object file by examining its phdrs */ static size_t object_calculate_size(elf_t * object) { uintptr_t base_addr = (uintptr_t)-1; uintptr_t end_addr = 0x0; size_t headers = 0; while (headers < object->header.e_phnum) { Elf64_Phdr phdr; /* Read the phdr */ fseek(object->file, object->header.e_phoff + object->header.e_phentsize * headers, SEEK_SET); fread(&phdr, object->header.e_phentsize, 1, object->file); switch (phdr.p_type) { case PT_LOAD: { /* If this loads lower than our current base... */ if (phdr.p_vaddr < base_addr) { base_addr = phdr.p_vaddr; } /* Or higher than our current end address... */ if (phdr.p_memsz + phdr.p_vaddr > end_addr) { end_addr = phdr.p_memsz + phdr.p_vaddr; } } break; /* TODO: Do we care about other PHDR types here? */ default: break; } headers++; } /* If base_addr is still -1, then no valid phdrs were found, and the object has no loaded size. */ if (base_addr == (uintptr_t)-1) return 0; return end_addr - base_addr; } /* Load an object into memory */ static uintptr_t object_load(elf_t * object, uintptr_t base) { uintptr_t end_addr = 0x0; object->base = base; size_t headers = 0; while (headers < object->header.e_phnum) { Elf64_Phdr phdr; /* Read the phdr */ fseek(object->file, object->header.e_phoff + object->header.e_phentsize * headers, SEEK_SET); fread(&phdr, object->header.e_phentsize, 1, object->file); switch (phdr.p_type) { case PT_LOAD: { /* Request memory to load this PHDR into */ char * args[] = {(char *)(base + phdr.p_vaddr), (char *)phdr.p_memsz}; sysfunc(TOARU_SYS_FUNC_MMAP, args); /* Copy the code into memory */ fseek(object->file, phdr.p_offset, SEEK_SET); fread((void *)(base + phdr.p_vaddr), phdr.p_filesz, 1, object->file); clear_cache(base + phdr.p_vaddr, base + phdr.p_vaddr + phdr.p_filesz); /* Zero the remaining area */ size_t r = phdr.p_filesz; while (r < phdr.p_memsz) { *(char *)(phdr.p_vaddr + base + r) = 0; r++; } /* If this expands our end address, be sure to update it */ if (end_addr < phdr.p_vaddr + base + phdr.p_memsz) { end_addr = phdr.p_vaddr + base + phdr.p_memsz; } } break; case PT_DYNAMIC: { /* Keep a reference to the dynamic section, which is actually loaded by a PT_LOAD normally. */ object->dynamic = (Elf64_Dyn *)(base + phdr.p_vaddr); } break; default: break; } headers++; } return end_addr; } /* Perform cleanup after loading */ static int object_postload(elf_t * object) { /* If there is a dynamic table, parse it. */ if (object->dynamic) { Elf64_Dyn * table; /* Locate string tables */ table = object->dynamic; while (table->d_tag) { switch (table->d_tag) { case DT_HASH: object->dyn_hash = (Elf64_Word *)(object->base + table->d_un.d_ptr); object->dyn_symbol_table_size = object->dyn_hash[1]; break; case DT_STRTAB: object->dyn_string_table = (char *)(object->base + table->d_un.d_ptr); break; case DT_SYMTAB: object->dyn_symbol_table = (Elf64_Sym *)(object->base + table->d_un.d_ptr); break; case DT_STRSZ: /* Size of string table */ object->dyn_string_table_size = table->d_un.d_val; break; case DT_INIT: /* DT_INIT - initialization function */ object->init = (void (*)(void))(table->d_un.d_ptr + object->base); break; case DT_INIT_ARRAY: /* DT_INIT_ARRAY - array of constructors */ object->init_array = (void (**)(void))(table->d_un.d_ptr + object->base); break; case DT_INIT_ARRAYSZ: /* DT_INIT_ARRAYSZ - size of the table of constructors */ object->init_array_size = table->d_un.d_val / sizeof(uintptr_t); break; } table++; } /* * Read through dependencies * We have to do this separately from the above to make sure * we have the dynamic string tables loaded first, as they * are needed for the dependency names. */ table = object->dynamic; while (table->d_tag) { switch (table->d_tag) { case 1: list_insert(object->dependencies, object->dyn_string_table + table->d_un.d_val); break; } table++; } } return 0; } /* Whether symbol addresses is needed for a relocation type */ static int need_symbol_for_type(unsigned int type) { #ifdef __x86_64__ switch(type) { case R_X86_64_64: case R_X86_64_PC32: case R_X86_64_COPY: case R_X86_64_GLOB_DAT: case R_X86_64_JUMP_SLOT: case R_X86_64_8: case R_X86_64_TPOFF64: case R_X86_64_DTPMOD64: case R_X86_64_DTPOFF64: return 1; default: return 0; } #else switch(type) { case 1024: case 1025: case 1026: case 1030: case 1031: case 257: return 1; default: return 0; } #endif } __attribute__((unused)) static size_t __tlsdesc_static(size_t * a) { return a[1]; } /* Apply ELF relocations */ static int object_relocate(elf_t * object) { /* If there is a dynamic symbol table, load symbols */ if (object->dyn_symbol_table) { Elf64_Sym * table = object->dyn_symbol_table; size_t i = 0; while (i < object->dyn_symbol_table_size) { char * symname = (char *)((uintptr_t)object->dyn_string_table + table->st_name); int is_tls = (table->st_info & 0xF) == 6; /* If we haven't added this symbol to our symbol table, do so now. */ if (table->st_shndx) { if (!hashmap_has(dumb_symbol_table, symname)) { hashmap_set(dumb_symbol_table, symname, (void*)(table->st_value + (is_tls ? 0 : object->base))); table->st_value = table->st_value + (is_tls ? 0 : object->base); } else { table->st_value = (uintptr_t)hashmap_get(dumb_symbol_table, symname); } } else { if (hashmap_has(dumb_symbol_table, symname)) { table->st_value = (uintptr_t)hashmap_get(dumb_symbol_table, symname); } else { table->st_value = table->st_value + (is_tls ? 0 : object->base); } } table++; i++; } } /* Find relocation table */ for (uintptr_t x = 0; x < object->header.e_shentsize * object->header.e_shnum; x += object->header.e_shentsize) { Elf64_Shdr shdr; /* Load section header */ fseek(object->file, object->header.e_shoff + x, SEEK_SET); fread(&shdr, object->header.e_shentsize, 1, object->file); /* Relocation table found */ if (shdr.sh_type == SHT_REL) { TRACE_LD("Found a REL section, this is not handled."); } else if (shdr.sh_type == SHT_RELA) { Elf64_Rela * table = (Elf64_Rela *)(shdr.sh_addr + object->base); while ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) { unsigned int symbol = ELF64_R_SYM(table->r_info); unsigned int type = ELF64_R_TYPE(table->r_info); Elf64_Sym * sym = &object->dyn_symbol_table[symbol]; /* If we need symbol for this, get it. */ char * symname = NULL; uintptr_t x = sym->st_value; if (need_symbol_for_type(type) || (type == 5)) { symname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name); if (symname && hashmap_has(dumb_symbol_table, symname)) { x = (uintptr_t)hashmap_get(dumb_symbol_table, symname); } else { /* This isn't fatal, but do log a message if debugging is enabled. */ if ((sym->st_info >> 4) != STB_WEAK) { extern char * _argv_0; fprintf(stderr, "%s: undefined symbol %s\n", _argv_0, symname); } TRACE_LD("Symbol not found: %s", symname); x = 0x0; } } /* Relocations, symbol lookups, etc. */ switch (type) { #if defined(__x86_64__) case R_X86_64_GLOB_DAT: /* 6 */ if (symname && hashmap_has(glob_dat, symname)) { x = (uintptr_t)hashmap_get(glob_dat, symname); } /* fallthrough */ case R_X86_64_JUMP_SLOT: /* 7 */ memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case R_X86_64_RELATIVE: /* 8*/ x = object->base; x += table->r_addend; //*((ssize_t *)(table->r_offset + object->base)); memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case R_X86_64_64: /* 1 */ x += table->r_addend; memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case R_X86_64_COPY: /* 5 */ memcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size); break; case R_X86_64_TPOFF64: x += *((ssize_t *)(table->r_offset + object->base)); if (!hashmap_has(tls_map, symname)) { if (!sym->st_size) { fprintf(stderr, "Haven't placed %s in static TLS yet but don't know its size?\n", symname); } x += current_tls_offset; hashmap_set(tls_map, symname, (void*)(current_tls_offset)); current_tls_offset += sym->st_size; /* TODO alignment restrictions */ } else { x += (size_t)hashmap_get(tls_map, symname); } memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case R_X86_64_DTPMOD64: if (!hashmap_has(tls_map, symname)) { fprintf(stderr, "tls entry is unallocated?\n"); break; } x = 0; memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case R_X86_64_DTPOFF64: if (!hashmap_has(tls_map, symname)) { fprintf(stderr, "tls entry is unallocated?\n"); break; } x = table->r_addend; x += (size_t)hashmap_get(tls_map, symname); memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; #elif defined(__aarch64__) case 1024: /* COPY */ memcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size); break; case 1025: /* GLOB_DAT */ if (symname && hashmap_has(glob_dat, symname)) { x = (uintptr_t)hashmap_get(glob_dat, symname); } /* fallthrough */ case 1026: /* JUMP_SLOT */ memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case 1027: /* RELATIVE */ x = object->base; x += table->r_addend; memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case 257: /* ABS64 */ x += table->r_addend; memcpy((void*)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case 1030: /* TLS_TPREL64 TPREL(S+A) */ x += *((ssize_t *)(table->r_offset + object->base)); /* A */ if (!hashmap_has(tls_map, symname)) { if (!sym->st_size) { fprintf(stderr, "Haven't placed %s in static TLS yet but don't know its size?\n", symname); } x += current_tls_offset; hashmap_set(tls_map, symname, (void*)(current_tls_offset)); current_tls_offset += sym->st_size; /* TODO alignment restrictions */ } else { size_t val = (size_t)hashmap_get(tls_map, symname); TRACE_LD("add %#zx to %zx\n", val, x); x += val; } memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case 1031: { if (symbol) { if (!hashmap_has(tls_map, symname)) { fprintf(stderr, "Warning: Don't know where to get %s (symbol %d) from TLS\n", symname, symbol); break; } x += *((ssize_t *)(table->r_offset + object->base)); x += (size_t)hashmap_get(tls_map, symname); } else { /* local tls descriptor? no symbol? idk what to do with this, hope it works */ x = current_tls_offset; current_tls_offset += 8*4; /* idk, alignment I guess */ } uintptr_t func = (uintptr_t)&__tlsdesc_static; memcpy((void *)(table->r_offset + object->base), &func, sizeof(uintptr_t)); memcpy((void *)(table->r_offset + object->base + sizeof(uintptr_t)), &x, sizeof(uintptr_t)); break; } #else # error "unsupported" #endif #if 0 case 6: /* GLOB_DAT */ if (symname && hashmap_has(glob_dat, symname)) { x = (uintptr_t)hashmap_get(glob_dat, symname); } case 7: /* JUMP_SLOT */ memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case 1: /* 32 */ x += *((ssize_t *)(table->r_offset + object->base)); memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case 2: /* PC32 */ x += *((ssize_t *)(table->r_offset + object->base)); x -= (table->r_offset + object->base); memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case 8: /* RELATIVE */ x = object->base; x += *((ssize_t *)(table->r_offset + object->base)); memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; case 5: /* COPY */ memcpy((void *)(table->r_offset + object->base), (void *)x, sym->st_size); break; case 14: /* TLS_TPOFF */ x = *((ssize_t *)(table->r_offset + object->base)); if (!hashmap_has(tls_map, symname)) { if (!sym->st_size) { fprintf(stderr, "Haven't placed %s in static TLS yet but don't know its size?\n", symname); } current_tls_offset += sym->st_size; /* TODO alignment restrictions */ hashmap_set(tls_map, symname, (void*)(current_tls_offset)); x -= current_tls_offset; } else { x -= (size_t)hashmap_get(tls_map, symname); } memcpy((void *)(table->r_offset + object->base), &x, sizeof(uintptr_t)); break; #endif default: { char msg[200]; snprintf(msg, 200, "Unimplemented relocation (%d) requested, bailing.\n", type); sysfunc(TOARU_SYS_FUNC_LOGHERE, (char**)msg); exit(1); } TRACE_LD("Unknown relocation type: %d", type); break; } table++; } } } return 0; } /* Copy relocations are special and need to be located before other relocations. */ static void object_find_copy_relocations(elf_t * object) { for (uintptr_t x = 0; x < object->header.e_shentsize * object->header.e_shnum; x += object->header.e_shentsize) { Elf64_Shdr shdr; fseek(object->file, object->header.e_shoff + x, SEEK_SET); fread(&shdr, object->header.e_shentsize, 1, object->file); /* Relocation table found */ if (shdr.sh_type == SHT_REL) { Elf64_Rel * table = (Elf64_Rel *)(shdr.sh_addr + object->base); while ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) { unsigned int type = ELF64_R_TYPE(table->r_info); #if defined(__x86_64__) if (type == R_X86_64_COPY) { #elif defined(__aarch64__) if (type == R_AARCH64_COPY) { #else # error "Unsupported" #endif unsigned int symbol = ELF64_R_SYM(table->r_info); Elf64_Sym * sym = &object->dyn_symbol_table[symbol]; char * symname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name); hashmap_set(glob_dat, symname, (void *)table->r_offset); } table++; } } if (shdr.sh_type == SHT_RELA) { Elf64_Rela * table = (Elf64_Rela *)(shdr.sh_addr + object->base); while ((uintptr_t)table - ((uintptr_t)shdr.sh_addr + object->base) < shdr.sh_size) { unsigned int type = ELF64_R_TYPE(table->r_info); #if defined(__x86_64__) if (type == R_X86_64_COPY) { #elif defined(__aarch64__) if (type == R_AARCH64_COPY) { #else # error "Unsupported" #endif unsigned int symbol = ELF64_R_SYM(table->r_info); Elf64_Sym * sym = &object->dyn_symbol_table[symbol]; char * symname = (char *)((uintptr_t)object->dyn_string_table + sym->st_name); hashmap_set(glob_dat, symname, (void *)table->r_offset); } table++; } } } } /* Find a symbol in a specific object. */ static void * object_find_symbol(elf_t * object, const char * symbol_name) { if (!object->dyn_symbol_table) { last_error = "lib does not have a symbol table"; return NULL; } Elf64_Sym * table = object->dyn_symbol_table; size_t i = 0; while (i < object->dyn_symbol_table_size) { if (!strcmp(symbol_name, (char *)((uintptr_t)object->dyn_string_table + table->st_name))) { return (void *)(table->st_value); } table++; i++; } last_error = "symbol not found in library"; return NULL; } /* Fully load an object. */ static void * do_actual_load(const char * filename, elf_t * lib, int flags) { (void)flags; if (!lib) { last_error = "could not open library (not found, or other failure)"; TRACE_LD("could not open library"); return NULL; } size_t lib_size = object_calculate_size(lib); /* Needs to be at least a page. */ if (lib_size < 4096) { lib_size = 4096; } /* * Allocate space to load the library * This is where we should really be loading things into COW * but we don't have the functionality available. */ uintptr_t load_addr = (uintptr_t)valloc(lib_size); object_load(lib, load_addr); /* Perform cleanup steps */ object_postload(lib); /* Ensure dependencies are available */ node_t * item; while ((item = list_pop(lib->dependencies))) { elf_t * _lib = open_object(item->value); if (!_lib) { /* Missing dependencies are fatal to this process, but * not to the entire application. */ free((void *)load_addr); last_error = "Failed to load a dependency."; lib->loaded = 0; TRACE_LD("Failed to load object: %s", item->value); return NULL; } if (!_lib->loaded) { do_actual_load(item->value, _lib, 0); TRACE_LD("Loaded %s at 0x%x", item->value, lib->base); } } /* Perform relocations */ TRACE_LD("Relocating %s", filename); object_relocate(lib); /* We're done with the file. */ fclose(lib->file); /* If there was an init_array, call everything in it */ if (lib->init_array) { for (size_t i = 0; i < lib->init_array_size; i++) { TRACE_LD(" 0x%x()", lib->init_array[i]); lib->init_array[i](); } } /* If the library has an init function, call that last. */ if (lib->init) { lib->init(); } lib->loaded = 1; /* And return an object for the loaded library */ return (void *)lib; } static uintptr_t end_addr = 0; /** * Half loads an object using the dumb allocator and performs dependency * resolution. This is a separate process from do_actual_load and dlopen_ld * to avoid problems with malloc and library functions while loading. * Preloaded objects will be fully loaded everything is relocated. */ static elf_t * preload(hashmap_t * libs, list_t * load_libs, char * lib_name) { /* Find and open the library */ elf_t * lib = open_object(lib_name); if (!lib) { fprintf(stderr, "Failed to load dependency '%s'.\n", lib_name); return NULL; } /* Skip already loaded libraries */ if (lib->loaded) return lib; /* Mark this library available */ hashmap_set(libs, lib_name, lib); /* Adjust dumb allocator */ end_addr = (end_addr + 0xFFF) & ~(0xFFFUL); TRACE_LD("Loading %s at 0x%x", lib_name, end_addr); /* Load PHDRs */ end_addr = object_load(lib, end_addr); /* Extract information */ object_postload(lib); /* Mark loaded */ lib->loaded = 1; /* Verify dependencies are loaded before we relocate */ foreach(node, lib->dependencies) { if (!hashmap_has(libs, node->value)) { TRACE_LD("Need unloaded dependency %s", node->value); preload(libs, load_libs, node->value); } } /* Add this to the (forward scan) list of libraries to finish loading */ list_insert(load_libs, lib); return lib; } /* exposed dlopen() method */ static void * dlopen_ld(const char * filename, int flags) { TRACE_LD("dlopen(%s,0x%x)", filename, flags); elf_t * lib = open_object(filename); if (!lib) { return NULL; } if (lib->loaded) { return lib; } void * ret = do_actual_load(filename, lib, flags); if (!ret) { /* Dependency load failure, remove us from hash */ TRACE_LD("Dependency load failure"); hashmap_remove(objects_map, (void*)filename); } TRACE_LD("Loaded %s at 0x%x", filename, lib->base); return ret; } /* exposed dlclose() method - XXX not fully implemented */ static int dlclose_ld(elf_t * lib) { /* TODO close dependencies? Make sure nothing references this. */ free((void *)lib->base); return 0; } /* exposed dlerror() method */ static char * dlerror_ld(void) { char * this_error = last_error; last_error = NULL; return this_error; } /* Specially used by libc */ static void * _argv_value = NULL; static char * argv_value(void) { return _argv_value; } static uintptr_t dl_symbol_table_ptr_addr(void) { return (uintptr_t)&dumb_symbol_table; } static uintptr_t dl_objects_table_ptr_addr(void) { return (uintptr_t)&objects_map; } /* Exported methods (dlfcn) */ typedef struct { char * name; void * symbol; } ld_exports_t; ld_exports_t ld_builtin_exports[] = { {"dlopen", dlopen_ld}, {"dlsym", object_find_symbol}, {"dlclose", dlclose_ld}, {"dlerror", dlerror_ld}, {"__get_argv", argv_value}, {"__ld_symbol_table", dl_symbol_table_ptr_addr}, {"__ld_objects_table", dl_objects_table_ptr_addr}, {NULL, NULL}, }; int main(int argc, char * argv[]) { if (argc < 2) { fprintf(stderr, "ld.so - dynamic binary loader\n" "\n" "usage: %s [-e] [EXECUTABLE PATH]\n" "\n" " -e \033[3mAdjust argument offset\033[0m\n" "\n", argv[0]); return -1; } char * file = argv[1]; size_t arg_offset = 1; if (!strcmp(argv[1], "-e")) { arg_offset = 3; file = argv[2]; } _argv_value = argv+arg_offset; /* Enable tracing if requested */ char * trace_ld_env = getenv("LD_DEBUG"); if (trace_ld_env && (!strcmp(trace_ld_env,"1") || !strcmp(trace_ld_env,"yes"))) { __trace_ld = 1; } /* Initialize hashmaps for symbols, GLOB_DATs, and objects */ dumb_symbol_table = hashmap_create(100); glob_dat = hashmap_create(10); objects_map = hashmap_create(10); tls_map = hashmap_create(10); /* Setup symbols for built-in exports */ ld_exports_t * ex = ld_builtin_exports; while (ex->name) { hashmap_set(dumb_symbol_table, ex->name, ex->symbol); ex++; } /* Technically there's a potential time-of-use probably if we check like this but * this is a toy linker for a toy OS so the fact that we even need to check suid * bits at all is outrageous */ struct stat buf; if (stat(file, &buf)) { fprintf(stderr, "%s: target binary '%s' not available\n", argv[0], file); } /* Technically there's a way to know we're running suid, but let's check the actual file */ if (buf.st_mode & S_ISUID) { _target_is_suid = 1; } /* Open the requested main object */ elf_t * main_obj = open_object(file); _main_obj = main_obj; if (!main_obj) { fprintf(stderr, "%s: error: failed to open object '%s'.\n", argv[0], file); return 1; } /* Load the main object */ end_addr = object_load(main_obj, 0x0); object_postload(main_obj); object_find_copy_relocations(main_obj); /* Load library dependencies */ hashmap_t * libs = hashmap_create(10); while (end_addr & 0xFFF) { end_addr++; } /* Load dependent libraries, recursively. */ TRACE_LD("Loading dependencies."); list_t * load_libs = list_create(); node_t * item; while ((item = list_pop(main_obj->dependencies))) { char * lib_name = item->value; /* Skip libg.so which is a fake library that doesn't really exist. * XXX: Only binaries should depend on this I think? */ if (!strcmp(lib_name, "libg.so")) goto nope; /* Preload library */ elf_t * lib = preload(libs, load_libs, lib_name); /* Failed to load */ if (!lib) return 1; nope: free(item); } list_t * ctor_libs = list_create(); list_t * init_libs = list_create(); while ((item = list_dequeue(load_libs))) { elf_t * lib = item->value; /* Complete relocation */ object_relocate(lib); /* Close the underlying file */ fclose(lib->file); /* Store constructors for later execution */ if (lib->init_array) { list_insert(ctor_libs, lib); } if (lib->init) { list_insert(init_libs, lib); } free(item); } /* Relocate the main object */ TRACE_LD("Relocating main object"); object_relocate(main_obj); fclose(main_obj->file); TRACE_LD("Placing heap at end"); while (end_addr & 0xFFF) { end_addr++; } /* Move heap start (kind of like a weird sbrk) */ { char * args[] = {(char*)end_addr}; sysfunc(TOARU_SYS_FUNC_SETHEAP, args); } /* Call constructors for loaded dependencies */ char * ld_no_ctors = getenv("LD_DISABLE_CTORS"); if (ld_no_ctors && (!strcmp(ld_no_ctors,"1") || !strcmp(ld_no_ctors,"yes"))) { TRACE_LD("skipping ctors because LD_DISABLE_CTORS was set"); } else { foreach(node, ctor_libs) { elf_t * lib = node->value; if (lib->init_array) { TRACE_LD("Executing init_array..."); for (size_t i = 0; i < lib->init_array_size; i++) { TRACE_LD(" 0x%x()", lib->init_array[i]); lib->init_array[i](); } } } } foreach(node, init_libs) { elf_t * lib = node->value; lib->init(); } /* If main object had constructors, call them. */ if (main_obj->init_array) { for (size_t i = 0; i < main_obj->init_array_size; i++) { TRACE_LD(" 0x%x()", main_obj->init_array[i]); main_obj->init_array[i](); } } if (main_obj->init) { main_obj->init(); } main_obj->loaded = 1; /* Set heap functions for later usage */ if (hashmap_has(dumb_symbol_table, "malloc")) _malloc = hashmap_get(dumb_symbol_table, "malloc"); if (hashmap_has(dumb_symbol_table, "free")) _free = hashmap_get(dumb_symbol_table, "free"); _malloc_minimum = 0x40000000; /* Jump to the entry for the main object */ TRACE_LD("Jumping to entry point 0x%lx", main_obj->header.e_entry); entry_point_t entry = (entry_point_t)main_obj->header.e_entry; entry(argc-arg_offset,argv+arg_offset,environ); return 0; } ================================================ FILE: modules/ac97.c ================================================ /** * @file kernel/audio/ac97.c * @brief Driver for the Intel AC'97. * @package x86_64 * @package aarch64 * * Simple PCM interface for the AC'97 codec when used with the * ICH hardware interface. There are other hardware interfaces * that use this codec and this driver could probably be ported * to them. * * Note that the audio subsystem is intended to be non-blocking * so that buffer filling can be done directly in interrupt handlers. * * @see http://www.intel.com/design/chipsets/manuals/29802801.pdf * * @copyright * 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 Michael Gerow * Copyright (C) 2015-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #if defined(__x86_64__) #include #include #include #elif defined(__aarch64__) #include #endif /* Utility macros */ #define N_ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0])) /* BARs! */ #define AC97_NAMBAR 0x10 /* Native Audio Mixer Base Address Register */ #define AC97_NABMBAR 0x14 /* Native Audio Bus Mastering Base Address Register */ /* Bus mastering IO port offsets */ #define AC97_PO_BDBAR 0x10 /* PCM out buffer descriptor BAR */ #define AC97_PO_CIV 0x14 /* PCM out current index value */ #define AC97_PO_LVI 0x15 /* PCM out last valid index */ #define AC97_PO_SR 0x16 /* PCM out status register */ #define AC97_PO_PICB 0x18 /* PCM out position in current buffer register */ #define AC97_PO_CR 0x1B /* PCM out control register */ /* Bus mastering misc */ /* Buffer descriptor list constants */ #define AC97_BDL_LEN 32 /* Buffer descriptor list length */ #define AC97_BDL_BUFFER_LEN 0x1000 /* Length of buffer in BDL */ #define AC97_CL_GET_LENGTH(cl) ((cl) & 0xFFFF) /* Decode length from cl */ #define AC97_CL_SET_LENGTH(cl, v) ((cl) = (v) & 0xFFFF) /* Encode length to cl */ #define AC97_CL_BUP ((uint32_t)1 << 30) /* Buffer underrun policy in cl */ #define AC97_CL_IOC ((uint32_t)1 << 31) /* Interrupt on completion flag in cl */ /* PCM out control register flags */ #define AC97_X_CR_RPBM (1 << 0) /* Run/pause bus master */ #define AC97_X_CR_RR (1 << 1) /* Reset registers */ #define AC97_X_CR_LVBIE (1 << 2) /* Last valid buffer interrupt enable */ #define AC97_X_CR_FEIE (1 << 3) /* FIFO error interrupt enable */ #define AC97_X_CR_IOCE (1 << 4) /* Interrupt on completion enable */ /* Status register flags */ #define AC97_X_SR_DCH (1 << 0) /* DMA controller halted */ #define AC97_X_SR_CELV (1 << 1) /* Current equals last valid */ #define AC97_X_SR_LVBCI (1 << 2) /* Last valid buffer completion interrupt */ #define AC97_X_SR_BCIS (1 << 3) /* Buffer completion interrupt status */ #define AC97_X_SR_FIFOE (1 << 4) /* FIFO error */ /* Mixer IO port offsets */ #define AC97_RESET 0x00 #define AC97_MASTER_VOLUME 0x02 #define AC97_AUX_OUT_VOLUME 0x04 #define AC97_MONO_VOLUME 0x06 #define AC97_PCM_OUT_VOLUME 0x18 /* snd values */ #define AC97_SND_NAME "Intel AC'97" #define AC97_PLAYBACK_SPEED 48000 #define AC97_PLAYBACK_FORMAT SND_FORMAT_L16SLE /* An entry in a buffer dscriptor list */ typedef struct { uint32_t pointer; /* Pointer to buffer */ uint32_t cl; /* Control values and buffer length */ } __attribute__((packed)) ac97_bdl_entry_t; typedef struct { uint32_t pci_device; uint16_t nabmbar; /* Native audio bus mastring BAR */ uint16_t nambar; /* Native audio mixing BAR */ size_t irq; /* This ac97's irq */ uint8_t lvi; /* The currently set last valid index */ uint8_t bits; /* How many bits of volume are supported (5 or 6) */ ac97_bdl_entry_t * bdl; /* Buffer descriptor list */ uint16_t * bufs[AC97_BDL_LEN]; /* Virtual addresses for buffers in BDL */ uint32_t bdl_p; uint32_t mask; volatile char * _iobase; spin_lock_t lock; } ac97_device_t; static ac97_device_t _device; #if defined(__aarch64__) static uint8_t inportb(size_t port) { volatile uint8_t * _port = (volatile uint8_t*)(_device._iobase + port); return *_port; } static uint16_t inports(size_t port) { volatile uint16_t * _port = (volatile uint16_t*)(_device._iobase + port); return *_port; } static uint32_t inportl(size_t port) { volatile uint32_t * _port = (volatile uint32_t*)(_device._iobase + port); return *_port; } static void outportb(size_t port, uint8_t val) { volatile uint8_t * _port = (volatile uint8_t*)(_device._iobase + port); *_port = val; } static void outports(size_t port, uint16_t val) { volatile uint16_t * _port = (volatile uint16_t*)(_device._iobase + port); *_port = val; } static void outportl(size_t port, uint32_t val) { volatile uint32_t * _port = (volatile uint32_t*)(_device._iobase + port); *_port = val; } #endif #define AC97_KNOB_PCM_OUT (SND_KNOB_VENDOR + 0) static snd_knob_t _knobs[] = { { "Master", SND_KNOB_MASTER }, { "PCM Out", SND_KNOB_VENDOR + 0 } }; static int ac97_mixer_read(uint32_t knob_id, uint32_t *val); static int ac97_mixer_write(uint32_t knob_id, uint32_t val); static snd_device_t _snd = { .name = AC97_SND_NAME, .device = &_device, .playback_speed = AC97_PLAYBACK_SPEED, .playback_format = AC97_PLAYBACK_FORMAT, .knobs = _knobs, .num_knobs = N_ELEMENTS(_knobs), .mixer_read = ac97_mixer_read, .mixer_write = ac97_mixer_write, }; /* * This could be unnecessary if we instead allocate just two buffers and make * the ac97 think there are more. */ static void find_ac97(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) { ac97_device_t * ac97 = extra; if ((vendorid == 0x8086) && (deviceid == 0x2415)) { ac97->pci_device = device; } } #define DIVISION 0x1000 #if defined(__x86_64__) static int ac97_irq_handler(struct regs * regs) { #elif defined(__aarch64__) int ac97_irq_handler(process_t * this, int irq, void * data) { #endif spin_lock(_device.lock); uint16_t sr = inports(_device.nabmbar + AC97_PO_SR); if (sr & AC97_X_SR_BCIS) { outports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_BCIS); spin_unlock(_device.lock); uint16_t current_buffer = inportb(_device.nabmbar + AC97_PO_CIV); uint16_t last_valid = ((current_buffer+2) & (AC97_BDL_LEN-1)); snd_request_buf(&_snd, 0x1000, (uint8_t *)_device.bufs[last_valid]); outportb(_device.nabmbar + AC97_PO_LVI, last_valid); snd_request_buf(&_snd, 0x1000, (uint8_t *)_device.bufs[last_valid]+0x1000); } else if (sr & AC97_X_SR_LVBCI) { outports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_LVBCI); spin_unlock(_device.lock); } else if (sr & AC97_X_SR_FIFOE) { outports(_device.nabmbar + AC97_PO_SR, AC97_X_SR_FIFOE); spin_unlock(_device.lock); } else { spin_unlock(_device.lock); return 0; } #ifdef __x86_64__ irq_ack(_device.irq); #endif return 1; } /* Currently we just assume right and left are the same */ static int ac97_mixer_read(uint32_t knob_id, uint32_t *val) { uint16_t tmp; switch (knob_id) { case SND_KNOB_MASTER: tmp = inports(_device.nambar + AC97_MASTER_VOLUME); if (tmp == 0x8000) { *val = 0; } else { /* 6 bit value */ *val = (tmp & _device.mask) << (sizeof(*val) * 8 - _device.bits); *val = ~*val; *val &= (uint32_t)_device.mask << (sizeof(*val) * 8 - _device.bits); } break; case AC97_KNOB_PCM_OUT: tmp = inports(_device.nambar + AC97_PCM_OUT_VOLUME); if (tmp == 0x8000) { *val = 0; } else { /* 5 bit value */ *val = (tmp & 0x1f) << (sizeof(*val) * 8 - 5); *val = ~*val; *val &= 0x1f << (sizeof(*val) * 8 - 5); } break; default: return -1; } return 0; } static int ac97_mixer_write(uint32_t knob_id, uint32_t val) { switch (knob_id) { case SND_KNOB_MASTER: { uint16_t encoded; if (val == 0x0) { encoded = 0x8000; } else { /* 0 is the highest volume */ val = ~val; /* 6 bit value */ val >>= (sizeof(val) * 8 - _device.bits); encoded = (val & 0xFF) | (val << 8); } outports(_device.nambar + AC97_MASTER_VOLUME, encoded); break; } case AC97_KNOB_PCM_OUT: { uint16_t encoded; if (val == 0x0) { encoded = 0x8000; } else { /* 0 is the highest volume */ val = ~val; /* 5 bit value */ val >>= (sizeof(val) * 8 - 5); encoded = (val & 0xFF) | (val << 8); } outports(_device.nambar + AC97_PCM_OUT_VOLUME, encoded); break; } default: return -1; } return 0; } static int ac97_install(int argc, char * argv[]) { //debug_print(NOTICE, "Initializing AC97"); pci_scan(&find_ac97, -1, &_device); if (!_device.pci_device) { return -ENODEV; } #if defined(__aarch64__) pci_write_field(_device.pci_device, PCI_COMMAND, 2, 0x5); pci_write_field(_device.pci_device, AC97_NABMBAR, 2, 0x1001); pci_write_field(_device.pci_device, PCI_BAR0, 4, 0x2001); _device._iobase = (volatile char *)mmu_map_mmio_region(0x3eff0000, 0x3000); asm volatile ("isb" ::: "memory"); #endif _device.nabmbar = pci_read_field(_device.pci_device, AC97_NABMBAR, 2) & ((uint32_t) -1) << 1; _device.nambar = pci_read_field(_device.pci_device, PCI_BAR0, 4) & ((uint32_t) -1) << 1; #if defined(__x86_64__) _device.irq = pci_get_interrupt(_device.pci_device); //printf("device wants irq %zd\n", _device.irq); irq_install_handler(_device.irq, ac97_irq_handler, "ac97"); #elif defined(__aarch64__) int irq; gic_map_pci_interrupt("ac97",_device.pci_device,&irq,ac97_irq_handler,&_device); _device.irq = irq; #endif /* Enable all matter of interrupts */ outportb(_device.nabmbar + AC97_PO_CR, AC97_X_CR_FEIE | AC97_X_CR_IOCE); /* Enable bus mastering and disable memory mapped space */ pci_write_field(_device.pci_device, PCI_COMMAND, 2, 0x5); /* Default the PCM output to full volume. */ outports(_device.nambar + AC97_PCM_OUT_VOLUME, 0x0000); /* Allocate our BDL and our buffers */ _device.bdl_p = mmu_allocate_a_frame() << 12; _device.bdl = mmu_map_from_physical(_device.bdl_p); memset(_device.bdl, 0, AC97_BDL_LEN * sizeof(*_device.bdl)); for (int i = 0; i < AC97_BDL_LEN; i++) { _device.bdl[i].pointer = mmu_allocate_n_frames(2) << 12; _device.bufs[i] = mmu_map_from_physical(_device.bdl[i].pointer); memset(_device.bufs[i], 0, AC97_BDL_BUFFER_LEN * sizeof(*_device.bufs[0])); AC97_CL_SET_LENGTH(_device.bdl[i].cl, AC97_BDL_BUFFER_LEN); /* Set all buffers to interrupt */ _device.bdl[i].cl |= AC97_CL_IOC; } /* Tell the ac97 where our BDL is */ outportl(_device.nabmbar + AC97_PO_BDBAR, _device.bdl_p); /* Set the LVI to be the last index */ _device.lvi = 2; outportb(_device.nabmbar + AC97_PO_LVI, _device.lvi); /* detect whether device supports MSB */ outports(_device.nambar + AC97_MASTER_VOLUME, 0x2020); uint16_t t = inports(_device.nambar + AC97_MASTER_VOLUME) & 0x1f; if (t == 0x1f) { //debug_print(WARNING, "This device only supports 5 bits of audio volume."); _device.bits = 5; _device.mask = 0x1f; } else { _device.bits = 6; _device.mask = 0x3f; } outports(_device.nambar + AC97_MASTER_VOLUME, 0x0000); snd_register(&_snd); /* Start things playing */ outportb(_device.nabmbar + AC97_PO_CR, inportb(_device.nabmbar + AC97_PO_CR) | AC97_X_CR_RPBM); //debug_print(NOTICE, "AC97 initialized successfully"); return 0; } static int fini(void) { snd_unregister(&_snd); free(_device.bdl); for (int i = 0; i < AC97_BDL_LEN; i++) { free(_device.bufs[i]); } return 0; } struct Module metadata = { .name = "ac97", .init = ac97_install, .fini = fini, }; ================================================ FILE: modules/ahci.c ================================================ /** * @brief AHCI Block Device Driver * @file modules/ahci.c * @package x86_64 * * @warning This is a stub driver. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include static uint32_t mmio_read4(uintptr_t mmiobase, intptr_t offset) { volatile uint32_t * data = (volatile uint32_t *)(mmiobase + offset); return *data; } static void mmio_write4(uintptr_t mmiobase, intptr_t offset, uint32_t value) { volatile uint32_t * data = (volatile uint32_t *)(mmiobase + offset); *data = value; } static char * ahci_device_name(uint32_t pcidev, int port) { static char buf[20]; snprintf(buf,19,"ahcip%ds%dp%d", (int)pci_extract_bus(pcidev), (int)pci_extract_slot(pcidev), port); return buf; } #define AHCI_PXCMD_ST (1 << 0UL) #define AHCI_PXCMD_SUD (1 << 1UL) #define AHCI_PXCMD_POD (1 << 2UL) #define AHCI_PXCMD_CLO (1 << 3UL) #define AHCI_PXCMD_FRE (1 << 4UL) #define AHCI_PXCMD_MPSS (1 << 13UL) #define AHCI_PXCMD_FR (1 << 14UL) #define AHCI_PXCMD_CR (1 << 15UL) #define DPRINT(fmt,...) fprintf(stderr, "%s: " fmt, ahci_device_name(pcidev,port), ##__VA_ARGS__) static void ahci_setup_atapi(fs_node_t * stderr, uint32_t pcidev, uintptr_t mmio_addr, int port) { intptr_t offset = 0x100 + port * 0x80; DPRINT("setting up ATAPI device\n"); uint32_t PxCMD = mmio_read4(mmio_addr, offset + 0x18); DPRINT("device cmd: %#x\n", PxCMD); if (PxCMD & AHCI_PXCMD_ST) DPRINT(" started (not idle!)\n"); if (PxCMD & AHCI_PXCMD_FRE) DPRINT(" FIS receive enable (not idle!)\n"); if (PxCMD & AHCI_PXCMD_FR) DPRINT(" FIS receive running (not idle!)\n"); if (PxCMD & AHCI_PXCMD_CR) DPRINT(" command list running (not idle!)\n"); if (PxCMD & (AHCI_PXCMD_ST | AHCI_PXCMD_FRE | AHCI_PXCMD_FR | AHCI_PXCMD_CR)) { DPRINT("Not idle, setting to idle state...\n"); PxCMD &= ~(AHCI_PXCMD_ST); mmio_write4(mmio_addr, offset + 0x18, PxCMD); DPRINT("Waiting for device...\n"); while (mmio_read4(mmio_addr, offset + 0x18) & AHCI_PXCMD_CR); DPRINT("Device is stopped.\n"); } } static void find_ahci(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) { if (pci_find_type(device) != 0x0106) return; /* Mass Storage, SATA controller */ if (pci_read_field(device, PCI_PROG_IF, 1) != 0x01) return; /* AHCI */ fs_node_t * stderr = extra; fprintf(stderr, "ahci: located device at %#x\n", device); uint16_t command_reg = pci_read_field(device, PCI_COMMAND, 2); command_reg |= (1 << 2); command_reg |= (1 << 1); command_reg ^= (1 << 10); pci_write_field(device, PCI_COMMAND, 2, command_reg); fprintf(stderr, "ahci: examining PCI config space...\n"); fprintf(stderr, "ahci: interrupt line = %d\n", pci_get_interrupt(device)); fprintf(stderr, "ahci: BAR5 = %#x\n", pci_read_field(device, PCI_BAR5, 4)); uintptr_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 */ fprintf(stderr, "ahci: mapping mmio to %#zx\n", mmio_addr); uint32_t enabledPorts = mmio_read4(mmio_addr, 0x0C); fprintf(stderr, "ahci: implemented ports = %#x\n", enabledPorts); uint32_t ahciVersion = mmio_read4(mmio_addr, 0x10); fprintf(stderr, "ahci: version %d.%d%d\n", (ahciVersion >> 16) & 0xFFF, (ahciVersion >> 8) & 0xFF, (ahciVersion) & 0xFF); fprintf(stderr, "ahci: Telling host controller we are aware of it.\n"); mmio_write4(mmio_addr, 0x04, mmio_read4(mmio_addr, 0x04) | (1 << 31UL)); int offset = 0x100; for (int port = 0; port < 32; ++port) { if (enabledPorts & (1UL << port)) { /* Check status */ uint32_t portSig = mmio_read4(mmio_addr, offset + 0x24); uint32_t portStatus = mmio_read4(mmio_addr, offset + 0x28); fprintf(stderr, "ahci: port %d: status = %#x\n", port, portStatus); fprintf(stderr, "ahci: port %d: sig = %#x\n", port, portSig); switch (portSig) { case 0xeb140101: fprintf(stderr, "ahci: ATAPI (CD, DVD)\n"); ahci_setup_atapi(stderr, device, mmio_addr, port); break; case 0x00000101: fprintf(stderr, "ahci: hard disk\n"); break; case 0xffff0101: fprintf(stderr, "ahci: no device\n"); break; default: fprintf(stderr, "ahci: unsupported/unknown\n"); break; } } offset += 0x80; } } static int init(int argc, char * argv[]) { fs_node_t * node = FD_ENTRY(1); /* Get the stdout for the process that loaded the module */ pci_scan(find_ahci, -1, node); return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "ahci", .init = init, .fini = fini, }; ================================================ FILE: modules/ata.c ================================================ /** * @brief (P)ATA / IDE disk driver * @file modules/ata.c * @package x86_64 * * @warning This is very buggy. * * This is a port of the original ATA driver for toaru32. * It has a number of issues. It should not be considered * stable, or a viable reference. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ATA_SR_BSY 0x80 #define ATA_SR_DRDY 0x40 #define ATA_SR_DF 0x20 #define ATA_SR_DSC 0x10 #define ATA_SR_DRQ 0x08 #define ATA_SR_CORR 0x04 #define ATA_SR_IDX 0x02 #define ATA_SR_ERR 0x01 #define ATA_ER_BBK 0x80 #define ATA_ER_UNC 0x40 #define ATA_ER_MC 0x20 #define ATA_ER_IDNF 0x10 #define ATA_ER_MCR 0x08 #define ATA_ER_ABRT 0x04 #define ATA_ER_TK0NF 0x02 #define ATA_ER_AMNF 0x01 #define ATA_CMD_READ_PIO 0x20 #define ATA_CMD_READ_PIO_EXT 0x24 #define ATA_CMD_READ_DMA 0xC8 #define ATA_CMD_READ_DMA_EXT 0x25 #define ATA_CMD_WRITE_PIO 0x30 #define ATA_CMD_WRITE_PIO_EXT 0x34 #define ATA_CMD_WRITE_DMA 0xCA #define ATA_CMD_WRITE_DMA_EXT 0x35 #define ATA_CMD_CACHE_FLUSH 0xE7 #define ATA_CMD_CACHE_FLUSH_EXT 0xEA #define ATA_CMD_PACKET 0xA0 #define ATA_CMD_IDENTIFY_PACKET 0xA1 #define ATA_CMD_IDENTIFY 0xEC #define ATAPI_CMD_READ 0xA8 #define ATAPI_CMD_EJECT 0x1B #define ATA_IDENT_DEVICETYPE 0 #define ATA_IDENT_CYLINDERS 2 #define ATA_IDENT_HEADS 6 #define ATA_IDENT_SECTORS 12 #define ATA_IDENT_SERIAL 20 #define ATA_IDENT_MODEL 54 #define ATA_IDENT_CAPABILITIES 98 #define ATA_IDENT_FIELDVALID 106 #define ATA_IDENT_MAX_LBA 120 #define ATA_IDENT_COMMANDSETS 164 #define ATA_IDENT_MAX_LBA_EXT 200 #define IDE_ATA 0x00 #define IDE_ATAPI 0x01 #define ATA_MASTER 0x00 #define ATA_SLAVE 0x01 #define ATA_REG_DATA 0x00 #define ATA_REG_ERROR 0x01 #define ATA_REG_FEATURES 0x01 #define ATA_REG_SECCOUNT0 0x02 #define ATA_REG_LBA0 0x03 #define ATA_REG_LBA1 0x04 #define ATA_REG_LBA2 0x05 #define ATA_REG_HDDEVSEL 0x06 #define ATA_REG_COMMAND 0x07 #define ATA_REG_STATUS 0x07 #define ATA_REG_SECCOUNT1 0x08 #define ATA_REG_LBA3 0x09 #define ATA_REG_LBA4 0x0A #define ATA_REG_LBA5 0x0B #define ATA_REG_CONTROL 0x0C #define ATA_REG_ALTSTATUS 0x0C #define ATA_REG_DEVADDRESS 0x0D // Channels: #define ATA_PRIMARY 0x00 #define ATA_SECONDARY 0x01 // Directions: #define ATA_READ 0x00 #define ATA_WRITE 0x01 typedef struct { uint16_t base; uint16_t ctrl; uint16_t bmide; uint16_t nien; } ide_channel_regs_t; typedef struct { uint8_t reserved; uint8_t channel; uint8_t drive; uint16_t type; uint16_t signature; uint16_t capabilities; uint32_t command_sets; uint32_t size; uint8_t model[41]; } ide_device_t; typedef struct { uint16_t flags; uint16_t unused1[9]; char serial[20]; uint16_t unused2[3]; char firmware[8]; char model[40]; uint16_t sectors_per_int; uint16_t unused3; uint16_t capabilities[2]; uint16_t unused4[2]; uint16_t valid_ext_data; uint16_t unused5[5]; uint16_t size_of_rw_mult; uint32_t sectors_28; uint16_t unused6[38]; uint64_t sectors_48; uint16_t unused7[152]; } __attribute__((packed)) ata_identify_t; static char ata_drive_char = 'a'; static int cdrom_number = 0; static uint32_t ata_pci = 0x00000000; static list_t * atapi_waiter; static int found_something = 0; typedef union { uint8_t command_bytes[12]; uint16_t command_words[6]; } atapi_command_t; /* 8086:7010 */ static void find_ata_pci(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) { if ((vendorid == 0x8086) && (deviceid == 0x7010 || deviceid == 0x7111)) { *((uint32_t *)extra) = device; } } typedef struct { uintptr_t offset; uint16_t bytes; uint16_t last; } prdt_t; struct ata_device { int io_base; int control; int slave; int is_atapi; ata_identify_t identity; prdt_t * dma_prdt; uintptr_t dma_prdt_phys; uint8_t * dma_start; uintptr_t dma_start_phys; uint32_t bar4; uint32_t atapi_lba; uint32_t atapi_sector_size; }; static struct ata_device ata_primary_master = {.io_base = 0x1F0, .control = 0x3F6, .slave = 0}; static struct ata_device ata_primary_slave = {.io_base = 0x1F0, .control = 0x3F6, .slave = 1}; static struct ata_device ata_secondary_master = {.io_base = 0x170, .control = 0x376, .slave = 0}; static struct ata_device ata_secondary_slave = {.io_base = 0x170, .control = 0x376, .slave = 1}; static spin_lock_t atapi_cmd_lock = { 0 }; /* TODO support other sector sizes */ #define ATA_SECTOR_SIZE 512 #define ATA_CACHE_SIZE 4096 #define SECTORS_PER_CACHE_BLOCK 8 static void ata_device_read_sector(struct ata_device * dev, uint64_t lba, uint8_t * buf); static void ata_device_read_sector_atapi(struct ata_device * dev, uint64_t lba, uint8_t * buf); static void ata_device_write_sector(struct ata_device * dev, uint64_t lba, uint8_t * buf); static void ata_device_write_sector_actual(struct ata_device * dev, uint64_t lba); struct CacheEntry { struct ata_device * dev; uint64_t lba; uint64_t last_use; uint64_t flags; }; #define CACHE_COUNT 4096 static uint64_t hit_count = 0; static uint64_t miss_count = 0; static uint64_t eviction_count = 0; static uint64_t write_count = 0; static sched_mutex_t * ata_mutex = NULL; static struct CacheEntry * cache_entries = NULL; static char * cache_blocks = NULL; static uint64_t counter = 1; static off_t ata_max_offset(struct ata_device * dev) { uint64_t sectors = dev->identity.sectors_48; if (!sectors) { /* Fall back to sectors_28 */ sectors = dev->identity.sectors_28; } return sectors * ATA_SECTOR_SIZE; } static off_t atapi_max_offset(struct ata_device * dev) { uint64_t max_sector = dev->atapi_lba; if (!max_sector) return 0; return (max_sector + 1) * dev->atapi_sector_size; } static ssize_t read_ata(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct ata_device * dev = (struct ata_device *)node->device; unsigned int start_block = offset / ATA_CACHE_SIZE; unsigned int end_block = (offset + size - 1) / ATA_CACHE_SIZE; unsigned int x_offset = 0; if (offset > ata_max_offset(dev)) { return 0; } if (offset + (ssize_t)size > ata_max_offset(dev)) { unsigned int i = ata_max_offset(dev) - offset; size = i; } if (offset % ATA_CACHE_SIZE || size < ATA_CACHE_SIZE) { unsigned int prefix_size = (ATA_CACHE_SIZE - (offset % ATA_CACHE_SIZE)); if (prefix_size > size) prefix_size = size; char * tmp = malloc(ATA_CACHE_SIZE); ata_device_read_sector(dev, start_block, (uint8_t *)tmp); memcpy(buffer, (void *)((uintptr_t)tmp + ((uintptr_t)offset % ATA_CACHE_SIZE)), prefix_size); free(tmp); x_offset += prefix_size; start_block++; } if ((offset + size) % ATA_CACHE_SIZE && start_block <= end_block) { unsigned int postfix_size = (offset + size) % ATA_CACHE_SIZE; char * tmp = malloc(ATA_CACHE_SIZE); ata_device_read_sector(dev, end_block, (uint8_t *)tmp); memcpy((void *)((uintptr_t)buffer + size - postfix_size), tmp, postfix_size); free(tmp); end_block--; } while (start_block <= end_block) { ata_device_read_sector(dev, start_block, (uint8_t *)((uintptr_t)buffer + x_offset)); x_offset += ATA_CACHE_SIZE; start_block++; } return size; } static ssize_t read_atapi(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct ata_device * dev = (struct ata_device *)node->device; unsigned int start_block = offset / dev->atapi_sector_size; unsigned int end_block = (offset + size - 1) / dev->atapi_sector_size; unsigned int x_offset = 0; if (offset > atapi_max_offset(dev)) { return 0; } if (offset + (ssize_t)size > atapi_max_offset(dev)) { unsigned int i = atapi_max_offset(dev) - offset; size = i; } if (offset % dev->atapi_sector_size || size < dev->atapi_sector_size) { unsigned int prefix_size = (dev->atapi_sector_size - (offset % dev->atapi_sector_size)); if (prefix_size > size) prefix_size = size; char * tmp = malloc(dev->atapi_sector_size); ata_device_read_sector_atapi(dev, start_block, (uint8_t *)tmp); memcpy(buffer, (void *)((uintptr_t)tmp + ((uintptr_t)offset % dev->atapi_sector_size)), prefix_size); free(tmp); x_offset += prefix_size; start_block++; } if ((offset + size) % dev->atapi_sector_size && start_block <= end_block) { unsigned int postfix_size = (offset + size) % dev->atapi_sector_size; char * tmp = malloc(dev->atapi_sector_size); ata_device_read_sector_atapi(dev, end_block, (uint8_t *)tmp); memcpy((void *)((uintptr_t)buffer + size - postfix_size), tmp, postfix_size); free(tmp); end_block--; } while (start_block <= end_block) { ata_device_read_sector_atapi(dev, start_block, (uint8_t *)((uintptr_t)buffer + x_offset)); x_offset += dev->atapi_sector_size; start_block++; } return size; } static ssize_t write_ata(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct ata_device * dev = (struct ata_device *)node->device; unsigned int start_block = offset / ATA_CACHE_SIZE; unsigned int end_block = (offset + size - 1) / ATA_CACHE_SIZE; unsigned int x_offset = 0; if (offset > ata_max_offset(dev)) { return 0; } if (offset + (ssize_t)size > ata_max_offset(dev)) { unsigned int i = ata_max_offset(dev) - offset; size = i; } if (offset % ATA_CACHE_SIZE) { unsigned int prefix_size = (ATA_CACHE_SIZE - (offset % ATA_CACHE_SIZE)); char * tmp = malloc(ATA_CACHE_SIZE); ata_device_read_sector(dev, start_block, (uint8_t *)tmp); memcpy((void *)((uintptr_t)tmp + ((uintptr_t)offset % ATA_CACHE_SIZE)), buffer, prefix_size); ata_device_write_sector(dev, start_block, (uint8_t *)tmp); free(tmp); x_offset += prefix_size; start_block++; } if ((offset + size) % ATA_CACHE_SIZE && start_block <= end_block) { unsigned int postfix_size = (offset + size) % ATA_CACHE_SIZE; char * tmp = malloc(ATA_CACHE_SIZE); ata_device_read_sector(dev, end_block, (uint8_t *)tmp); memcpy(tmp, (void *)((uintptr_t)buffer + size - postfix_size), postfix_size); ata_device_write_sector(dev, end_block, (uint8_t *)tmp); free(tmp); end_block--; } while (start_block <= end_block) { ata_device_write_sector(dev, start_block, (uint8_t *)((uintptr_t)buffer + x_offset)); x_offset += ATA_CACHE_SIZE; start_block++; } return size; } static void open_ata(fs_node_t * node, unsigned int flags) { return; } static void close_ata(fs_node_t * node) { return; } static int ioctl_ata(fs_node_t * node, unsigned long request, void * argp) { struct ata_device * dev = (struct ata_device *)node->device; switch (request) { case IOCTLSYNC: { mutex_acquire(ata_mutex); for (int i = 0; i < CACHE_COUNT; ++i) { if (cache_entries[i].dev == dev && cache_entries[i].flags & 1) { eviction_count++; memcpy(cache_entries[i].dev->dma_start, cache_blocks + i * ATA_CACHE_SIZE, ATA_CACHE_SIZE); ata_device_write_sector_actual(cache_entries[i].dev, cache_entries[i].lba); cache_entries[i].flags = 0; } } mutex_release(ata_mutex); return 0; } case 0x2A01234UL: { uint64_t * args = argp; memcpy(&args[0], &hit_count, sizeof(uint64_t)); memcpy(&args[1], &miss_count, sizeof(uint64_t)); memcpy(&args[2], &eviction_count, sizeof(uint64_t)); memcpy(&args[3], &write_count, sizeof(uint64_t)); return 0; } default: return -EINVAL; } } static fs_node_t * atapi_device_create(struct ata_device * device) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; snprintf(fnode->name, 20, "cdrom%d", cdrom_number); fnode->device = device; fnode->uid = 0; fnode->gid = 0; fnode->mask = 0664; fnode->length = atapi_max_offset(device); fnode->flags = FS_BLOCKDEVICE; fnode->read = read_atapi; fnode->write = NULL; /* no write support */ fnode->open = open_ata; fnode->close = close_ata; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = NULL; /* TODO, identify, etc? */ return fnode; } static fs_node_t * ata_device_create(struct ata_device * device) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; snprintf(fnode->name, 10, "atadev%d", ata_drive_char - 'a'); fnode->device = device; fnode->uid = 0; fnode->gid = 0; fnode->mask = 0660; fnode->length = ata_max_offset(device); /* TODO */ fnode->flags = FS_BLOCKDEVICE; fnode->read = read_ata; fnode->write = write_ata; fnode->open = open_ata; fnode->close = close_ata; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = ioctl_ata; /* TODO, identify, etc? */ return fnode; } static void ata_io_wait(struct ata_device * dev) { inportb(dev->io_base + ATA_REG_ALTSTATUS); inportb(dev->io_base + ATA_REG_ALTSTATUS); inportb(dev->io_base + ATA_REG_ALTSTATUS); inportb(dev->io_base + ATA_REG_ALTSTATUS); } static int ata_status_wait(struct ata_device * dev, int timeout) { int status; if (timeout > 0) { int i = 0; while ((status = inportb(dev->io_base + ATA_REG_STATUS)) & ATA_SR_BSY && (i < timeout)) i++; } else { while ((status = inportb(dev->io_base + ATA_REG_STATUS)) & ATA_SR_BSY); } return status; } static int ata_wait(struct ata_device * dev, int advanced) { uint8_t status = 0; ata_io_wait(dev); status = ata_status_wait(dev, -1); if (advanced) { status = inportb(dev->io_base + ATA_REG_STATUS); if (status & ATA_SR_ERR) return 1; if (status & ATA_SR_DF) return 1; if (!(status & ATA_SR_DRQ)) return 1; } return 0; } static void ata_soft_reset(struct ata_device * dev) { outportb(dev->control, 0x04); ata_io_wait(dev); outportb(dev->control, 0x00); } static int ata_irq_handler(struct regs *r) { struct ata_device * dev = r->int_no == 14 ? &ata_primary_master : &ata_secondary_master; inportb(dev->io_base + ATA_REG_STATUS); spin_lock(atapi_cmd_lock); wakeup_queue(atapi_waiter); spin_unlock(atapi_cmd_lock); irq_ack(r->int_no); return 1; } static void * kvmalloc_p(size_t size, uintptr_t * outphys) { uintptr_t index = mmu_allocate_n_frames(size / 0x1000) << 12; *outphys = index; return mmu_map_from_physical(index); } static void ata_device_init(struct ata_device * dev) { outportb(dev->io_base + 1, 1); outportb(dev->control, 0); outportb(dev->io_base + ATA_REG_HDDEVSEL, 0xA0 | dev->slave << 4); ata_io_wait(dev); outportb(dev->io_base + ATA_REG_COMMAND, ATA_CMD_IDENTIFY); ata_io_wait(dev); inportb(dev->io_base + ATA_REG_COMMAND); ata_wait(dev, 0); uint16_t * buf = (uint16_t *)&dev->identity; for (int i = 0; i < 256; ++i) { buf[i] = inports(dev->io_base); } uint8_t * ptr = (uint8_t *)&dev->identity.model; for (int i = 0; i < 39; i+=2) { uint8_t tmp = ptr[i+1]; ptr[i+1] = ptr[i]; ptr[i] = tmp; } dev->is_atapi = 0; dev->dma_prdt = (void *)kvmalloc_p(4096, &dev->dma_prdt_phys); dev->dma_start = (void *)kvmalloc_p(4096, &dev->dma_start_phys); dev->dma_prdt[0].offset = dev->dma_start_phys; dev->dma_prdt[0].bytes = ATA_CACHE_SIZE; dev->dma_prdt[0].last = 0x8000; uint16_t command_reg = pci_read_field(ata_pci, PCI_COMMAND, 4); if (!(command_reg & (1 << 2))) { command_reg |= (1 << 2); /* bit 2 */ pci_write_field(ata_pci, PCI_COMMAND, 4, command_reg); command_reg = pci_read_field(ata_pci, PCI_COMMAND, 4); } dev->bar4 = pci_read_field(ata_pci, PCI_BAR4, 4); if (dev->bar4 & 0x00000001) { dev->bar4 = dev->bar4 & 0xFFFFFFFC; } else { return; /* No DMA because we're not sure what to do here */ } } static int atapi_device_init(struct ata_device * dev) { dev->is_atapi = 1; outportb(dev->io_base + 1, 1); outportb(dev->control, 0); outportb(dev->io_base + ATA_REG_HDDEVSEL, 0xA0 | dev->slave << 4); ata_io_wait(dev); outportb(dev->io_base + ATA_REG_COMMAND, ATA_CMD_IDENTIFY_PACKET); ata_io_wait(dev); inportb(dev->io_base + ATA_REG_COMMAND); ata_wait(dev, 0); uint16_t * buf = (uint16_t *)&dev->identity; for (int i = 0; i < 256; ++i) { buf[i] = inports(dev->io_base); } uint8_t * ptr = (uint8_t *)&dev->identity.model; for (int i = 0; i < 39; i+=2) { uint8_t tmp = ptr[i+1]; ptr[i+1] = ptr[i]; ptr[i] = tmp; } /* Detect medium */ atapi_command_t command; command.command_bytes[0] = 0x25; command.command_bytes[1] = 0; command.command_bytes[2] = 0; command.command_bytes[3] = 0; command.command_bytes[4] = 0; command.command_bytes[5] = 0; command.command_bytes[6] = 0; command.command_bytes[7] = 0; command.command_bytes[8] = 0; /* bit 0 = PMI (0, last sector) */ command.command_bytes[9] = 0; /* control */ command.command_bytes[10] = 0; command.command_bytes[11] = 0; uint16_t bus = dev->io_base; outportb(bus + ATA_REG_FEATURES, 0x00); outportb(bus + ATA_REG_LBA1, 0x08); outportb(bus + ATA_REG_LBA2, 0x08); outportb(bus + ATA_REG_COMMAND, ATA_CMD_PACKET); /* poll */ int timeout = 100; while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if ((status & ATA_SR_ERR)) goto atapi_error; if (timeout-- < 0) goto atapi_timeout; if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break; } for (int i = 0; i < 6; ++i) { outports(bus, command.command_words[i]); } /* poll */ timeout = 100; while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if ((status & ATA_SR_ERR)) goto atapi_error_read; if (timeout-- < 0) goto atapi_timeout; if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break; if ((status & ATA_SR_DRQ)) break; } uint16_t data[4]; for (int i = 0; i < 4; ++i) { data[i] = inports(bus); } #define htonl(l) ( (((l) & 0xFF) << 24) | (((l) & 0xFF00) << 8) | (((l) & 0xFF0000) >> 8) | (((l) & 0xFF000000) >> 24)) uint32_t lba, blocks;; memcpy(&lba, &data[0], sizeof(uint32_t)); lba = htonl(lba); memcpy(&blocks, &data[2], sizeof(uint32_t)); blocks = htonl(blocks); dev->atapi_lba = lba; dev->atapi_sector_size = blocks; if (!lba) return 1; return 0; atapi_error_read: return 1; atapi_error: return 1; atapi_timeout: return 1; } static int ata_device_detect(struct ata_device * dev) { ata_soft_reset(dev); ata_io_wait(dev); outportb(dev->io_base + ATA_REG_HDDEVSEL, 0xA0 | dev->slave << 4); ata_io_wait(dev); ata_status_wait(dev, 10000); unsigned char cl = inportb(dev->io_base + ATA_REG_LBA1); /* CYL_LO */ unsigned char ch = inportb(dev->io_base + ATA_REG_LBA2); /* CYL_HI */ if (cl == 0xFF && ch == 0xFF) { /* Nothing here */ return 0; } if ((cl == 0x00 && ch == 0x00) || (cl == 0x3C && ch == 0xC3)) { /* Parallel ATA device, or emulated SATA */ ata_device_init(dev); off_t sectors = ata_max_offset(dev); if (sectors == 0) { return 0; } char devname[64]; snprintf((char *)&devname, 20, "/dev/hd%c", ata_drive_char); fs_node_t * node = ata_device_create(dev); char options[21]; snprintf(options, 20, "%c", ata_drive_char); vfs_mount(devname, node, "ata-hd", options); node->length = sectors; ata_drive_char++; found_something = 1; return 1; } else if ((cl == 0x14 && ch == 0xEB) || (cl == 0x69 && ch == 0x96)) { char devname[64]; snprintf((char *)&devname, 20, "/dev/cdrom%d", cdrom_number); if (atapi_device_init(dev)) { return 0; } fs_node_t * node = atapi_device_create(dev); char options[21]; snprintf(options, 20, "%d", cdrom_number); vfs_mount(devname, node, "atapi-cdrom", options); cdrom_number++; found_something = 1; return 2; } /* TODO: ATAPI, SATA, SATAPI */ return 0; } static void ata_device_read_sector_actual(struct ata_device * dev, uint64_t lba) { uint16_t bus = dev->io_base; uint8_t slave = dev->slave; if (dev->is_atapi) return; ata_wait(dev, 0); /* Stop */ outportb(dev->bar4, 0x00); /* Set the PRDT */ outportl(dev->bar4 + 0x04, dev->dma_prdt_phys); /* Enable error, irq status */ outportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02); /* set read */ outportb(dev->bar4, 0x08); while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if (!(status & ATA_SR_BSY)) break; } outportb(bus + ATA_REG_CONTROL, 0x00); outportb(bus + ATA_REG_HDDEVSEL, 0xe0 | slave << 4); ata_io_wait(dev); outportb(bus + ATA_REG_FEATURES, 0x00); outportb(bus + ATA_REG_SECCOUNT0, 0); outportb(bus + ATA_REG_LBA0, (lba & 0xff000000) >> 24); outportb(bus + ATA_REG_LBA1, (lba & 0xff00000000) >> 32); outportb(bus + ATA_REG_LBA2, (lba & 0xff0000000000) >> 40); outportb(bus + ATA_REG_SECCOUNT0, SECTORS_PER_CACHE_BLOCK); outportb(bus + ATA_REG_LBA0, (lba & 0x000000ff) >> 0); outportb(bus + ATA_REG_LBA1, (lba & 0x0000ff00) >> 8); outportb(bus + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16); while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break; } spin_lock(atapi_cmd_lock); outportb(bus + ATA_REG_COMMAND, ATA_CMD_READ_DMA_EXT); ata_io_wait(dev); outportb(dev->bar4, 0x08 | 0x01); sleep_on_unlocking(atapi_waiter, &atapi_cmd_lock); while (1) { int status = inportb(dev->bar4 + 0x02); int dstatus = inportb(dev->io_base + ATA_REG_STATUS); if (!(status & 0x04)) { continue; } if (!(dstatus & ATA_SR_BSY)) { break; } } /* Inform device we are done. */ outportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02); } static void ata_device_read_sector_atapi_actual(struct ata_device * dev, uint64_t lba, uint8_t * buf) { if (!dev->is_atapi) return; uint16_t bus = dev->io_base; outportb(dev->io_base + ATA_REG_HDDEVSEL, 0xA0 | dev->slave << 4); ata_io_wait(dev); outportb(bus + ATA_REG_FEATURES, 0x00); outportb(bus + ATA_REG_LBA1, dev->atapi_sector_size & 0xFF); outportb(bus + ATA_REG_LBA2, dev->atapi_sector_size >> 8); outportb(bus + ATA_REG_COMMAND, ATA_CMD_PACKET); /* poll */ int timeout = 100; while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if ((status & ATA_SR_ERR)) goto atapi_error_on_read_setup; if (timeout-- < 0) { dprintf("atapi: timeout while waiting for controller to be ready\n"); goto atapi_timeout; } if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break; } atapi_command_t command; command.command_bytes[0] = 0xA8; command.command_bytes[1] = 0; command.command_bytes[2] = (lba >> 0x18) & 0xFF; command.command_bytes[3] = (lba >> 0x10) & 0xFF; command.command_bytes[4] = (lba >> 0x08) & 0xFF; command.command_bytes[5] = (lba >> 0x00) & 0xFF; command.command_bytes[6] = 0; command.command_bytes[7] = 0; command.command_bytes[8] = 0; /* bit 0 = PMI (0, last sector) */ command.command_bytes[9] = 1; /* control */ command.command_bytes[10] = 0; command.command_bytes[11] = 0; spin_lock(atapi_cmd_lock); for (int i = 0; i < 6; ++i) { outports(bus, command.command_words[i]); } sleep_on_unlocking(atapi_waiter, &atapi_cmd_lock); while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if ((status & ATA_SR_ERR)) goto atapi_error_on_read_setup; if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRQ)) break; } uint16_t size_to_read = inportb(bus + ATA_REG_LBA2) << 8; size_to_read = size_to_read | inportb(bus + ATA_REG_LBA1); inportsm(bus,buf,size_to_read/2); timeout = 1000; while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if ((status & ATA_SR_ERR)) goto atapi_error_on_read_setup; if (timeout-- < 0) { dprintf("atapi: timeout while waiting for controller to be ready after transaction\n"); goto atapi_timeout; } if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break; } atapi_error_on_read_setup: return; atapi_timeout: return; } static void ata_device_write_sector_actual(struct ata_device * dev, uint64_t lba) { uint16_t bus = dev->io_base; uint8_t slave = dev->slave; ata_wait(dev, 0); outportb(dev->bar4, 0x00); outportl(dev->bar4 + 0x04, dev->dma_prdt_phys); outportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02); /* set write */ outportb(dev->bar4, 0x00); while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if (!(status & ATA_SR_BSY)) break; } outportb(bus + ATA_REG_CONTROL, 0x02); outportb(bus + ATA_REG_HDDEVSEL, 0xe0 | slave << 4); ata_wait(dev, 0); outportb(bus + ATA_REG_FEATURES, 0x00); outportb(bus + ATA_REG_SECCOUNT0, 0); outportb(bus + ATA_REG_LBA0, (lba & 0xff000000) >> 24); outportb(bus + ATA_REG_LBA1, (lba & 0xff00000000) >> 32); outportb(bus + ATA_REG_LBA2, (lba & 0xff0000000000) >> 40); outportb(bus + ATA_REG_SECCOUNT0, SECTORS_PER_CACHE_BLOCK); outportb(bus + ATA_REG_LBA0, (lba & 0x000000ff) >> 0); outportb(bus + ATA_REG_LBA1, (lba & 0x0000ff00) >> 8); outportb(bus + ATA_REG_LBA2, (lba & 0x00ff0000) >> 16); while (1) { uint8_t status = inportb(dev->io_base + ATA_REG_STATUS); if (!(status & ATA_SR_BSY) && (status & ATA_SR_DRDY)) break; } spin_lock(atapi_cmd_lock); outportb(bus + ATA_REG_COMMAND, ATA_CMD_WRITE_DMA_EXT); ata_io_wait(dev); outportb(dev->bar4, 0x00 | 0x01); sleep_on_unlocking(atapi_waiter, &atapi_cmd_lock); #if 0 ata_wait(dev, 0); int size = ATA_SECTOR_SIZE / 2; outportsm(bus,buf,size); #endif outportb(dev->bar4 + 0x2, inportb(dev->bar4 + 0x02) | 0x04 | 0x02); #if 0 outportb(bus + 0x07, ATA_CMD_CACHE_FLUSH); ata_wait(dev, 0); #endif } static void ata_device_read_sector(struct ata_device * dev, uint64_t lba, uint8_t * buf) { lba *= SECTORS_PER_CACHE_BLOCK; /* Is it in the cache? */ mutex_acquire(ata_mutex); int found = 0; int oldest = 0; uint64_t lu = -1; for (int i = 0; i < CACHE_COUNT; ++i) { if (cache_entries[i].dev == dev && cache_entries[i].lba == lba) { //dprintf("ata: cache hit\n"); hit_count++; oldest = i; found = 1; break; } else if (cache_entries[i].dev == NULL) { oldest = i; break; } else if (cache_entries[i].last_use < lu) { oldest = i; lu = cache_entries[oldest].last_use; } } if (!found) { miss_count++; if (cache_entries[oldest].dev && cache_entries[oldest].flags & 1) { eviction_count++; memcpy(cache_entries[oldest].dev->dma_start, cache_blocks + oldest * ATA_CACHE_SIZE, ATA_CACHE_SIZE); ata_device_write_sector_actual(cache_entries[oldest].dev, cache_entries[oldest].lba); } ata_device_read_sector_actual(dev, lba); cache_entries[oldest].dev = dev; cache_entries[oldest].lba = lba; cache_entries[oldest].flags = 0; memcpy(cache_blocks + oldest * ATA_CACHE_SIZE, dev->dma_start, ATA_CACHE_SIZE); } cache_entries[oldest].last_use = counter++; memcpy(buf, cache_blocks + ATA_CACHE_SIZE * oldest, ATA_CACHE_SIZE); mutex_release(ata_mutex); } static void ata_device_write_sector(struct ata_device * dev, uint64_t lba, uint8_t * buf) { lba *= SECTORS_PER_CACHE_BLOCK; mutex_acquire(ata_mutex); int found = 0; int oldest = 0; uint64_t lu = -1; for (int i = 0; i < CACHE_COUNT; ++i) { if (cache_entries[i].dev == dev && cache_entries[i].lba == lba) { //dprintf("ata: cache hit\n"); hit_count++; oldest = i; found = 1; break; } else if (cache_entries[i].dev == NULL) { oldest = i; break; } else if (cache_entries[i].last_use < lu) { oldest = i; lu = cache_entries[oldest].last_use; } } if (!found) { miss_count++; if (cache_entries[oldest].dev && cache_entries[oldest].flags & 1) { eviction_count++; memcpy(cache_entries[oldest].dev->dma_start, cache_blocks + oldest * ATA_CACHE_SIZE, ATA_CACHE_SIZE); ata_device_write_sector_actual(cache_entries[oldest].dev, cache_entries[oldest].lba); } cache_entries[oldest].dev = dev; cache_entries[oldest].lba = lba; } write_count++; cache_entries[oldest].last_use = counter++; memcpy(cache_blocks + oldest * ATA_CACHE_SIZE, buf, ATA_CACHE_SIZE); cache_entries[oldest].flags = 1; mutex_release(ata_mutex); } static void ata_device_read_sector_atapi(struct ata_device * dev, uint64_t lba, uint8_t * buf) { mutex_acquire(ata_mutex); ata_device_read_sector_atapi_actual(dev, lba, buf); mutex_release(ata_mutex); } static int ata_initialize(int argc, char * argv[]) { /* Detect drives and mount them */ /* Locate ATA device via PCI */ pci_scan(&find_ata_pci, -1, &ata_pci); irq_install_handler(14, ata_irq_handler, "ide master"); irq_install_handler(15, ata_irq_handler, "ide slave"); atapi_waiter = list_create("atapi waiter", NULL); cache_entries = malloc(sizeof(struct CacheEntry) * CACHE_COUNT); memset(cache_entries, 0, sizeof(struct CacheEntry) * CACHE_COUNT); cache_blocks = mmu_map_module(CACHE_COUNT * ATA_CACHE_SIZE); ata_mutex = mutex_init("ata lock"); ata_device_detect(&ata_primary_master); ata_device_detect(&ata_primary_slave); ata_device_detect(&ata_secondary_master); ata_device_detect(&ata_secondary_slave); return 0; } static int ata_finalize(void) { return 0; } struct Module metadata = { .name = "ata", .init = ata_initialize, .fini = ata_finalize, }; ================================================ FILE: modules/dospart.c ================================================ /** * @file modules/dospart.c * @brief DOS MBR partition table mapper * @package x86_64 * @package aarch64 * * Provides partition entries for disks. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #define SECTORSIZE 512 typedef struct { uint8_t status; uint8_t chs_first_sector[3]; uint8_t type; uint8_t chs_last_sector[3]; uint32_t lba_first_sector; uint32_t sector_count; } partition_t; typedef struct { uint8_t boostrap[446]; partition_t partitions[4]; uint8_t signature[2]; } __attribute__((packed)) mbr_t; struct dos_partition_entry { fs_node_t * device; partition_t partition; }; static ssize_t read_part(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct dos_partition_entry * device = (struct dos_partition_entry *)node->device; if ((size_t)offset > device->partition.sector_count * SECTORSIZE) { return 0; } if (offset + size > device->partition.sector_count * SECTORSIZE) { size = device->partition.sector_count * SECTORSIZE - offset; } return read_fs(device->device, offset + device->partition.lba_first_sector * SECTORSIZE, size, buffer); } static ssize_t write_part(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct dos_partition_entry * device = (struct dos_partition_entry *)node->device; if ((size_t)offset > device->partition.sector_count * SECTORSIZE) { return 0; } if (offset + size > device->partition.sector_count * SECTORSIZE) { size = device->partition.sector_count * SECTORSIZE - offset; } return write_fs(device->device, offset + device->partition.lba_first_sector * SECTORSIZE, size, buffer); } static void open_part(fs_node_t * node, unsigned int flags) { return; } static void close_part(fs_node_t * node) { return; } static fs_node_t * dospart_device_create(int i, fs_node_t * dev, mbr_t * mbr, int id) { vfs_lock(dev); struct dos_partition_entry * device = malloc(sizeof(struct dos_partition_entry)); memcpy(&device->partition, &mbr->partitions[id], sizeof(partition_t)); device->device = dev; fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); fnode->inode = 0; snprintf(fnode->name, 20, "dospart%d", i); fnode->device = device; fnode->uid = 0; fnode->gid = 0; fnode->mask = 0660; fnode->length = device->partition.sector_count * SECTORSIZE; /* TODO */ fnode->flags = FS_BLOCKDEVICE; fnode->read = read_part; fnode->write = write_part; fnode->open = open_part; fnode->close = close_part; fnode->readdir = NULL; fnode->finddir = NULL; fnode->ioctl = NULL; /* TODO, identify, etc? */ return fnode; } static fs_node_t * dospart_map(const char * device, const char * mount_path) { char * arg = strdup(device); char * argv[10]; tokenize(arg, ",", argv); fs_node_t * dev = kopen(argv[0], 0); if (!dev) { return NULL; } mbr_t mbr; read_fs(dev, 0, SECTORSIZE, (uint8_t *)&mbr); if (mbr.signature[0] == 0x55 && mbr.signature[1] == 0xAA) { for (int i = 0; i < 4; ++i) { if (mbr.partitions[i].status & 0x80) { fs_node_t * node = dospart_device_create(i, dev, &mbr, i); char tmp[64]; snprintf(tmp, 20, "%s%d", device, i); vfs_mount(tmp, node, "dospart", tmp); } } } /* VFS_MOUNT_PARTITION_MAPPER_SUCCESS? */ return (fs_node_t*)1; } static int dospart_initialize(int argc, char * argv[]) { vfs_register("mbr", dospart_map); return 0; } static int dospart_finalize(void) { return 0; } struct Module metadata = { .name = "dospart", .init = dospart_initialize, .fini = dospart_finalize, }; ================================================ FILE: modules/e1000.c ================================================ /** * @file kernel/net/e1000.c * @brief Intel Gigabit Ethernet device driver * @package x86_64 * @package aarch64 * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2017-2021 K. Lange * * @ref https://www.intel.com/content/dam/www/public/us/en/documents/manuals/pcie-gbe-controllers-open-source-manual.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__x86_64__) #include #elif defined(__aarch64__) #include #endif #include #include #include #define INTS (ICR_LSC | ICR_RXO | ICR_RXT0 | ICR_TXQE | ICR_TXDW | ICR_ACK | ICR_RXDMT0 | ICR_SRPD) struct e1000_nic { struct EthernetDevice eth; uint32_t pci_device; uint16_t deviceid; uintptr_t mmio_addr; int irq_number; int has_eeprom; int rx_index; int tx_index; int link_status; spin_lock_t tx_lock; uint8_t * rx_virt[E1000_NUM_RX_DESC]; uint8_t * tx_virt[E1000_NUM_TX_DESC]; volatile struct e1000_rx_desc * rx; volatile struct e1000_tx_desc * tx; uintptr_t rx_phys; uintptr_t tx_phys; int configured; process_t * queuer; process_t * processor; netif_counters_t counts; }; static int device_count = 0; static struct e1000_nic * devices[32] = {NULL}; #ifdef __aarch64__ static uint32_t mmio_read32(uintptr_t addr) { asm volatile ("dc ivac, %0\ndsb sy\nisb\n" :: "r"(addr) : "memory"); uint32_t res = *((volatile uint32_t*)(addr)); asm volatile ("dmb ish" ::: "memory"); return res; } static void mmio_write32(uintptr_t addr, uint32_t val) { (*((volatile uint32_t*)(addr))) = val; asm volatile ("dsb ishst\nisb\ndc cvac, %0\n" :: "r"(addr) : "memory"); } static void cache_invalidate(void *addr) { uintptr_t a = (uintptr_t)addr; for (uintptr_t x = 0; x < 4096; x += 64) { asm volatile ("dc ivac, %0\n" :: "r"(a + x) : "memory"); } asm volatile ("dsb sy\nisb":::"memory"); } static void cache_clean(void *addr) { uintptr_t a = (uintptr_t)addr; asm volatile ("dmb ish" ::: "memory"); for (uintptr_t x = 0; x < 4096; x += 64) { asm volatile ("dc cvac, %0" :: "r"(a + x) : "memory"); } asm volatile ("dsb sy\nisb":::"memory"); } #else static uint32_t mmio_read32(uintptr_t addr) { return *((volatile uint32_t*)(addr)); } static void mmio_write32(uintptr_t addr, uint32_t val) { (*((volatile uint32_t*)(addr))) = val; } #endif static void write_command(struct e1000_nic * device, uint16_t addr, uint32_t val) { mmio_write32(device->mmio_addr + addr, val); } static uint32_t read_command(struct e1000_nic * device, uint16_t addr) { return mmio_read32(device->mmio_addr + addr); } static void delay_yield(size_t subticks) { #ifdef __aarch64__ asm volatile ("isb" ::: "memory"); #endif unsigned long s, ss; relative_time(0, subticks, &s, &ss); sleep_until((process_t *)this_core->current_process, s, ss); switch_task(0); } static int eeprom_detect(struct e1000_nic * device) { /* Definitely not */ if (device->deviceid == 0x10d3) return 0; write_command(device, E1000_REG_EEPROM, 1); for (int i = 0; i < 10000 && !device->has_eeprom; ++i) { uint32_t val = read_command(device, E1000_REG_EEPROM); if (val & 0x10) device->has_eeprom = 1; } return 0; } static uint16_t eeprom_read(struct e1000_nic * device, uint8_t addr) { uint32_t temp = 0; write_command(device, E1000_REG_EEPROM, 1 | ((uint32_t)(addr) << 8)); while (!((temp = read_command(device, E1000_REG_EEPROM)) & (1 << 4))); return (uint16_t)((temp >> 16) & 0xFFFF); } static void write_mac(struct e1000_nic * device) { uint32_t low, high; memcpy(&low, &device->eth.mac[0], 4); memcpy(&high,&device->eth.mac[4], 2); memset((uint8_t *)&high + 2, 0, 2); high |= 0x80000000; write_command(device, E1000_REG_RXADDR + 0, low); write_command(device, E1000_REG_RXADDR + 4, high); } static void read_mac(struct e1000_nic * device) { if (device->has_eeprom) { uint32_t t; t = eeprom_read(device, 0); device->eth.mac[0] = t & 0xFF; device->eth.mac[1] = t >> 8; t = eeprom_read(device, 1); device->eth.mac[2] = t & 0xFF; device->eth.mac[3] = t >> 8; t = eeprom_read(device, 2); device->eth.mac[4] = t & 0xFF; device->eth.mac[5] = t >> 8; } else { uint32_t mac_addr_low = *(uint32_t *)(device->mmio_addr + E1000_REG_RXADDR); uint32_t mac_addr_high = *(uint32_t *)(device->mmio_addr + E1000_REG_RXADDR + 4); device->eth.mac[0] = (mac_addr_low >> 0 ) & 0xFF; device->eth.mac[1] = (mac_addr_low >> 8 ) & 0xFF; device->eth.mac[2] = (mac_addr_low >> 16) & 0xFF; device->eth.mac[3] = (mac_addr_low >> 24) & 0xFF; device->eth.mac[4] = (mac_addr_high>> 0 ) & 0xFF; device->eth.mac[5] = (mac_addr_high>> 8 ) & 0xFF; } } static void e1000_handle(struct e1000_nic * nic, uint32_t status) { write_command(nic, E1000_REG_ICR, status); if (!nic->configured) { return; } if (status & ICR_LSC) { nic->link_status= (read_command(nic, E1000_REG_STATUS) & (1 << 1)); } make_process_ready(nic->queuer); } static void e1000_queuer(void * data) { struct e1000_nic * nic = data; int head = read_command(nic, E1000_REG_RXDESCHEAD); int budget = 8; while (1) { int processed = 0; if (head == nic->rx_index) { head = read_command(nic, E1000_REG_RXDESCHEAD); } if (head != nic->rx_index) { #ifdef __aarch64__ __sync_synchronize(); #endif while ((nic->rx[nic->rx_index].status & 0x01) && (processed < budget)) { int i = nic->rx_index; if (!(nic->rx[i].errors & (0x97))) { nic->counts.rx_count++; nic->counts.rx_bytes += nic->rx[i].length; #ifdef __aarch64__ cache_invalidate(nic->rx_virt[i]); #endif net_eth_handle((void*)nic->rx_virt[i], nic->eth.device_node, nic->rx[i].length); } else { printf("error bits set in packet: %x\n", nic->rx[i].errors); } processed++; #ifdef __aarch64__ __sync_synchronize(); #endif nic->rx[i].status = 0; if (++nic->rx_index == E1000_NUM_RX_DESC) { nic->rx_index = 0; } if (nic->rx_index == head) { head = read_command(nic, E1000_REG_RXDESCHEAD); if (nic->rx_index == head) break; } write_command(nic, E1000_REG_RXDESCTAIL, nic->rx_index); read_command(nic, E1000_REG_STATUS); #ifdef __aarch64__ __sync_synchronize(); #endif } } if (processed == 0) { delay_yield(100000); } else { if (this_core->cpu_id == 0) switch_task(1); } } } #if defined(__x86_64__) static int irq_handler(struct regs *r) { int irq = r->int_no - 32; #elif defined(__aarch64__) static int e1000_irq_handler(process_t * this, int irq, void * data) { #endif int handled = 0; for (int i = 0; i < device_count; ++i) { if (devices[i]->irq_number == irq) { uint32_t status = read_command(devices[i], E1000_REG_ICR); if (status) { e1000_handle(devices[i], status); if (!handled) { handled = 1; #if defined(__x86_64__) irq_ack(irq); #endif } } } } return handled; } static int tx_full(struct e1000_nic * device, int tx_tail, int tx_head) { if (tx_tail == tx_head) return 0; if (device->tx_index == tx_head) return 1; if (((device->tx_index + 1) & E1000_NUM_TX_DESC) == tx_head) return 1; return 0; } static void send_packet(struct e1000_nic * device, uint8_t* payload, size_t payload_size) { spin_lock(device->tx_lock); int tx_tail = read_command(device, E1000_REG_TXDESCTAIL); int tx_head = read_command(device, E1000_REG_TXDESCHEAD); if (tx_full(device, tx_tail, tx_head)) { int timeout = 1000; do { spin_unlock(device->tx_lock); delay_yield(10000); timeout--; if (timeout == 0) { printf("e1000: wait for tx timed out, giving up\n"); return; } spin_lock(device->tx_lock); tx_tail = read_command(device, E1000_REG_TXDESCTAIL); tx_head = read_command(device, E1000_REG_TXDESCHEAD); } while (tx_full(device, tx_tail, tx_head)); } int sent = device->tx_index; memcpy(device->tx_virt[device->tx_index], payload, payload_size); #if defined(__aarch64__) asm volatile ("dmb ish\nisb" ::: "memory"); cache_clean(device->tx_virt[device->tx_index]); #endif device->tx[device->tx_index].length = payload_size; device->tx[device->tx_index].cmd = CMD_EOP | CMD_IFCS | CMD_RS | CMD_RPS; device->tx[device->tx_index].status = 0; #if defined(__aarch64__) asm volatile ("dmb ish\nisb" ::: "memory"); #endif device->counts.tx_count++; device->counts.tx_bytes += payload_size; if (++device->tx_index == E1000_NUM_TX_DESC) { device->tx_index = 0; } write_command(device, E1000_REG_TXDESCTAIL, device->tx_index); int st = read_command(device, E1000_REG_STATUS); (void)st; #if defined(__aarch64__) asm volatile ("dc ivac, %0\ndsb sy\n" :: "r"(&device->tx[sent]) : "memory"); #else (void)sent; #endif spin_unlock(device->tx_lock); } static void init_rx(struct e1000_nic * device) { write_command(device, E1000_REG_RXDESCLO, device->rx_phys); write_command(device, E1000_REG_RXDESCHI, 0); write_command(device, E1000_REG_RXDESCLEN, E1000_NUM_RX_DESC * sizeof(struct e1000_rx_desc)); write_command(device, E1000_REG_RXDESCHEAD, 0); write_command(device, E1000_REG_RXDESCTAIL, E1000_NUM_RX_DESC - 1); device->rx_index = 0; write_command(device, E1000_REG_RCTRL, RCTL_EN | (1 << 2) | /* store bad packets */ (1 << 4) | /* multicast promiscuous */ (1 << 15) | /* broadcast accept */ (1 << 25) | /* Extended size... */ (3 << 16) | /* 4096 */ (1 << 26) /* strip CRC */ ); } static void init_tx(struct e1000_nic * device) { write_command(device, E1000_REG_TXDESCLO, device->tx_phys); write_command(device, E1000_REG_TXDESCHI, 0); write_command(device, E1000_REG_TXDESCLEN, E1000_NUM_TX_DESC * sizeof(struct e1000_tx_desc)); write_command(device, E1000_REG_TXDESCHEAD, 0); write_command(device, E1000_REG_TXDESCTAIL, 0); device->tx_index = 0; uint32_t tctl = read_command(device, E1000_REG_TCTRL); /* Collision threshold */ tctl &= ~(0xFF << 4); tctl |= (15 << 4); /* Turn it on */ tctl |= TCTL_EN; tctl |= TCTL_PSP; tctl |= (1 << 24); /* retransmit on late collision */ write_command(device, E1000_REG_TCTRL, tctl); } #define privileged() do { if (this_core->current_process->user != USER_ROOT_UID) { return -EPERM; } } while (0) static int ioctl_e1000(fs_node_t * node, unsigned long request, void * argp) { struct e1000_nic * nic = node->device; switch (request) { case SIOCGIFHWADDR: /* fill argp with mac */ memcpy(argp, nic->eth.mac, 6); return 0; case SIOCGIFADDR: if (nic->eth.ipv4_addr == 0) return -ENOENT; memcpy(argp, &nic->eth.ipv4_addr, sizeof(nic->eth.ipv4_addr)); return 0; case SIOCSIFADDR: privileged(); memcpy(&nic->eth.ipv4_addr, argp, sizeof(nic->eth.ipv4_addr)); return 0; case SIOCGIFNETMASK: if (nic->eth.ipv4_subnet == 0) return -ENOENT; memcpy(argp, &nic->eth.ipv4_subnet, sizeof(nic->eth.ipv4_subnet)); return 0; case SIOCSIFNETMASK: privileged(); memcpy(&nic->eth.ipv4_subnet, argp, sizeof(nic->eth.ipv4_subnet)); return 0; case SIOCGIFGATEWAY: if (nic->eth.ipv4_subnet == 0) return -ENOENT; memcpy(argp, &nic->eth.ipv4_gateway, sizeof(nic->eth.ipv4_gateway)); return 0; case SIOCSIFGATEWAY: privileged(); memcpy(&nic->eth.ipv4_gateway, argp, sizeof(nic->eth.ipv4_gateway)); net_arp_ask(nic->eth.ipv4_gateway, node); return 0; case SIOCGIFADDR6: return -ENOENT; case SIOCSIFADDR6: privileged(); memcpy(&nic->eth.ipv6_addr, argp, sizeof(nic->eth.ipv6_addr)); return 0; case SIOCGIFFLAGS: { uint32_t * flags = argp; *flags = IFF_RUNNING; if (nic->link_status) *flags |= IFF_UP; /* We turn these on in our init_tx */ *flags |= IFF_BROADCAST; *flags |= IFF_MULTICAST; return 0; } case SIOCGIFMTU: { uint32_t * mtu = argp; *mtu = nic->eth.mtu; return 0; } case SIOCGIFCOUNTS: { memcpy(argp, &nic->counts, sizeof(netif_counters_t)); return 0; } default: return -EINVAL; } } static ssize_t write_e1000(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { struct e1000_nic * nic = node->device; /* write packet */ send_packet(nic, buffer, size); return size; } static void ints_off(struct e1000_nic * nic) { write_command(nic, E1000_REG_IMC, 0xFFFFFFFF); write_command(nic, E1000_REG_ICR, 0xFFFFFFFF); read_command(nic, E1000_REG_STATUS); } static void e1000_init(struct e1000_nic * nic) { uint32_t e1000_device_pci = nic->pci_device; nic->rx_phys = mmu_allocate_n_frames(2) << 12; nic->rx = mmu_map_mmio_region(nic->rx_phys, 8192); nic->tx_phys = mmu_allocate_n_frames(2) << 12; nic->tx = mmu_map_mmio_region(nic->tx_phys, 8192); memset((void*)nic->rx, 0, sizeof(struct e1000_rx_desc) * E1000_NUM_RX_DESC); memset((void*)nic->tx, 0, sizeof(struct e1000_tx_desc) * E1000_NUM_TX_DESC); /* Allocate buffers */ for (int i = 0; i < E1000_NUM_RX_DESC; ++i) { nic->rx[i].addr = mmu_allocate_a_frame() << 12; nic->rx_virt[i] = mmu_map_mmio_region(nic->rx[i].addr, 4096); mmu_frame_map_address(mmu_get_page((uintptr_t)nic->rx_virt[i],0),MMU_FLAG_KERNEL|MMU_FLAG_WRITABLE,nic->rx[i].addr); nic->rx[i].status = 0; } for (int i = 0; i < E1000_NUM_TX_DESC; ++i) { nic->tx[i].addr = mmu_allocate_a_frame() << 12; nic->tx_virt[i] = mmu_map_mmio_region(nic->tx[i].addr, 4096); mmu_frame_allocate(mmu_get_page((uintptr_t)nic->tx_virt[i],0),MMU_FLAG_KERNEL|MMU_FLAG_WRITABLE); memset(nic->tx_virt[i], 0, 4096); nic->tx[i].status = 0; nic->tx[i].cmd = (1 << 0); } uint16_t command_reg = pci_read_field(e1000_device_pci, PCI_COMMAND, 2); command_reg = (1 << 1) | (1 << 2); pci_write_field(e1000_device_pci, PCI_COMMAND, 2, command_reg); #if defined(__aarch64__) pci_write_field(e1000_device_pci, PCI_BAR0, 4, 0x12200000); asm volatile ("isb" ::: "memory"); #endif delay_yield(10000); /* Is this size enough? */ uint32_t initial_bar = pci_read_field(e1000_device_pci, PCI_BAR0, 4); nic->mmio_addr = (uintptr_t)mmu_map_mmio_region(initial_bar, 0x20000); #if defined(__aarch64__) asm volatile ("isb" ::: "memory"); #endif eeprom_detect(nic); read_mac(nic); write_mac(nic); nic->queuer = (process_t*)this_core->current_process; #define CTRL_PHY_RST (1UL << 31UL) #define CTRL_RST (1UL << 26UL) #define CTRL_SLU (1UL << 6UL) #define CTRL_LRST (1UL << 3UL) #if defined(__x86_64__) nic->irq_number = pci_get_interrupt(e1000_device_pci); irq_install_handler(nic->irq_number, irq_handler, nic->eth.if_name); #elif defined(__aarch64__) int irq; gic_map_pci_interrupt(nic->eth.if_name,e1000_device_pci,&irq,e1000_irq_handler,nic); nic->irq_number = irq; #endif /* Disable interrupts */ ints_off(nic); /* Turn off receive + transmit */ write_command(nic, E1000_REG_RCTRL, 0); write_command(nic, E1000_REG_TCTRL, TCTL_PSP); read_command(nic, E1000_REG_STATUS); delay_yield(10000); /* Reset everything */ uint32_t ctrl = read_command(nic, E1000_REG_CTRL); ctrl |= CTRL_RST; write_command(nic, E1000_REG_CTRL, ctrl); delay_yield(20000); /* Turn off interrupts _again_ */ ints_off(nic); /* Recommended flow control settings? */ write_command(nic, 0x0028, 0x002C8001); write_command(nic, 0x002c, 0x0100); write_command(nic, 0x0030, 0x8808); write_command(nic, 0x0170, 0xFFFF); /* Link up */ uint32_t status = read_command(nic, E1000_REG_CTRL); status |= CTRL_SLU; status |= (2 << 8); /* Speed to gigabit... */ status &= ~CTRL_LRST; status &= ~CTRL_PHY_RST; write_command(nic, E1000_REG_CTRL, status); /* Clear statistical counters */ #ifndef __aarch64__ for (int i = 0; i < 128; ++i) { write_command(nic, 0x5200 + i * 4, 0); } for (int i = 0; i < 64; ++i) { read_command(nic, 0x4000 + i * 4); } #endif init_rx(nic); init_tx(nic); write_command(nic, E1000_REG_RDTR, 0); write_command(nic, E1000_REG_ITR, 500); read_command(nic, E1000_REG_STATUS); nic->link_status = (read_command(nic, E1000_REG_STATUS) & (1 << 1)); nic->eth.device_node = calloc(sizeof(fs_node_t),1); snprintf(nic->eth.device_node->name, 100, "%s", nic->eth.if_name); nic->eth.device_node->flags = FS_BLOCKDEVICE; /* NETDEVICE? */ nic->eth.device_node->mask = 0644; /* temporary; shouldn't be doing this with these device files */ nic->eth.device_node->ioctl = ioctl_e1000; nic->eth.device_node->write = write_e1000; nic->eth.device_node->device = nic; nic->eth.mtu = 1500; /* guess */ net_add_interface(nic->eth.if_name, nic->eth.device_node); char worker_name[34]; snprintf(worker_name, 33, "[%s]", nic->eth.if_name); nic->queuer = spawn_worker_thread(e1000_queuer, worker_name, nic); nic->configured = 1; /* Twiddle interrupts */ write_command(nic, E1000_REG_IMS, INTS); delay_yield(10000); } static void find_e1000(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * found) { if ((vendorid == 0x8086) && (deviceid == 0x100e || deviceid == 0x1004 || deviceid == 0x100f || deviceid == 0x10ea || deviceid == 0x10d3)) { /* Allocate a device */ struct e1000_nic * nic = calloc(1,sizeof(struct e1000_nic)); nic->pci_device = device; nic->deviceid = deviceid; devices[device_count++] = nic; snprintf(nic->eth.if_name, 31, "enp%ds%d", (int)pci_extract_bus(device), (int)pci_extract_slot(device)); e1000_init(nic); *(int*)found = 1; } } static int e1000_install(int argc, char * argv[]) { uint32_t found = 0; pci_scan(&find_e1000, -1, &found); if (!found) { /* TODO: Clean up? Remove ourselves? */ return -ENODEV; } return 0; } static int fini(void) { /* TODO: Uninstall device */ return 0; } struct Module metadata = { .name = "e1000", .init = e1000_install, .fini = fini, }; ================================================ FILE: modules/es1371.c ================================================ /** * @file kernel/audio/es1371.c * @brief Driver for the Ensoniq ES1371. * @package x86_64 * * @ref http://www.vogons.org/download/file.php?id=13036&sid=30df81e15e2521deb842a79f451b1161 * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ES_PORT_CONTROL 0x00 #define ES_PORT_STATUS 0x04 #define ES_PORT_UART_DATA 0x08 #define ES_PORT_UART_STS 0x09 #define ES_PORT_UART_TEST 0x0a #define ES_PORT_MEMORY_PAGE 0x0c #define ES_PORT_SRC_RW 0x10 #define ES_PORT_CODEC_RW 0x14 #define ES_PORT_LEGACY 0x18 #define ES_PORT_SERIAL 0x20 #define ES_PORT_P1_FRAME_CNT 0x24 #define ES_PORT_P2_FRAME_CNT 0x28 #define ES_PORT_R_FRAME_CNT 0x2c #define ES_PORT_P1_BUF_ADDR 0x30 #define ES_PORT_P1_BUF_DEF 0x34 #define ES_PORT_P2_BUF_ADDR 0x38 #define ES_PORT_P2_BUF_DEF 0x3c /** * Control bits */ #define ES_CTRL_SYNC_RES (1 << 14) #define ES_CTRL_DAC2_EN (1 << 5) /** * Status bits */ #define ES_STATUS_INTR (1 << 31) #define ES_STATUS_DAC2 (1 << 1) /** * Serial control bits */ #define ES_SERIAL_P2_END_INC_MASK (0x7 << 19) #define ES_SERIAL_P2_END_INC_TWO (2 << 19) #define ES_SERIAL_P2_ST_INC_MASK (0x7 << 16) #define ES_SERIAL_P2_LOOP_MASK (1 << 14) #define ES_SERIAL_P2_PAUSE (1 << 12) #define ES_SERIAL_P2_INTR_EN (1 << 9) #define ES_SERIAL_P2_DAC_SEN (1 << 6) #define ES_SERIAL_P2_MODE_MASK (0x3 << 2) #define ES_SERIAL_P2_MODE_16BIT (1 << 3) #define ES_SERIAL_P2_MODE_STEREO (1 << 2) /** * SRC RW register bits */ #define ES_SRC_REG_MASK (0xF << 19) /* SRC, DAC1, DAC2, ADC disable... */ #define ES_SRC_REG_WE (1 << 24) #define ES_SRC_REG_BUSY (1 << 23) #define ES_SRC_REG(x) ((x & 0x7F) << 25) /** * 16-bit registers for the samplerate converter */ #define ES_SRC_P2_TRUNCN 0x74 #define ES_SRC_P2_INTREGS 0x75 #define ES_SRC_P2_ACCUMFRAC 0x76 #define ES_SRC_P2_VFREQFRAC 0x77 /** * Volume is specified with a sign bit, 3 integer bits, and the rest as fractional. * I am not sure what this actually gets used for - a multiplier? */ #define ES_SRC_P2_VOL_L 0x7E #define ES_SRC_P2_VOL_R 0x7F struct es1371_device { uint32_t pci_device; int portbase; int irq; int bits; int mask; uint32_t serial; int16_t * buf; }; static struct es1371_device _device; static snd_knob_t _knobs[] = { { "Master", SND_KNOB_MASTER }, }; static int es1371_mixer_read(uint32_t knob_id, uint32_t *val); static int es1371_mixer_write(uint32_t knob_id, uint32_t val); static snd_device_t _snd = { .name = "Ensoniq ES1371", .device = &_device, .playback_speed = 48000, .playback_format = SND_FORMAT_L16SLE, .knobs = _knobs, .num_knobs = 1, .mixer_read = es1371_mixer_read, .mixer_write = es1371_mixer_write, }; static void find_es1371(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) { struct es1371_device * es1371 = extra; if ((vendorid == 0x1274) && (deviceid == 0x1371)) { es1371->pci_device = device; } } static int es1371_irq_handler(struct regs * regs) { uint32_t status = inportl(_device.portbase + ES_PORT_STATUS); if (!(status & ES_STATUS_INTR)) return 0; if (status & ES_STATUS_DAC2) { /* Reset the interrupt-waiting bit by toggling interrupts off and on for this DAC */ outportl(_device.portbase + ES_PORT_SERIAL, _device.serial & ~ES_SERIAL_P2_INTR_EN); outportl(_device.portbase + ES_PORT_SERIAL, _device.serial); outportl(_device.portbase + ES_PORT_MEMORY_PAGE, 0x0c); uint32_t def = inportl(_device.portbase + ES_PORT_P2_BUF_DEF); snd_request_buf(&_snd, 0x1000, (uint8_t*)_device.buf + ((def & 0xFFFF0000) ? 0 : 0x1000)); } irq_ack(_device.irq); return 1; } static int es1371_mixer_read(uint32_t knob_id, uint32_t *val) { switch (knob_id) { /* This is essentially the same as how we get the volume from the ICH AC'97, but * we have to get the AC97 codec through this port access thing... */ case SND_KNOB_MASTER: { outportl(_device.portbase + ES_PORT_CODEC_RW, (0x02 << 16) | (1 << 23)); uint32_t tmp = inportl(_device.portbase + ES_PORT_CODEC_RW) & 0xFFFF; if (tmp == 0x8000) { *val = 0; } else { /* 6 bit value */ *val = (tmp & _device.mask) << (sizeof(*val) * 8 - _device.bits); *val = ~*val; *val &= (uint32_t)_device.mask << (sizeof(*val) * 8 - _device.bits); } break; } default: return -1; } return 0; } static int es1371_mixer_write(uint32_t knob_id, uint32_t val) { switch (knob_id) { case SND_KNOB_MASTER: { uint16_t encoded; if (val == 0x0) { encoded = 0x8000; } else { val = ~val; val >>= (sizeof(val) * 8 - _device.bits); encoded = (val & 0xFF) | (val << 8); } outportl(_device.portbase + ES_PORT_CODEC_RW, (0x02 << 16) | encoded); break; } default: return -1; } return 0; } static void delay_yield(size_t subticks) { unsigned long s, ss; relative_time(0, subticks, &s, &ss); sleep_until((process_t *)this_core->current_process, s, ss); switch_task(0); } /** * @brief Write to the samplerate converter RAM */ static void src_write(int port, uint16_t value) { uint32_t x; while ((x = inportl(_device.portbase + ES_PORT_SRC_RW)) & ES_SRC_REG_BUSY); x &= ES_SRC_REG_MASK; x |= ES_SRC_REG_WE | ES_SRC_REG(port) | (value & 0xFFFF); outportl(_device.portbase + ES_PORT_SRC_RW, x); } static int es1371_install(int argc, char * argv[]) { pci_scan(&find_es1371, -1, &_device); if (!_device.pci_device) { return -ENODEV; } /* Get I/O port from PCI */ _device.portbase = pci_read_field(_device.pci_device, PCI_BAR0, 4) & ((uint32_t)-1) << 1; /* Enable port-IO, bus mastering, interrupts */ uint16_t command_reg = pci_read_field(_device.pci_device, PCI_COMMAND, 2); command_reg |= (1 << 2) | (1 << 0); command_reg &= ~(1 << 10); pci_write_field(_device.pci_device, PCI_COMMAND, 2, command_reg); /* Reset the controller */ uint32_t ctrl = 0; uint32_t serial = 0; outportl(_device.portbase + ES_PORT_CONTROL, ctrl); outportl(_device.portbase + ES_PORT_SERIAL, serial); outportl(_device.portbase + ES_PORT_LEGACY, 0); outportl(_device.portbase + ES_PORT_CONTROL, ctrl | ES_CTRL_SYNC_RES); inportl(_device.portbase + ES_PORT_CONTROL); delay_yield(2000); outportl(_device.portbase + ES_PORT_CONTROL, ctrl); /* Get 8192 of audio buffer space */ uintptr_t addr = mmu_allocate_n_frames(2) << 12; if (!addr) { return -ENODEV; } if (addr > 0xFFFFffff) { /* This thing only supports 32-bit physical addresses, so if we got something * too high (unlikely at early boot) we need to bail. */ printf("es1371: Allocated buffer is beyond the reach of 32-bit DMA engine.\n"); return -ENODEV; } /* Map interrupt handler */ _device.irq = pci_get_interrupt(_device.pci_device); irq_install_handler(_device.irq, es1371_irq_handler, "es1371"); /* Zero out the audio buffer */ _device.buf = mmu_map_from_physical(addr); memset(_device.buf, 0, 0x2000); /* Disable sound rate converter while we program it */ outportl(_device.portbase + ES_PORT_SRC_RW, (1 << 22)); /* Turn everything off? */ for (int i = 0; i < 0x80; ++i) { src_write(i, 0); } /* Set for 48KHz */ src_write(ES_SRC_P2_TRUNCN, 16 << 4); src_write(ES_SRC_P2_INTREGS, 16 << 10); /* This seems to be the sample rate converter's left/right audio volume? */ src_write(ES_SRC_P2_VOL_L, 0x1 << 12); src_write(ES_SRC_P2_VOL_R, 0x1 << 12); outportl(_device.portbase + ES_PORT_SRC_RW, 0); outportl(_device.portbase + ES_PORT_CODEC_RW, 0); delay_yield(2000); /* Set AC'97 codec volumes, which are inverted (-dB) */ _device.bits = 5; _device.mask = 0x1f; outportl(_device.portbase + ES_PORT_CODEC_RW, (0x02 << 16)); /* Master */ outportl(_device.portbase + ES_PORT_CODEC_RW, (0x18 << 16)); /* PCM OUT */ outportb(ES_PORT_UART_STS, 0); outportb(ES_PORT_UART_TEST, 0); outportb(ES_PORT_STATUS, 0); /* Set up buffer for two pages we can swap between, * which gives us good latency for immediate playback */ outportl(_device.portbase + ES_PORT_MEMORY_PAGE, 0x0c); outportl(_device.portbase + ES_PORT_P2_BUF_ADDR, addr); outportl(_device.portbase + ES_PORT_P2_BUF_DEF, 0x7FF); outportl(_device.portbase + ES_PORT_P2_FRAME_CNT, 0x400); /* Configure playback mode */ serial = inportl(_device.portbase + ES_PORT_SERIAL); serial &= ~(ES_SERIAL_P2_LOOP_MASK | ES_SERIAL_P2_END_INC_MASK | ES_SERIAL_P2_DAC_SEN | ES_SERIAL_P2_PAUSE | ES_SERIAL_P2_ST_INC_MASK | ES_SERIAL_P2_MODE_MASK); serial |= ES_SERIAL_P2_INTR_EN | ES_SERIAL_P2_MODE_STEREO | ES_SERIAL_P2_MODE_16BIT | /* 16-bit sterio */ ES_SERIAL_P2_END_INC_TWO; outportl(_device.portbase + ES_PORT_SERIAL, serial); _device.serial = serial; /* Turn it on! */ ctrl = inportl(_device.portbase + ES_PORT_CONTROL); outportl(_device.portbase + ES_PORT_CONTROL, ES_CTRL_DAC2_EN | ctrl); snd_register(&_snd); return 0; } static int fini(void) { snd_unregister(&_snd); return 0; } struct Module metadata = { .name = "es1371", .init = es1371_install, .fini = fini, }; ================================================ FILE: modules/ext2.c ================================================ /** * @file modules/ext2.c * @brief Implementation of the Ext2 filesystem. * @package x86_64 * @package aarch64 * * @warning There are many known bugs in this implementation. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #define debug_print(lvl, str, ...) do { if (this->flags & EXT2_FLAG_LOUD) { printf("ext2: %s: " str "\n", #lvl __VA_OPT__(,) __VA_ARGS__); } } while (0) #define EXT2_SUPER_MAGIC 0xEF53 #define EXT2_DIRECT_BLOCKS 12 /* Super block struct. */ struct ext2_superblock { uint32_t inodes_count; uint32_t blocks_count; uint32_t r_blocks_count; uint32_t free_blocks_count; uint32_t free_inodes_count; uint32_t first_data_block; uint32_t log_block_size; uint32_t log_frag_size; uint32_t blocks_per_group; uint32_t frags_per_group; uint32_t inodes_per_group; uint32_t mtime; uint32_t wtime; uint16_t mnt_count; uint16_t max_mnt_count; uint16_t magic; uint16_t state; uint16_t errors; uint16_t minor_rev_level; uint32_t lastcheck; uint32_t checkinterval; uint32_t creator_os; uint32_t rev_level; uint16_t def_resuid; uint16_t def_resgid; /* EXT2_DYNAMIC_REV */ uint32_t first_ino; uint16_t inode_size; uint16_t block_group_nr; uint32_t feature_compat; uint32_t feature_incompat; uint32_t feature_ro_compat; uint8_t uuid[16]; uint8_t volume_name[16]; uint8_t last_mounted[64]; uint32_t algo_bitmap; /* Performance Hints */ uint8_t prealloc_blocks; uint8_t prealloc_dir_blocks; uint16_t _padding; /* Journaling Support */ uint8_t journal_uuid[16]; uint32_t journal_inum; uint32_t jounral_dev; uint32_t last_orphan; /* Directory Indexing Support */ uint32_t hash_seed[4]; uint8_t def_hash_version; uint16_t _padding_a; uint8_t _padding_b; /* Other Options */ uint32_t default_mount_options; uint32_t first_meta_bg; uint8_t _unused[760]; } __attribute__ ((packed)); typedef struct ext2_superblock ext2_superblock_t; /* Block group descriptor. */ struct ext2_bgdescriptor { uint32_t block_bitmap; uint32_t inode_bitmap; // block no. of inode bitmap uint32_t inode_table; uint16_t free_blocks_count; uint16_t free_inodes_count; uint16_t used_dirs_count; uint16_t pad; uint8_t reserved[12]; } __attribute__ ((packed)); typedef struct ext2_bgdescriptor ext2_bgdescriptor_t; /* File Types */ #define EXT2_S_IFSOCK 0xC000 #define EXT2_S_IFLNK 0xA000 #define EXT2_S_IFREG 0x8000 #define EXT2_S_IFBLK 0x6000 #define EXT2_S_IFDIR 0x4000 #define EXT2_S_IFCHR 0x2000 #define EXT2_S_IFIFO 0x1000 /* setuid, etc. */ #define EXT2_S_ISUID 0x0800 #define EXT2_S_ISGID 0x0400 #define EXT2_S_ISVTX 0x0200 /* rights */ #define EXT2_S_IRUSR 0x0100 #define EXT2_S_IWUSR 0x0080 #define EXT2_S_IXUSR 0x0040 #define EXT2_S_IRGRP 0x0020 #define EXT2_S_IWGRP 0x0010 #define EXT2_S_IXGRP 0x0008 #define EXT2_S_IROTH 0x0004 #define EXT2_S_IWOTH 0x0002 #define EXT2_S_IXOTH 0x0001 /* This is not actually the inode table. * It represents an inode in an inode table on disk. */ struct ext2_inodetable { uint16_t mode; uint16_t uid; uint32_t size; // file length in byte. uint32_t atime; uint32_t ctime; uint32_t mtime; uint32_t dtime; uint16_t gid; uint16_t links_count; uint32_t blocks; uint32_t flags; uint32_t osd1; uint32_t block[15]; uint32_t generation; uint32_t file_acl; uint32_t dir_acl; uint32_t faddr; uint8_t osd2[12]; } __attribute__ ((packed)); typedef struct ext2_inodetable ext2_inodetable_t; /* Represents directory entry on disk. */ struct ext2_dir { uint32_t inode; uint16_t rec_len; uint8_t name_len; uint8_t file_type; char name[]; /* Actually a set of characters, at most 255 bytes */ } __attribute__ ((packed)); typedef struct ext2_dir ext2_dir_t; #define EXT2_BGD_BLOCK 2 #define E_SUCCESS 0 #define E_BADBLOCK 1 #define E_NOSPACE 2 #define E_BADPARENT 3 #undef _symlink #define _symlink(inode) ((char *)(inode)->block) /* * EXT2 filesystem object */ typedef struct { ext2_superblock_t * superblock; /* Device superblock, contains important information */ ext2_bgdescriptor_t * block_groups; /* Block Group Descriptor / Block groups */ fs_node_t * root_node; /* Root FS node (attached to mountpoint) */ fs_node_t * block_device; /* Block device node XXX unused */ unsigned int block_size; /* Size of one block */ unsigned int pointers_per_block; /* Number of pointers that fit in a block */ unsigned int inodes_per_group; /* Number of inodes in a "group" */ unsigned int block_group_count; /* Number of blocks groups */ uint8_t bgd_block_span; uint8_t bgd_offset; unsigned int inode_size; uint8_t * cache_data; int flags; sched_mutex_t * mutex; } ext2_fs_t; #define EXT2_FLAG_READWRITE 0x0002 #define EXT2_FLAG_LOUD 0x0004 /* * These macros were used in the original toaru ext2 driver. * They make referring to some of the core parts of the drive a bit easier. */ #define BGDS (this->block_group_count) #define SB (this->superblock) #define BGD (this->block_groups) #define RN (this->root_node) /* * These macros deal with the block group descriptor bitmap */ #define BLOCKBIT(n) (bg_buffer[((n) >> 3)] & (1 << (((n) % 8)))) #define BLOCKBYTE(n) (bg_buffer[((n) >> 3)]) #define SETBIT(n) (1 << (((n) % 8))) static int node_from_file(ext2_fs_t * this, ext2_inodetable_t *inode, ext2_dir_t *direntry, fs_node_t *fnode); static int ext2_root(ext2_fs_t * this, ext2_inodetable_t *inode, fs_node_t *fnode); static ext2_inodetable_t * read_inode(ext2_fs_t * this, size_t inode); static void refresh_inode(ext2_fs_t * this, ext2_inodetable_t * inodet, size_t inode); static int write_inode(ext2_fs_t * this, ext2_inodetable_t *inode, size_t index); static fs_node_t * finddir_ext2(fs_node_t *node, char *name); static size_t allocate_block(ext2_fs_t * this); /** * ext2->rewrite_superblock Rewrite the superblock. * * Superblocks are a bit different from other blocks, as they are always in the same place, * regardless of what the filesystem block size is. This doesn't work well with our setup, * so we need to special-case it. */ static int rewrite_superblock(ext2_fs_t * this) { write_fs(this->block_device, 1024, sizeof(ext2_superblock_t), (uint8_t *)SB); return E_SUCCESS; } /** * ext2->read_block Read a block from the block device associated with this filesystem. * * The read block will be copied into the buffer pointed to by `buf`. * * @param block_no Number of block to read. * @param buf Where to put the data read. * @returns Error code or E_SUCCESS */ static int read_block(ext2_fs_t * this, unsigned int block_no, uint8_t * buf) { /* 0 is an invalid block number. So is anything beyond the total block count, but we can't check that. */ if (!block_no) { return E_BADBLOCK; } /* In such cases, we read directly from the block device */ read_fs(this->block_device, block_no * this->block_size, this->block_size, (uint8_t *)buf); /* And return SUCCESS */ return E_SUCCESS; } /** * ext2->write_block Write a block to the block device. * * @param block_no Block to write * @param buf Data in the block * @returns Error code or E_SUCCESSS */ static int write_block(ext2_fs_t * this, unsigned int block_no, uint8_t *buf) { if (!block_no) { debug_print(ERROR, "Attempted to write to block #0. Enable tracing and retry this operation."); debug_print(ERROR, "Your file system is most likely corrupted now."); return E_BADBLOCK; } /* This operation requires the filesystem lock */ write_fs(this->block_device, block_no * this->block_size, this->block_size, buf); /* We're done. */ return E_SUCCESS; } /** * ext2->set_block_number Set the "real" block number for a given "inode" block number. * * @param inode Inode to operate on * @param iblock Block offset within the inode * @param rblock Real block number * @returns Error code or E_SUCCESS */ static unsigned int set_block_number(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int inode_no, unsigned int iblock, unsigned int rblock) { unsigned int p = this->pointers_per_block; /* We're going to do some crazy math in a bit... */ unsigned int a, b, c, d, e, f, g; uint8_t * tmp; if (iblock < EXT2_DIRECT_BLOCKS) { inode->block[iblock] = rblock; return E_SUCCESS; } else if (iblock < EXT2_DIRECT_BLOCKS + p) { /* XXX what if inode->block[EXT2_DIRECT_BLOCKS] isn't set? */ if (!inode->block[EXT2_DIRECT_BLOCKS]) { unsigned int block_no = allocate_block(this); if (!block_no) return E_NOSPACE; inode->block[EXT2_DIRECT_BLOCKS] = block_no; write_inode(this, inode, inode_no); } tmp = malloc(this->block_size); read_block(this, inode->block[EXT2_DIRECT_BLOCKS], (uint8_t *)tmp); ((uint32_t *)tmp)[iblock - EXT2_DIRECT_BLOCKS] = rblock; write_block(this, inode->block[EXT2_DIRECT_BLOCKS], (uint8_t *)tmp); free(tmp); return E_SUCCESS; } else if (iblock < EXT2_DIRECT_BLOCKS + p + p * p) { a = iblock - EXT2_DIRECT_BLOCKS; b = a - p; c = b / p; d = b - c * p; if (!inode->block[EXT2_DIRECT_BLOCKS+1]) { unsigned int block_no = allocate_block(this); if (!block_no) return E_NOSPACE; inode->block[EXT2_DIRECT_BLOCKS+1] = block_no; write_inode(this, inode, inode_no); } tmp = malloc(this->block_size); read_block(this, inode->block[EXT2_DIRECT_BLOCKS + 1], (uint8_t *)tmp); if (!((uint32_t *)tmp)[c]) { unsigned int block_no = allocate_block(this); if (!block_no) goto no_space_free; ((uint32_t *)tmp)[c] = block_no; write_block(this, inode->block[EXT2_DIRECT_BLOCKS + 1], (uint8_t *)tmp); } uint32_t nblock = ((uint32_t *)tmp)[c]; read_block(this, nblock, (uint8_t *)tmp); ((uint32_t *)tmp)[d] = rblock; write_block(this, nblock, (uint8_t *)tmp); free(tmp); return E_SUCCESS; } else if (iblock < EXT2_DIRECT_BLOCKS + p + p * p + p) { a = iblock - EXT2_DIRECT_BLOCKS; b = a - p; c = b - p * p; d = c / (p * p); e = c - d * p * p; f = e / p; g = e - f * p; if (!inode->block[EXT2_DIRECT_BLOCKS+2]) { unsigned int block_no = allocate_block(this); if (!block_no) return E_NOSPACE; inode->block[EXT2_DIRECT_BLOCKS+2] = block_no; write_inode(this, inode, inode_no); } tmp = malloc(this->block_size); read_block(this, inode->block[EXT2_DIRECT_BLOCKS + 2], (uint8_t *)tmp); if (!((uint32_t *)tmp)[d]) { unsigned int block_no = allocate_block(this); if (!block_no) goto no_space_free; ((uint32_t *)tmp)[d] = block_no; write_block(this, inode->block[EXT2_DIRECT_BLOCKS + 2], (uint8_t *)tmp); } uint32_t nblock = ((uint32_t *)tmp)[d]; read_block(this, nblock, (uint8_t *)tmp); if (!((uint32_t *)tmp)[f]) { unsigned int block_no = allocate_block(this); if (!block_no) goto no_space_free; ((uint32_t *)tmp)[f] = block_no; write_block(this, nblock, (uint8_t *)tmp); } nblock = ((uint32_t *)tmp)[f]; read_block(this, nblock, (uint8_t *)tmp); ((uint32_t *)tmp)[g] = nblock; write_block(this, nblock, (uint8_t *)tmp); free(tmp); return E_SUCCESS; } debug_print(CRITICAL, "EXT2 driver tried to write to a block number that was too high (%d)", rblock); return E_BADBLOCK; no_space_free: free(tmp); return E_NOSPACE; } /** * ext2->get_block_number Given an inode block number, get the real block number. * * @param inode Inode to operate on * @param iblock Block offset within the inode * @returns Real block number */ static unsigned int get_block_number(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int iblock) { unsigned int p = this->pointers_per_block; /* We're going to do some crazy math in a bit... */ unsigned int a, b, c, d, e, f, g; uint8_t * tmp; if (iblock < EXT2_DIRECT_BLOCKS) { return inode->block[iblock]; } else if (iblock < EXT2_DIRECT_BLOCKS + p) { /* XXX what if inode->block[EXT2_DIRECT_BLOCKS] isn't set? */ tmp = malloc(this->block_size); read_block(this, inode->block[EXT2_DIRECT_BLOCKS], (uint8_t *)tmp); unsigned int out = ((uint32_t *)tmp)[iblock - EXT2_DIRECT_BLOCKS]; free(tmp); return out; } else if (iblock < EXT2_DIRECT_BLOCKS + p + p * p) { a = iblock - EXT2_DIRECT_BLOCKS; b = a - p; c = b / p; d = b - c * p; tmp = malloc(this->block_size); read_block(this, inode->block[EXT2_DIRECT_BLOCKS + 1], (uint8_t *)tmp); uint32_t nblock = ((uint32_t *)tmp)[c]; read_block(this, nblock, (uint8_t *)tmp); unsigned int out = ((uint32_t *)tmp)[d]; free(tmp); return out; } else if (iblock < EXT2_DIRECT_BLOCKS + p + p * p + p) { a = iblock - EXT2_DIRECT_BLOCKS; b = a - p; c = b - p * p; d = c / (p * p); e = c - d * p * p; f = e / p; g = e - f * p; tmp = malloc(this->block_size); read_block(this, inode->block[EXT2_DIRECT_BLOCKS + 2], (uint8_t *)tmp); uint32_t nblock = ((uint32_t *)tmp)[d]; read_block(this, nblock, (uint8_t *)tmp); nblock = ((uint32_t *)tmp)[f]; read_block(this, nblock, (uint8_t *)tmp); unsigned int out = ((uint32_t *)tmp)[g]; free(tmp); return out; } debug_print(CRITICAL, "EXT2 driver tried to read to a block number that was too high (%d)", iblock); return 0; } static int write_inode(ext2_fs_t * this, ext2_inodetable_t *inode, size_t index) { if (!index) { dprintf("ext2: Attempt to write inode 0\n"); return E_BADBLOCK; } index--; size_t group = index / this->inodes_per_group; if (group > BGDS) { return E_BADBLOCK; } size_t inode_table_block = BGD[group].inode_table; index -= group * this->inodes_per_group; size_t block_offset = (index * this->inode_size) / this->block_size; size_t offset_in_block = index - block_offset * (this->block_size / this->inode_size); ext2_inodetable_t *inodet = malloc(this->block_size); /* Read the current table block */ read_block(this, inode_table_block + block_offset, (uint8_t *)inodet); memcpy((uint8_t *)((uintptr_t)inodet + offset_in_block * this->inode_size), inode, this->inode_size); write_block(this, inode_table_block + block_offset, (uint8_t *)inodet); free(inodet); return E_SUCCESS; } static size_t allocate_block(ext2_fs_t * this) { size_t block_no = 0; size_t block_offset = 0; size_t group = 0; uint8_t * bg_buffer = malloc(this->block_size); mutex_acquire(this->mutex); for (unsigned int i = 0; i < BGDS; ++i) { if (BGD[i].free_blocks_count > 0) { read_block(this, BGD[i].block_bitmap, (uint8_t *)bg_buffer); while (BLOCKBIT(block_offset)) { ++block_offset; } block_no = block_offset + SB->blocks_per_group * i; group = i; break; } } if (!block_no) { mutex_release(this->mutex); debug_print(CRITICAL, "No available blocks, disk is out of space!"); free(bg_buffer); return 0; } debug_print(WARNING, "allocating block #%zu (group %zu)", block_no, group); BLOCKBYTE(block_offset) |= SETBIT(block_offset); write_block(this, BGD[group].block_bitmap, (uint8_t *)bg_buffer); BGD[group].free_blocks_count--; for (int i = 0; i < this->bgd_block_span; ++i) { write_block(this, this->bgd_offset + i, (uint8_t *)((uintptr_t)BGD + this->block_size * i)); } SB->free_blocks_count--; rewrite_superblock(this); memset(bg_buffer, 0x00, this->block_size); write_block(this, block_no, bg_buffer); mutex_release(this->mutex); free(bg_buffer); return block_no; } /** * ext2->allocate_inode_block Allocate a block in an inode. * * @param inode Inode to operate on * @param inode_no Number of the inode (this is not part of the struct) * @param block Block within inode to allocate * @returns Error code or E_SUCCESS */ static int allocate_inode_block(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int inode_no, unsigned int block) { debug_print(NOTICE, "Allocating block #%d for inode #%d", block, inode_no); unsigned int block_no = allocate_block(this); if (!block_no) return E_NOSPACE; set_block_number(this, inode, inode_no, block, block_no); unsigned int t = (block + 1) * (this->block_size / 512); if (inode->blocks < t) { debug_print(NOTICE, "Setting inode->blocks to %d = (%d fs blocks)", t, t / (this->block_size / 512)); inode->blocks = t; } write_inode(this, inode, inode_no); return E_SUCCESS; } /** * ext2->inode_read_block * * @param inode * @param no * @param block * @parma buf * @returns Real block number for reference. */ static unsigned int inode_read_block(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int block, uint8_t * buf) { if (block >= inode->blocks / (this->block_size / 512)) { memset(buf, 0x00, this->block_size); debug_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)); return 0; } unsigned int real_block = get_block_number(this, inode, block); read_block(this, real_block, buf); return real_block; } /** * ext2->inode_write_block */ static unsigned int inode_write_block(ext2_fs_t * this, ext2_inodetable_t * inode, unsigned int inode_no, unsigned int block, uint8_t * buf) { if (block >= inode->blocks / (this->block_size / 512)) { debug_print(WARNING, "Attempting to write beyond the existing allocated blocks for this inode."); debug_print(WARNING, "Inode %d, Block %d", inode_no, block); } debug_print(WARNING, "clearing and allocating up to required blocks (block=%d, %d)", block, inode->blocks); char * empty = NULL; while (block >= inode->blocks / (this->block_size / 512)) { allocate_inode_block(this, inode, inode_no, inode->blocks / (this->block_size / 512)); refresh_inode(this, inode, inode_no); } if (empty) free(empty); debug_print(WARNING, "... done"); unsigned int real_block = get_block_number(this, inode, block); debug_print(WARNING, "Writing virtual block %d for inode %d maps to real block %d", block, inode_no, real_block); write_block(this, real_block, buf); return real_block; } /** * ext2->create_entry * * @returns Error code or E_SUCCESS */ static int create_entry(fs_node_t * parent, char * name, uint32_t inode) { ext2_fs_t * this = (ext2_fs_t *)parent->device; ext2_inodetable_t * pinode = read_inode(this,parent->inode); if (((pinode->mode & EXT2_S_IFDIR) == 0) || (name == NULL)) { debug_print(WARNING, "Attempted to allocate an inode in a parent that was not a directory."); return E_BADPARENT; } debug_print(WARNING, "Creating a directory entry for %s pointing to inode %d.", name, inode); /* okay, how big is it... */ debug_print(WARNING, "We need to append %zd bytes to the direcotry.", sizeof(ext2_dir_t) + strlen(name)); unsigned int rec_len = sizeof(ext2_dir_t) + strlen(name); rec_len += (rec_len % 4) ? (4 - (rec_len % 4)) : 0; debug_print(WARNING, "Our directory entry looks like this:"); debug_print(WARNING, " inode = %d", inode); debug_print(WARNING, " rec_len = %d", rec_len); debug_print(WARNING, " name_len = %zd", strlen(name)); debug_print(WARNING, " file_type = %d", 0); debug_print(WARNING, " name = %s", name); debug_print(WARNING, "The inode size is marked as: %d", pinode->size); debug_print(WARNING, "Block size is %d", this->block_size); uint8_t * block = malloc(this->block_size); uint8_t block_nr = 0; uint32_t dir_offset = 0; uint32_t total_offset = 0; int modify_or_replace = 0; ext2_dir_t *previous; inode_read_block(this, pinode, block_nr, block); while (total_offset < pinode->size) { if (dir_offset >= this->block_size) { block_nr++; dir_offset -= this->block_size; inode_read_block(this, pinode, block_nr, block); } ext2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset); unsigned int sreclen = d_ent->name_len + sizeof(ext2_dir_t); sreclen += (sreclen % 4) ? (4 - (sreclen % 4)) : 0; { char f[d_ent->name_len+1]; memcpy(f, d_ent->name, d_ent->name_len); f[d_ent->name_len] = 0; debug_print(WARNING, " * file: %s", f); } debug_print(WARNING, " rec_len: %d", d_ent->rec_len); debug_print(WARNING, " type: %d", d_ent->file_type); debug_print(WARNING, " namel: %d", d_ent->name_len); debug_print(WARNING, " inode: %d", d_ent->inode); if (d_ent->rec_len != sreclen && total_offset + d_ent->rec_len == pinode->size) { debug_print(WARNING, " - should be %d, but instead points to end of block", sreclen); debug_print(WARNING, " - we've hit the end, should change this pointer"); dir_offset += sreclen; total_offset += sreclen; modify_or_replace = 1; /* Modify */ previous = d_ent; break; } if (d_ent->inode == 0) { modify_or_replace = 2; /* Replace */ } dir_offset += d_ent->rec_len; total_offset += d_ent->rec_len; } if (!modify_or_replace) { debug_print(WARNING, "That's odd, this shouldn't have happened, we made it all the way here without hitting our two end conditions?"); } if (modify_or_replace == 1) { debug_print(WARNING, "The last node in the list is a real node, we need to modify it."); if (dir_offset + rec_len >= this->block_size) { block_nr++; allocate_inode_block(this, pinode, parent->inode, block_nr); memset(block, 0, this->block_size); dir_offset = 0; pinode->size += this->block_size; write_inode(this, pinode, parent->inode); } else { unsigned int sreclen = previous->name_len + sizeof(ext2_dir_t); sreclen += (sreclen % 4) ? (4 - (sreclen % 4)) : 0; previous->rec_len = sreclen; debug_print(WARNING, "Set previous node rec_len to %d", sreclen); } } else if (modify_or_replace == 2) { debug_print(WARNING, "The last node in the list is a fake node, we'll replace it."); } debug_print(WARNING, " total_offset = 0x%x", total_offset); debug_print(WARNING, " dir_offset = 0x%x", dir_offset); ext2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset); d_ent->inode = inode; d_ent->rec_len = this->block_size - dir_offset; d_ent->name_len = strlen(name); d_ent->file_type = 0; /* This is unused */ memcpy(d_ent->name, name, strlen(name)); inode_write_block(this, pinode, parent->inode, block_nr, block); free(block); free(pinode); return E_NOSPACE; } static unsigned int allocate_inode(ext2_fs_t * this) { uint32_t node_no = 0; uint32_t node_offset = 0; uint32_t group = 0; uint8_t * bg_buffer = malloc(this->block_size); mutex_acquire(this->mutex); for (unsigned int i = 0; i < BGDS; ++i) { if (BGD[i].free_inodes_count > 0) { debug_print(NOTICE, "Group %d has %d free inodes.", i, BGD[i].free_inodes_count); read_block(this, BGD[i].inode_bitmap, (uint8_t *)bg_buffer); /* Sorry for the weird loops */ while (1) { while (BLOCKBIT(node_offset)) { node_offset++; if (node_offset == this->inodes_per_group) { goto _next_block; } } node_no = node_offset + i * this->inodes_per_group + 1; /* Is this a reserved inode? */ if (node_no <= 10) { node_offset++; if (node_offset == this->inodes_per_group) { goto _next_block; } continue; } break; } if (node_offset == this->inodes_per_group) { _next_block: node_offset = 0; continue; } group = i; break; } } if (!node_no) { mutex_release(this->mutex); dprintf("ext2: Out of inodes? node_no = 0\n"); return 0; } BLOCKBYTE(node_offset) |= SETBIT(node_offset); write_block(this, BGD[group].inode_bitmap, (uint8_t *)bg_buffer); free(bg_buffer); BGD[group].free_inodes_count--; for (int i = 0; i < this->bgd_block_span; ++i) { write_block(this, this->bgd_offset + i, (uint8_t *)((uintptr_t)BGD + this->block_size * i)); } SB->free_inodes_count--; rewrite_superblock(this); mutex_release(this->mutex); return node_no; } static int mkdir_ext2(fs_node_t * parent, char * name, mode_t permission) { if (!name) return -EINVAL; ext2_fs_t * this = parent->device; if (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS; /* first off, check if it exists */ fs_node_t * check = finddir_ext2(parent, name); if (check) { debug_print(WARNING, "A file by this name already exists: %s", name); free(check); return -EEXIST; } if (!has_permission(parent, 02) || !has_permission(parent, 01)) { return -EACCES; } /* Allocate an inode for it */ unsigned int inode_no = allocate_inode(this); ext2_inodetable_t * inode = read_inode(this,inode_no); /* Set the access and creation times to now */ inode->atime = now(); inode->ctime = inode->atime; inode->mtime = inode->atime; inode->dtime = 0; /* This inode was never deleted */ /* Empty the file */ memset(inode->block, 0x00, sizeof(inode->block)); inode->blocks = 0; inode->size = 0; /* empty */ /* Assign it to root */ inode->uid = this_core->current_process->user; inode->gid = this_core->current_process->user_group; /* misc */ inode->faddr = 0; inode->links_count = 2; /* There's the parent's pointer to us, and our pointer to us. */ inode->flags = 0; inode->osd1 = 0; inode->generation = 0; inode->file_acl = 0; inode->dir_acl = 0; /* File mode */ inode->mode = EXT2_S_IFDIR; inode->mode |= 0xFFF & permission; /* Write the osd blocks to 0 */ memset(inode->osd2, 0x00, sizeof(inode->osd2)); /* Write out inode changes */ write_inode(this, inode, inode_no); /* Now append the entry to the parent */ create_entry(parent, name, inode_no); inode->size = this->block_size; write_inode(this, inode, inode_no); uint8_t * tmp = malloc(this->block_size); ext2_dir_t * t = malloc(12); memset(t, 0, 12); t->inode = inode_no; t->rec_len = 12; t->name_len = 1; t->name[0] = '.'; memcpy(&tmp[0], t, 12); t->inode = parent->inode; t->name_len = 2; t->name[1] = '.'; t->rec_len = this->block_size - 12; memcpy(&tmp[12], t, 12); free(t); inode_write_block(this, inode, inode_no, 0, tmp); free(inode); free(tmp); /* Update parent link count */ ext2_inodetable_t * pinode = read_inode(this, parent->inode); pinode->links_count++; write_inode(this, pinode, parent->inode); free(pinode); /* Update directory count in block group descriptor */ uint32_t group = inode_no / this->inodes_per_group; BGD[group].used_dirs_count++; for (int i = 0; i < this->bgd_block_span; ++i) { write_block(this, this->bgd_offset + i, (uint8_t *)((uintptr_t)BGD + this->block_size * i)); } return 0; } static int create_ext2(fs_node_t * parent, char * name, mode_t permission) { if (!name) return -EINVAL; ext2_fs_t * this = parent->device; if (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS; /* first off, check if it exists */ fs_node_t * check = finddir_ext2(parent, name); if (check) { debug_print(WARNING, "A file by this name already exists: %s", name); free(check); return -EEXIST; } /* Allocate an inode for it */ unsigned int inode_no = allocate_inode(this); ext2_inodetable_t * inode = read_inode(this,inode_no); /* Set the access and creation times to now */ inode->atime = now(); inode->ctime = inode->atime; inode->mtime = inode->atime; inode->dtime = 0; /* This inode was never deleted */ /* Empty the file */ memset(inode->block, 0x00, sizeof(inode->block)); inode->blocks = 0; inode->size = 0; /* empty */ inode->uid = this_core->current_process->user; inode->gid = this_core->current_process->user_group; /* misc */ inode->faddr = 0; inode->links_count = 1; /* The one we're about to create. */ inode->flags = 0; inode->osd1 = 0; inode->generation = 0; inode->file_acl = 0; inode->dir_acl = 0; /* File mode */ /* TODO: Use the mask from `permission` */ inode->mode = EXT2_S_IFREG; inode->mode |= 0xFFF & permission; /* Write the osd blocks to 0 */ memset(inode->osd2, 0x00, sizeof(inode->osd2)); /* Write out inode changes */ write_inode(this, inode, inode_no); /* Now append the entry to the parent */ create_entry(parent, name, inode_no); free(inode); return 0; } static int chmod_ext2(fs_node_t * node, mode_t mode) { ext2_fs_t * this = node->device; if (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS; ext2_inodetable_t * inode = read_inode(this,node->inode); inode->mode = (inode->mode & 0xFFFFF000) | mode; write_inode(this, inode, node->inode); return 0; } /** * direntry_ext2 */ static ext2_dir_t * direntry_ext2(ext2_fs_t * this, ext2_inodetable_t * inode, uint32_t no, uint32_t index) { uint8_t *block = malloc(this->block_size); uint8_t block_nr = 0; inode_read_block(this, inode, block_nr, block); uint32_t dir_offset = 0; uint32_t total_offset = 0; uint32_t dir_index = 0; while (total_offset < inode->size && dir_index <= index) { ext2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset); if (d_ent->inode != 0 && dir_index == index) { ext2_dir_t *out = malloc(d_ent->rec_len); memcpy(out, d_ent, d_ent->rec_len); free(block); return out; } dir_offset += d_ent->rec_len; total_offset += d_ent->rec_len; if (d_ent->inode) { dir_index++; } if (dir_offset >= this->block_size) { block_nr++; dir_offset -= this->block_size; inode_read_block(this, inode, block_nr, block); } } free(block); return NULL; } /** * finddir_ext2 */ static fs_node_t * finddir_ext2(fs_node_t *node, char *name) { ext2_fs_t * this = (ext2_fs_t *)node->device; ext2_inodetable_t *inode = read_inode(this,node->inode); //assert(inode->mode & EXT2_S_IFDIR); uint8_t * block = malloc(this->block_size); ext2_dir_t *direntry = NULL; uint8_t block_nr = 0; inode_read_block(this, inode, block_nr, block); uint32_t dir_offset = 0; uint32_t total_offset = 0; while (total_offset < inode->size) { if (dir_offset >= this->block_size) { block_nr++; dir_offset -= this->block_size; inode_read_block(this, inode, block_nr, block); } ext2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset); if (d_ent->inode == 0 || strlen(name) != d_ent->name_len) { dir_offset += d_ent->rec_len; total_offset += d_ent->rec_len; continue; } char *dname = malloc(sizeof(char) * (d_ent->name_len + 1)); memcpy(dname, &(d_ent->name), d_ent->name_len); dname[d_ent->name_len] = '\0'; if (!strcmp(dname, name)) { free(dname); direntry = malloc(d_ent->rec_len); memcpy(direntry, d_ent, d_ent->rec_len); break; } free(dname); dir_offset += d_ent->rec_len; total_offset += d_ent->rec_len; } free(inode); if (!direntry) { free(block); return NULL; } fs_node_t *outnode = malloc(sizeof(fs_node_t)); memset(outnode, 0, sizeof(fs_node_t)); inode = read_inode(this, direntry->inode); if (!node_from_file(this, inode, direntry, outnode)) { debug_print(CRITICAL, "Oh dear. Couldn't allocate the outnode?"); } free(direntry); free(inode); free(block); return outnode; } static int unlink_ext2(fs_node_t * node, char * name) { /* XXX this is a very bad implementation */ ext2_fs_t * this = (ext2_fs_t *)node->device; if (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS; ext2_inodetable_t *inode = read_inode(this,node->inode); //assert(inode->mode & EXT2_S_IFDIR); uint8_t * block = malloc(this->block_size); ext2_dir_t *direntry = NULL; uint8_t block_nr = 0; inode_read_block(this, inode, block_nr, block); uint32_t dir_offset = 0; uint32_t total_offset = 0; while (total_offset < inode->size) { if (dir_offset >= this->block_size) { block_nr++; dir_offset -= this->block_size; inode_read_block(this, inode, block_nr, block); } ext2_dir_t *d_ent = (ext2_dir_t *)((uintptr_t)block + dir_offset); if (d_ent->inode == 0 || strlen(name) != d_ent->name_len) { dir_offset += d_ent->rec_len; total_offset += d_ent->rec_len; continue; } char *dname = malloc(sizeof(char) * (d_ent->name_len + 1)); memcpy(dname, &(d_ent->name), d_ent->name_len); dname[d_ent->name_len] = '\0'; if (!strcmp(dname, name)) { free(dname); direntry = d_ent; break; } free(dname); dir_offset += d_ent->rec_len; total_offset += d_ent->rec_len; } if (!direntry) { free(inode); free(block); return -ENOENT; } unsigned int new_inode = direntry->inode; direntry->inode = 0; inode_write_block(this, inode, node->inode, block_nr, block); free(inode); free(block); inode = read_inode(this, new_inode); if (inode->links_count == 1) { dprintf("ext2: TODO: unlinking '%s' (inode=%u) which now has no links; should delete\n", name, new_inode); } if (inode->links_count > 0) { inode->links_count--; write_inode(this, inode, new_inode); } free(inode); return 0; } static void refresh_inode(ext2_fs_t * this, ext2_inodetable_t * inodet, size_t inode) { if (!inode) { dprintf("ext2: Attempt to read inode 0\n"); return; } inode--; uint32_t group = inode / this->inodes_per_group; if (group > BGDS) { return; } uint32_t inode_table_block = BGD[group].inode_table; inode -= group * this->inodes_per_group; // adjust index within group uint32_t block_offset = (inode * this->inode_size) / this->block_size; uint32_t offset_in_block = inode - block_offset * (this->block_size / this->inode_size); uint8_t * buf = malloc(this->block_size); read_block(this, inode_table_block + block_offset, buf); ext2_inodetable_t *inodes = (ext2_inodetable_t *)buf; memcpy(inodet, (uint8_t *)((uintptr_t)inodes + offset_in_block * this->inode_size), this->inode_size); free(buf); } /** * read_inode */ static ext2_inodetable_t * read_inode(ext2_fs_t * this, size_t inode) { ext2_inodetable_t *inodet = malloc(this->inode_size); refresh_inode(this, inodet, inode); return inodet; } static ssize_t read_ext2(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { ext2_fs_t * this = (ext2_fs_t *)node->device; ext2_inodetable_t * inode = read_inode(this, node->inode); uint32_t end; if (inode->size == 0) return 0; if (offset + size > inode->size) { end = inode->size; } else { end = offset + size; } uint32_t start_block = offset / this->block_size; uint32_t end_block = end / this->block_size; uint32_t end_size = end - end_block * this->block_size; uint32_t size_to_read = end - offset; uint8_t * buf = malloc(this->block_size); if (start_block == end_block) { inode_read_block(this, inode, start_block, buf); memcpy(buffer, (uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % this->block_size)), size_to_read); } else { uint32_t block_offset; uint32_t blocks_read = 0; for (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) { if (block_offset == start_block) { inode_read_block(this, inode, block_offset, buf); memcpy(buffer, (uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % this->block_size)), this->block_size - (offset % this->block_size)); } else { inode_read_block(this, inode, block_offset, buf); memcpy(buffer + this->block_size * blocks_read - (offset % this->block_size), buf, this->block_size); } } if (end_size) { inode_read_block(this, inode, end_block, buf); memcpy(buffer + this->block_size * blocks_read - (offset % this->block_size), buf, end_size); } } free(inode); free(buf); return size_to_read; } static 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) { uint32_t end = offset + size; if (end > inode->size) { inode->size = end; write_inode(this, inode, inode_number); } uint32_t start_block = offset / this->block_size; uint32_t end_block = end / this->block_size; uint32_t end_size = end - end_block * this->block_size; uint32_t size_to_read = end - offset; uint8_t * buf = malloc(this->block_size); if (start_block == end_block) { inode_read_block(this, inode, start_block, buf); memcpy((uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % this->block_size)), buffer, size_to_read); inode_write_block(this, inode, inode_number, start_block, buf); } else { uint32_t block_offset; uint32_t blocks_read = 0; for (block_offset = start_block; block_offset < end_block; block_offset++, blocks_read++) { if (block_offset == start_block) { int b = inode_read_block(this, inode, block_offset, buf); memcpy((uint8_t *)(((uintptr_t)buf) + ((uintptr_t)offset % this->block_size)), buffer, this->block_size - (offset % this->block_size)); inode_write_block(this, inode, inode_number, block_offset, buf); if (!b) { refresh_inode(this, inode, inode_number); } } else { int b = inode_read_block(this, inode, block_offset, buf); memcpy(buf, buffer + this->block_size * blocks_read - (offset % this->block_size), this->block_size); inode_write_block(this, inode, inode_number, block_offset, buf); if (!b) { refresh_inode(this, inode, inode_number); } } } if (end_size) { inode_read_block(this, inode, end_block, buf); memcpy(buf, buffer + this->block_size * blocks_read - (offset % this->block_size), end_size); inode_write_block(this, inode, inode_number, end_block, buf); } } free(buf); return size_to_read; } static ssize_t write_ext2(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { ext2_fs_t * this = (ext2_fs_t *)node->device; if (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS; ext2_inodetable_t * inode = read_inode(this, node->inode); ssize_t rv = write_inode_buffer(this, inode, node->inode, offset, size, buffer); free(inode); return rv; } static int truncate_ext2(fs_node_t * node, size_t size) { ext2_fs_t * this = node->device; if (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS; if (size != 0) return -ENOTSUP; ext2_inodetable_t * inode = read_inode(this,node->inode); inode->size = 0; write_inode(this, inode, node->inode); return 0; } static void open_ext2(fs_node_t *node, unsigned int flags) { /* Nothing to do here */ } static void close_ext2(fs_node_t *node) { /* Nothing to do here */ } /** * readdir_ext2 */ static struct dirent * readdir_ext2(fs_node_t *node, unsigned long index) { ext2_fs_t * this = (ext2_fs_t *)node->device; ext2_inodetable_t *inode = read_inode(this, node->inode); //assert(inode->mode & EXT2_S_IFDIR); ext2_dir_t *direntry = direntry_ext2(this, inode, node->inode, index); if (!direntry) { free(inode); return NULL; } struct dirent *dirent = malloc(sizeof(struct dirent)); memcpy(&dirent->d_name, &direntry->name, direntry->name_len); dirent->d_name[direntry->name_len] = '\0'; dirent->d_ino = direntry->inode; free(direntry); free(inode); return dirent; } static int symlink_ext2(fs_node_t * parent, char * target, char * name) { if (!name) return -EINVAL; ext2_fs_t * this = parent->device; if (!(this->flags & EXT2_FLAG_READWRITE)) return -EROFS; /* first off, check if it exists */ fs_node_t * check = finddir_ext2(parent, name); if (check) { debug_print(WARNING, "A file by this name already exists: %s", name); free(check); return -EEXIST; /* this should probably have a return value... */ } /* Allocate an inode for it */ unsigned int inode_no = allocate_inode(this); ext2_inodetable_t * inode = read_inode(this,inode_no); /* Set the access and creation times to now */ inode->atime = now(); inode->ctime = inode->atime; inode->mtime = inode->atime; inode->dtime = 0; /* This inode was never deleted */ /* Empty the file */ memset(inode->block, 0x00, sizeof(inode->block)); inode->blocks = 0; inode->size = 0; /* empty */ /* Assign it to current user */ inode->uid = this_core->current_process->user; inode->gid = this_core->current_process->user_group; /* misc */ inode->faddr = 0; inode->links_count = 1; /* The one we're about to create. */ inode->flags = 0; inode->osd1 = 0; inode->generation = 0; inode->file_acl = 0; inode->dir_acl = 0; inode->mode = EXT2_S_IFLNK; /* I *think* this is what you're supposed to do with symlinks */ inode->mode |= 0777; /* Write the osd blocks to 0 */ memset(inode->osd2, 0x00, sizeof(inode->osd2)); size_t target_len = strlen(target); int embedded = target_len <= 60; // sizeof(_symlink(inode)); if (embedded) { memcpy(_symlink(inode), target, target_len); inode->size = target_len; } /* Write out inode changes */ write_inode(this, inode, inode_no); /* Now append the entry to the parent */ create_entry(parent, name, inode_no); /* If we didn't embed it in the inode just use write_inode_buffer to finish the job */ if (!embedded) { write_inode_buffer(parent->device, inode, inode_no, 0, target_len, (uint8_t *)target); } free(inode); return 0; } static ssize_t readlink_ext2(fs_node_t * node, char * buf, size_t size) { ext2_fs_t * this = (ext2_fs_t *)node->device; ext2_inodetable_t * inode = read_inode(this, node->inode); size_t read_size = inode->size < size ? inode->size : size; if (inode->size > 60) { //sizeof(_symlink(inode))) { read_ext2(node, 0, read_size, (uint8_t *)buf); } else { memcpy(buf, _symlink(inode), read_size); } /* Believe it or not, we actually aren't supposed to include the nul in the length. */ if (read_size < size) { buf[read_size] = '\0'; } free(inode); return read_size; } static int ioctl_ext2(fs_node_t * node, unsigned long request, void * argp) { ext2_fs_t * this = (ext2_fs_t *)node->device; switch (request) { case IOCTLSYNC: return ioctl_fs(this->block_device, IOCTLSYNC, NULL); default: return -EINVAL; } } static int node_from_file(ext2_fs_t * this, ext2_inodetable_t *inode, ext2_dir_t *direntry, fs_node_t *fnode) { if (!fnode) { /* You didn't give me a node to write into, go **** yourself */ return 0; } /* Information from the direntry */ fnode->device = (void *)this; fnode->inode = direntry->inode; memcpy(&fnode->name, &direntry->name, direntry->name_len); fnode->name[direntry->name_len] = '\0'; /* Information from the inode */ fnode->uid = inode->uid; fnode->gid = inode->gid; fnode->length = inode->size; fnode->mask = inode->mode & 0xFFF; fnode->nlink = inode->links_count; /* File Flags */ fnode->flags = 0; if ((inode->mode & EXT2_S_IFREG) == EXT2_S_IFREG) { fnode->flags |= FS_FILE; fnode->read = read_ext2; fnode->write = write_ext2; fnode->truncate = truncate_ext2; fnode->create = NULL; fnode->mkdir = NULL; fnode->readdir = NULL; fnode->finddir = NULL; fnode->symlink = NULL; fnode->readlink = NULL; } if ((inode->mode & EXT2_S_IFDIR) == EXT2_S_IFDIR) { fnode->flags |= FS_DIRECTORY; fnode->create = create_ext2; fnode->mkdir = mkdir_ext2; fnode->unlink = unlink_ext2; fnode->symlink = symlink_ext2; fnode->readdir = readdir_ext2; fnode->finddir = finddir_ext2; fnode->write = NULL; fnode->readlink = NULL; } if ((inode->mode & EXT2_S_IFBLK) == EXT2_S_IFBLK) { fnode->flags |= FS_BLOCKDEVICE; } if ((inode->mode & EXT2_S_IFCHR) == EXT2_S_IFCHR) { fnode->flags |= FS_CHARDEVICE; } if ((inode->mode & EXT2_S_IFIFO) == EXT2_S_IFIFO) { fnode->flags |= FS_PIPE; } if ((inode->mode & EXT2_S_IFLNK) == EXT2_S_IFLNK) { fnode->flags |= FS_SYMLINK; fnode->read = NULL; fnode->write = NULL; fnode->create = NULL; fnode->mkdir = NULL; fnode->readdir = NULL; fnode->finddir = NULL; fnode->readlink = readlink_ext2; } fnode->atime = inode->atime; fnode->mtime = inode->mtime; fnode->ctime = inode->ctime; fnode->chmod = chmod_ext2; fnode->open = open_ext2; fnode->close = close_ext2; fnode->ioctl = ioctl_ext2; return 1; } static int ext2_root(ext2_fs_t * this, ext2_inodetable_t *inode, fs_node_t *fnode) { if (!fnode) { return 0; } /* Information for root dir */ fnode->device = (void *)this; fnode->inode = 2; fnode->name[0] = '/'; fnode->name[1] = '\0'; /* Information from the inode */ fnode->uid = inode->uid; fnode->gid = inode->gid; fnode->length = inode->size; fnode->mask = inode->mode & 0xFFF; fnode->nlink = inode->links_count; /* File Flags */ fnode->flags = 0; if ((inode->mode & EXT2_S_IFREG) == EXT2_S_IFREG) { debug_print(CRITICAL, "Root appears to be a regular file."); debug_print(CRITICAL, "This is probably very, very wrong."); return 0; } if ((inode->mode & EXT2_S_IFDIR) == EXT2_S_IFDIR) { } else { debug_print(CRITICAL, "Root doesn't appear to be a directory."); debug_print(CRITICAL, "This is probably very, very wrong."); debug_print(ERROR, "Other useful information:"); debug_print(ERROR, "%d", inode->uid); debug_print(ERROR, "%d", inode->gid); debug_print(ERROR, "%d", inode->size); debug_print(ERROR, "%d", inode->mode); debug_print(ERROR, "%d", inode->links_count); return 0; } if ((inode->mode & EXT2_S_IFBLK) == EXT2_S_IFBLK) { fnode->flags |= FS_BLOCKDEVICE; } if ((inode->mode & EXT2_S_IFCHR) == EXT2_S_IFCHR) { fnode->flags |= FS_CHARDEVICE; } if ((inode->mode & EXT2_S_IFIFO) == EXT2_S_IFIFO) { fnode->flags |= FS_PIPE; } if ((inode->mode & EXT2_S_IFLNK) == EXT2_S_IFLNK) { fnode->flags |= FS_SYMLINK; } fnode->atime = inode->atime; fnode->mtime = inode->mtime; fnode->ctime = inode->ctime; fnode->flags |= FS_DIRECTORY; fnode->read = NULL; fnode->write = NULL; fnode->chmod = chmod_ext2; fnode->open = open_ext2; fnode->close = close_ext2; fnode->readdir = readdir_ext2; fnode->finddir = finddir_ext2; fnode->ioctl = NULL; fnode->create = create_ext2; fnode->mkdir = mkdir_ext2; fnode->unlink = unlink_ext2; return 1; } static fs_node_t * mount_ext2(fs_node_t * block_device, int flags) { ext2_fs_t * this = malloc(sizeof(ext2_fs_t)); memset(this, 0x00, sizeof(ext2_fs_t)); this->flags = flags; this->block_device = block_device; this->block_size = 1024; /* We need to keep an owned refcount to this device if it was something we opened... */ //vfs_lock(this->block_device); this->mutex = mutex_init("ext2 fs"); SB = malloc(this->block_size); debug_print(INFO, "Reading superblock..."); read_block(this, 1, (uint8_t *)SB); if (SB->magic != EXT2_SUPER_MAGIC) { debug_print(ERROR, "... not an EXT2 filesystem? (magic didn't match, got 0x%x)", SB->magic); return NULL; } this->inode_size = SB->inode_size; if (SB->inode_size == 0) { this->inode_size = 128; } this->block_size = 1024 << SB->log_block_size; this->pointers_per_block = this->block_size / 4; debug_print(INFO, "Log block size = %d -> %d", SB->log_block_size, this->block_size); BGDS = SB->blocks_count / SB->blocks_per_group; if (SB->blocks_per_group * BGDS < SB->blocks_count) { BGDS += 1; } this->inodes_per_group = SB->inodes_count / BGDS; // load the block group descriptors this->bgd_block_span = sizeof(ext2_bgdescriptor_t) * BGDS / this->block_size + 1; BGD = malloc(this->block_size * this->bgd_block_span); debug_print(INFO, "bgd_block_span = %d", this->bgd_block_span); this->bgd_offset = 2; if (this->block_size > 1024) { this->bgd_offset = 1; } for (int i = 0; i < this->bgd_block_span; ++i) { read_block(this, this->bgd_offset + i, (uint8_t *)((uintptr_t)BGD + this->block_size * i)); } dprintf("ext2: %u BGDs, %u inodes, %u inodes per group\n", BGDS, SB->inodes_count, this->inodes_per_group); #if 1 // DEBUG_BLOCK_DESCRIPTORS char * bg_buffer = malloc(this->block_size * sizeof(char)); for (uint32_t i = 0; i < BGDS; ++i) { debug_print(INFO, "Block Group Descriptor #%d @ %d", i, this->bgd_offset + i * SB->blocks_per_group); debug_print(INFO, "\tBlock Bitmap @ %d", BGD[i].block_bitmap); { debug_print(INFO, "\t\tExamining block bitmap at %d", BGD[i].block_bitmap); read_block(this, BGD[i].block_bitmap, (uint8_t *)bg_buffer); uint32_t j = 0; while (BLOCKBIT(j)) { ++j; } debug_print(INFO, "\t\tFirst free block in group is %d", j + BGD[i].block_bitmap - 2); } debug_print(INFO, "\tInode Bitmap @ %d", BGD[i].inode_bitmap); { debug_print(INFO, "\t\tExamining inode bitmap at %d", BGD[i].inode_bitmap); read_block(this, BGD[i].inode_bitmap, (uint8_t *)bg_buffer); uint32_t j = 0; while (BLOCKBIT(j)) { ++j; } debug_print(INFO, "\t\tFirst free inode in group is %d", j + this->inodes_per_group * i + 1); } debug_print(INFO, "\tInode Table @ %d", BGD[i].inode_table); debug_print(INFO, "\tFree Blocks = %d", BGD[i].free_blocks_count); debug_print(INFO, "\tFree Inodes = %d", BGD[i].free_inodes_count); } free(bg_buffer); #endif ext2_inodetable_t *root_inode = read_inode(this, 2); RN = (fs_node_t *)malloc(sizeof(fs_node_t)); if (!ext2_root(this, root_inode, RN)) { return NULL; } debug_print(NOTICE, "Mounted EXT2 disk, root VFS node is at %#zx", (uintptr_t)RN); return RN; } fs_node_t * ext2_fs_mount(const char * device, const char * mount_path) { char * arg = strdup(device); char * argv[10]; int argc = tokenize(arg, ",", argv); fs_node_t * dev = kopen(argv[0], 0); if (!dev) { return NULL; } int flags = 0; for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i],"rw")) { flags |= EXT2_FLAG_READWRITE; } if (!strcmp(argv[i],"verbose")) { flags |= EXT2_FLAG_LOUD; } } return mount_ext2(dev, flags); } static int init(int argc, char * argv[]) { vfs_register("ext2", ext2_fs_mount); return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "ext2", .init = init, .fini = fini, }; ================================================ FILE: modules/hda.c ================================================ /** * @file kernel/audio/hda.c * @brief Driver for the Intel High Definition Audio. * @package x86_64 * * @warning This is a stub driver. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #if 0 static snd_knob_t _knobs[] = { { "Master", SND_KNOB_MASTER } }; static int hda_mixer_read(uint32_t knob_id, uint32_t *val); static int hda_mixer_write(uint32_t knob_id, uint32_t val); /** * TODO: We should generate this dynamically * for each card based on the ports we find. */ static snd_device_t _snd = { .name = "Intel HDA", .device = NULL, .playback_speed = 48000, .playback_format = SND_FORMAT_L16SLE, .knobs = _knobs, .num_knobs = 1, .mixer_read = hda_mixer_read, .mixer_write = hda_mixer_write, }; static int hda_mixer_read(uint32_t knob_id, uint32_t *val) { return 0; } static int hda_mixer_write(uint32_t knob_id, uint32_t val) { return 0; } #endif static void hda_setup(uint32_t device) { /* Map MMIO */ uintptr_t mmio_addr = pci_read_field(device, PCI_BAR0, 4) & 0xFFFFFFFE; void* mapped_mmio = mmu_map_mmio_region(mmio_addr, 0x1000 * 8); /* TODO size? */ /* Enable bus mastering, MMIO */ pci_write_field(device, PCI_COMMAND, 2, 0x6); /* Enable controller */ ((volatile uint32_t*)mapped_mmio)[2] |= 1; while (!(((volatile uint32_t*)mapped_mmio)[2]) & 0x1); printf("hda: codec bitmap: %04x\n", ((volatile uint16_t*)mapped_mmio)[7]); /* Stop DMA engine */ /* Configure DMA engine */ /* Map space for DMA */ /* Set up ring buffer pointers */ /* Then do the same for the DMA response ring buffer... ? */ } static void find_hda(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) { if (vendorid == 0x8086 && deviceid == 0x2668) { hda_setup(device); } } static int hda_install(int argc, char * argv[]) { pci_scan(&find_hda, -1, NULL); return 0; } static int fini(void) { #if 0 snd_unregister(&_snd); free(_device.bdl); for (int i = 0; i < AC97_BDL_LEN; i++) { free(_device.bufs[i]); } #endif return 0; } struct Module metadata = { .name = "hda", .init = hda_install, .fini = fini, }; ================================================ FILE: modules/i965.c ================================================ /** * @file modules/i965.c * @brief Bitbanged modeset driver for a ThinkPad T410's Intel graphics. * @package x86_64 * * This is NOT a viable driver for Intel graphics devices. It assumes Vesa * has already properly set up the display pipeline with the needed timings * for the panel on one particular model of Lenovo ThinkPad and then sets * a handful of registers to get the framebuffer into the right resolution. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #define REG_PIPEASRC 0x6001C #define REG_PIPEACONF 0x70008 #define PIPEACONF_ENABLE (1 << 31) #define PIPEACONF_STATE (1 << 30) #define REG_DSPALINOFF 0x70184 #define REG_DSPASTRIDE 0x70188 #define REG_DSPASURF 0x7019c extern fs_node_t * lfb_device; extern int lfb_use_write_combining; static uintptr_t ctrl_regs = 0; static uint32_t i965_mmio_read(uint32_t reg) { return *(volatile uint32_t*)(ctrl_regs + reg); } static void i965_mmio_write(uint32_t reg, uint32_t val) { *(volatile uint32_t*)(ctrl_regs + reg) = val; } static void split(uint32_t val, uint32_t * a, uint32_t * b) { *a = (val & 0xFFFF) + 1; *b = (val >> 16) + 1; } static void i965_modeset(uint16_t x, uint16_t y) { /* Disable pipe A while we update source size */ uint32_t pipe = i965_mmio_read(REG_PIPEACONF); i965_mmio_write(REG_PIPEACONF, pipe & ~PIPEACONF_ENABLE); while (i965_mmio_read(REG_PIPEACONF) & PIPEACONF_STATE); /* Set source size */ i965_mmio_write(REG_PIPEASRC, ((x - 1) << 16) | (y - 1)); /* Re-enable pipe */ pipe = i965_mmio_read(REG_PIPEACONF); i965_mmio_write(REG_PIPEACONF, pipe | PIPEACONF_ENABLE); while (!(i965_mmio_read(REG_PIPEACONF) & PIPEACONF_STATE)); /* Keep the plane enabled while we update stride value */ i965_mmio_write(REG_DSPALINOFF, 0); /* offset to default of 0 */ i965_mmio_write(REG_DSPASTRIDE, x * 4); /* stride to 4 x width */ i965_mmio_write(REG_DSPASURF, 0); /* write to surface address triggers change; use default of 0 */ /* Update the values we expose to userspace. */ lfb_resolution_x = x; lfb_resolution_y = y; lfb_resolution_b = 32; lfb_resolution_s = i965_mmio_read(REG_DSPASTRIDE); lfb_memsize = lfb_resolution_s * lfb_resolution_y; lfb_device->length = lfb_memsize; } extern void fbterm_draw_logo(void); extern void fbterm_reset(void); static void setup_framebuffer(uint32_t pcidev) { /* Map BAR space for the control registers */ uint32_t ctrl_space = pci_read_field(pcidev, PCI_BAR0, 4); pci_write_field(pcidev, PCI_BAR0, 4, 0xFFFFFFFF); uint32_t ctrl_size = pci_read_field(pcidev, PCI_BAR0, 4); ctrl_size = ~(ctrl_size & -15) + 1; pci_write_field(pcidev, PCI_BAR0, 4, ctrl_space); ctrl_space &= 0xFFFFFF00; ctrl_regs = (uintptr_t)mmu_map_mmio_region(ctrl_space, ctrl_size); lfb_resolution_impl = i965_modeset; lfb_set_resolution(1440,900); lfb_use_write_combining = 1; /* Normally we don't clear the screen on mode set, but we should do it here */ memset(lfb_vid_memory, 0, lfb_memsize); /* Redraw the boot logo; if we were loaded by userspace, it'll probably * be overwritten pretty quickly by the compositor? But whatever... */ fbterm_reset(); fbterm_draw_logo(); /* Helpful to know why the console text got cleared */ dprintf("i965: video configured for %u x %u\n", lfb_resolution_x, lfb_resolution_y); } static void find_intel(uint32_t device, uint16_t v, uint16_t d, void * extra) { if (v == 0x8086 && d == 0x0046) { setup_framebuffer(device); } } static int i965_install(int argc, char * argv[]) { if (args_present("noi965")) return -ENODEV; if (!lfb_resolution_x) return -ENODEV; pci_scan(find_intel, -1, NULL); return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "i965", .init = i965_install, .fini = fini, }; ================================================ FILE: modules/iso9660.c ================================================ /** * @file modules/iso9660.c * @brief ISO9660 "High Sierra" CD file system driver. * @package x86_64 * @package aarch64 * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #define ISO_SECTOR_SIZE 2048 #define FLAG_HIDDEN 0x01 #define FLAG_DIRECTORY 0x02 #define FLAG_ASSOCIATED 0x04 #define FLAG_EXTENDED 0x08 #define FLAG_PERMISSIONS 0x10 #define FLAG_CONTINUES 0x80 typedef struct { fs_node_t * block_device; uint32_t block_size; hashmap_t * cache; list_t * lru; } iso_9660_fs_t; typedef struct { char year[4]; char month[2]; char day[2]; char hour[2]; char minute[2]; char second[2]; char hundredths[2]; int8_t timezone; } __attribute__((packed)) iso_9660_datetime_t; typedef struct { uint8_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; int8_t timezone; } __attribute__((packed)) iso_9660_rec_date_t; typedef struct { uint8_t length; uint8_t ext_length; uint32_t extent_start_LSB; uint32_t extent_start_MSB; uint32_t extent_length_LSB; uint32_t extent_length_MSB; iso_9660_rec_date_t record_date; uint8_t flags; uint8_t interleave_units; uint8_t interleave_gap; uint16_t volume_seq_LSB; uint16_t volume_seq_MSB; uint8_t name_len; char name[]; } __attribute__((packed)) iso_9660_directory_entry_t; typedef struct { uint8_t type; /* 0x01 */ char id[5]; /* CD001 */ uint8_t version; uint8_t _unused0; char system_id[32]; char volume_id[32]; uint8_t _unused1[8]; uint32_t volume_space_LSB; uint32_t volume_space_MSB; uint8_t _unused2[32]; uint16_t volume_set_LSB; uint16_t volume_set_MSB; uint16_t volume_seq_LSB; uint16_t volume_seq_MSB; uint16_t logical_block_size_LSB; uint16_t logical_block_size_MSB; uint32_t path_table_size_LSB; uint32_t path_table_size_MSB; uint32_t path_table_LSB; uint32_t optional_path_table_LSB; uint32_t path_table_MSB; uint32_t optional_path_table_MSB; /* iso_9660_directory_entry_t */ char root[34]; char volume_set_id[128]; char volume_publisher[128]; char data_preparer[128]; char application_id[128]; char copyright_file[38]; char abstract_file[36]; char bibliographic_file[37]; iso_9660_datetime_t creation; iso_9660_datetime_t modification; iso_9660_datetime_t expiration; iso_9660_datetime_t effective; uint8_t file_structure_version; uint8_t _unused_3; char application_use[]; } __attribute__((packed)) iso_9660_volume_descriptor_t; static 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); #define CACHE_SIZE 64 static int read_sector(iso_9660_fs_t * this, uint32_t sector_id, char * buffer) { if (this->cache) { void * sector_id_v = (void *)(uintptr_t)sector_id; if (hashmap_has(this->cache, sector_id_v)) { memcpy(buffer,hashmap_get(this->cache, sector_id_v), this->block_size); node_t * me = list_find(this->lru, sector_id_v); list_delete(this->lru, me); list_append(this->lru, me); return 0; } else { if (this->lru->length > CACHE_SIZE) { node_t * l = list_dequeue(this->lru); free(hashmap_get(this->cache, l->value)); hashmap_remove(this->cache, l->value); free(l); } int result = read_fs(this->block_device, sector_id * this->block_size, this->block_size, (uint8_t *)buffer); if (result < 0) return result; if (result == 0) return 1; char * buf = malloc(this->block_size); memcpy(buf, buffer, this->block_size); hashmap_set(this->cache, sector_id_v, buf); list_insert(this->lru, sector_id_v); return 0; } } else { int result = read_fs(this->block_device, sector_id * this->block_size, this->block_size, (uint8_t *)buffer); if (result < 0) return result; if (result == 0) return 1; return 0; } } static void inplace_lower(char * string) { while (*string) { if (*string >= 'A' && *string <= 'Z') { *string += ('a' - 'A'); } string++; } } static void open_iso(fs_node_t *node, unsigned int flags) { /* Nothing to do here */ } static void close_iso(fs_node_t *node) { /* Nothing to do here */ } static struct dirent * readdir_iso(fs_node_t *node, unsigned long index) { if (index == 0) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, "."); return out; } if (index == 1) { struct dirent * out = malloc(sizeof(struct dirent)); memset(out, 0x00, sizeof(struct dirent)); out->d_ino = 0; strcpy(out->d_name, ".."); return out; } iso_9660_fs_t * this = node->device; char * buffer = malloc(this->block_size); read_sector(this, node->inode, buffer); iso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(buffer + node->impl); uint8_t * root_data = malloc(root_entry->extent_length_LSB); uint8_t * offset = root_data; size_t sector_offset = 0; size_t length_to_read = root_entry->extent_length_LSB; while (length_to_read) { read_sector(this, root_entry->extent_start_LSB + sector_offset, (char*)offset); if (length_to_read >= this->block_size) { offset += this->block_size; sector_offset += 1; length_to_read -= this->block_size; } else { break; } } /* Examine directory */ offset = root_data; unsigned int i = 0; struct dirent *dirent = malloc(sizeof(struct dirent)); fs_node_t * out = malloc(sizeof(fs_node_t)); memset(dirent, 0, sizeof(struct dirent)); while (1) { iso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)offset; if (dir->length == 0) { if ((size_t)(offset - root_data) < root_entry->extent_length_LSB) { offset += 1; // this->block_size - ((uintptr_t)offset % this->block_size); goto try_again; } break; } if (!(dir->flags & FLAG_HIDDEN)) { if (i == index) { file_from_dir_entry(this, (root_entry->extent_start_LSB)+(offset - root_data)/this->block_size, dir, (offset - root_data) % this->block_size, out); memcpy(&dirent->d_name, out->name, strlen(out->name)+1); dirent->d_ino = out->inode; goto cleanup; } i += 1; } offset += dir->length; try_again: if ((size_t)(offset - root_data) >= root_entry->extent_length_LSB) break; } free(dirent); dirent = NULL; cleanup: free(root_data); free(buffer); free(out); return dirent; } static ssize_t read_iso(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { iso_9660_fs_t * this = node->device; char * tmp = malloc(this->block_size); read_sector(this, node->inode, tmp); iso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(tmp + node->impl); uint32_t end; /* We can do this in a single underlying read to the filesystem */ if (offset + size > root_entry->extent_length_LSB) { end = root_entry->extent_length_LSB; } else { end = offset + size; } uint32_t size_to_read = end - offset; read_fs(this->block_device, root_entry->extent_start_LSB * this->block_size + offset, size_to_read, (uint8_t *)buffer); free(tmp); return size_to_read; } static fs_node_t * finddir_iso(fs_node_t *node, char *name) { iso_9660_fs_t * this = node->device; char * buffer = malloc(this->block_size); read_sector(this, node->inode, buffer); iso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)(buffer + node->impl); uint8_t * root_data = malloc(root_entry->extent_length_LSB); uint8_t * offset = root_data; size_t sector_offset = 0; size_t length_to_read = root_entry->extent_length_LSB; while (length_to_read) { read_sector(this, root_entry->extent_start_LSB + sector_offset, (char*)offset); if (length_to_read >= this->block_size) { offset += this->block_size; sector_offset += 1; length_to_read -= this->block_size; } else { break; } } /* Examine directory */ offset = root_data; fs_node_t * out = malloc(sizeof(fs_node_t)); while (1) { iso_9660_directory_entry_t * dir = (iso_9660_directory_entry_t *)offset; if (dir->length == 0) { if ((size_t)(offset - root_data) < root_entry->extent_length_LSB) { offset += 1; // this->block_size - ((uintptr_t)offset % this->block_size); goto try_next_finddir; } break; } if (!(dir->flags & FLAG_HIDDEN)) { memset(out, 0, sizeof(fs_node_t)); file_from_dir_entry(this, (root_entry->extent_start_LSB)+(offset - root_data)/this->block_size, dir, (offset - root_data) % this->block_size, out); if (!strcmp(out->name, name)) { goto cleanup; /* found it */ } } offset += dir->length; try_next_finddir: if ((size_t)(offset - root_data) > root_entry->extent_length_LSB) break; } free(out); out = NULL; cleanup: free(root_data); free(buffer); return out; } static 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) { fs->device = this; fs->inode = sector; /* Sector the file is in */ fs->impl = offset; /* Offset */ char * file_name = malloc(dir->name_len + 1); memcpy(file_name, dir->name, dir->name_len); file_name[dir->name_len] = 0; inplace_lower(file_name); char * dot = strchr(file_name, '.'); if (!dot) { /* It's a directory. */ } else { char * ext = dot + 1; char * semi = strchr(ext, ';'); if (semi) { *semi = 0; } if (strlen(ext) == 0) { *dot = 0; } else { char * derp = ext; while (*derp == '.') derp++; if (derp != ext) { memmove(ext, derp, strlen(derp)+1); } } } memcpy(fs->name, file_name, strlen(file_name)+1); free(file_name); fs->uid = 0; fs->gid = 0; fs->length = dir->extent_length_LSB; fs->mask = 0555; fs->nlink = 0; /* Unsupported */ if (dir->flags & FLAG_DIRECTORY) { fs->flags = FS_DIRECTORY; fs->readdir = readdir_iso; fs->finddir = finddir_iso; } else { fs->flags = FS_FILE; fs->read = read_iso; } /* Other things not supported */ /* TODO actually get these from the CD into Unix time */ fs->atime = now(); fs->mtime = now(); fs->ctime = now(); fs->open = open_iso; fs->close = close_iso; } static fs_node_t * iso_fs_mount(const char * device, const char * mount_path) { char * arg = strdup(device); char * argv[10]; int argc = tokenize(arg, ",", argv); fs_node_t * dev = kopen(argv[0], 0); if (!dev) { return NULL; } int cache = 1; for (int i = 1; i < argc; ++i) { if (!strcmp(argv[i],"nocache")) { cache = 0; } } if (!dev) { free(arg); return NULL; } iso_9660_fs_t * this = malloc(sizeof(iso_9660_fs_t)); this->block_device = dev; this->block_size = ISO_SECTOR_SIZE; if (cache) { this->cache = hashmap_create_int(10); this->lru = list_create("iso9660 lru cache", this); } else { this->cache = NULL; } /* Probably want to put a block cache on this like EXT2 driver does; or do that in the ATAPI layer... */ /* Read the volume descriptors */ uint8_t * tmp = malloc(ISO_SECTOR_SIZE); int i = 0x10; int found = 0; while (1) { if (read_sector(this,i,(char*)tmp)) break; if (tmp[0] == 0x00) { //debug_print(WARNING, " Boot Record"); } else if (tmp[0] == 0x01) { //debug_print(WARNING, " Primary Volume Descriptor"); found = 1; break; } else if (tmp[0] == 0x02) { //debug_print(WARNING, " Secondary Volume Descriptor"); } else if (tmp[0] == 0x03) { //debug_print(WARNING, " Volume Partition Descriptor"); } if (tmp[0] == 0xFF) break; i++; } if (!found) { //debug_print(WARNING, "No primary volume descriptor?"); free(arg); return NULL; } iso_9660_volume_descriptor_t * root = (iso_9660_volume_descriptor_t *)tmp; iso_9660_directory_entry_t * root_entry = (iso_9660_directory_entry_t *)&root->root; fs_node_t * fs = malloc(sizeof(fs_node_t)); memset(fs, 0, sizeof(fs_node_t)); file_from_dir_entry(this, i, root_entry, 156, fs); free(arg); return fs; } static int init(int argc, char * argv[]) { vfs_register("iso", iso_fs_mount); return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "iso9660", .init = init, .fini = fini, }; ================================================ FILE: modules/pcspkr.c ================================================ /** * @file modules/pcspkr.c * @brief PC beeper device interface * @package x86_64 * * Use with @ref apps/beep.c to play music. * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include static void note(int length, int freq) { uint8_t t; if (length == 0) { t = inportb(0x61) & 0xFC; outportb(0x61, t); return; } uint32_t div = 11931800 / freq; outportb(0x43, 0xb6); outportb(0x42, (uint8_t)(div)); outportb(0x42, (uint8_t)(div >> 8)); t = inportb(0x61); outportb(0x61, t | 0x3); if (length > 0) { unsigned long s, ss; relative_time(length / 1000, (length % 1000) * 1000, &s, &ss); sleep_until((process_t*)this_core->current_process, s, ss); switch_task(0); t = inportb(0x61) & 0xFC; outportb(0x61, t); } } struct spkr { int length; int frequency; }; static ssize_t write_spkr(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { if (!size % (sizeof(struct spkr))) { return 0; } struct spkr * s = (struct spkr *)buffer; while ((uintptr_t)s < (uintptr_t)buffer + size) { note(s->length, s->frequency); s++; } return (uintptr_t)s - (uintptr_t)buffer; } static fs_node_t * spkr_device_create(void) { fs_node_t * fnode = malloc(sizeof(fs_node_t)); memset(fnode, 0x00, sizeof(fs_node_t)); snprintf(fnode->name, 5, "spkr"); fnode->mask = 0660; /* TODO need a speaker group */ fnode->gid = 1; fnode->flags = FS_CHARDEVICE; fnode->write = write_spkr; return fnode; } static int init(int argc, char * argv[]) { fs_node_t * node = spkr_device_create(); vfs_mount("/dev/spkr", node, "pcspkr", ""); return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "pcspkr", .init = init, .fini = fini, }; ================================================ FILE: modules/piix4.c ================================================ /** * @file modules/piix4.c * @brief Intel PIIX4 ISA Bridge Driver * @package x86_64 * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #define PIIX4_PCI_PIRQRC 0x60 static void find_isa_bridge(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) { if (vendorid == 0x8086 && (deviceid == 0x7000 || deviceid == 0x7110)) { *((uint32_t *)extra) = device; } } static uint32_t pci_isa = 0; static int pci_isa_base_slot = 0; static int pci_isa_bus_offset = 0; static int pci_isa_last_bus = 0; static uint8_t pci_remaps[4] = {0}; static void piix_remap(uint32_t device, uint16_t vendorid, uint16_t deviceid, void * extra) { uint32_t irq_pin = pci_read_field(device, PCI_INTERRUPT_PIN, 1); uint32_t irq_line = pci_read_field(device, PCI_INTERRUPT_LINE, 1); if (irq_pin == 0) return; if (pci_extract_bus(device) != pci_isa_last_bus) { pci_isa_bus_offset++; pci_isa_last_bus = pci_extract_bus(device); } /* Calculate PIRQ from pin and device slot. */ int slot = (pci_extract_slot(device) - pci_isa_base_slot) % 4; int bus = (pci_isa_bus_offset) % 4; int pirq = (slot + irq_pin + bus - 1) % 4; if (irq_line < 32 && irq_line != pci_remaps[pirq]) { pci_write_field(device, PCI_INTERRUPT_LINE, 1, pci_remaps[pirq]); } } static int init(int argc, char * argv[]) { if (args_present("nopciremap")) return -ENODEV; pci_scan(&find_isa_bridge, -1, &pci_isa); if (!pci_isa) { return -ENODEV; } pci_isa_base_slot = pci_extract_slot(pci_isa); pci_isa_last_bus = pci_extract_bus(pci_isa); for (int i = 0; i < 4; ++i) { pci_remaps[i] = pci_read_field(pci_isa, PIIX4_PCI_PIRQRC + i, 1); } uint32_t out = 0; memcpy(&out, &pci_remaps, 4); pci_write_field(pci_isa, PIIX4_PCI_PIRQRC, 4, out); pci_scan(piix_remap, -1, NULL); return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "piix4", .init = init, .fini = fini, }; ================================================ FILE: modules/test.c ================================================ /** * @file modules/test.c * @brief Test module. * @package x86_64 * @package aarch64 * * Load with various arguments to do things like crash the * kernel or print tracebacks. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include static int init(int argc, char * argv[]) { dprintf("Hello, modules.\n"); dprintf("Received %d arguments.\n", argc); if (argc > 1 && !strcmp(argv[1], "--traceback")) { arch_dump_traceback(); } else if (argc > 1 && !strcmp(argv[1], "--fail")) { return 1; } else if (argc > 1 && !strcmp(argv[1], "--crash")) { *(volatile int*)0x60000000 = 42; } else if (argc > 1 && !strcmp(argv[1], "--assert")) { assert(0 && "Intentional failure."); } return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "test", .init = init, .fini = fini, }; ================================================ FILE: modules/vbox.c ================================================ /** * @file kernel/arch/x86_64/vbox.c * @brief VirtualBox Guest Additions driver * @package x86_64 * * Implements the following features: * - Absolute mouse cursor positioning * - "Hardware" cursor sprites * - Automatic display modesetting * - "Seamless" mode rectangle device * - Log device * * @copyright * 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-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VBOX_VENDOR_ID 0x80EE #define VBOX_DEVICE_ID 0xCAFE static void vbox_scan_pci(uint32_t device, uint16_t v, uint16_t d, void * extra) { if (v == VBOX_VENDOR_ID && d == VBOX_DEVICE_ID) { *((uint32_t *)extra) = device; } } #define VMM_GetMouseState 1 #define VMM_SetMouseState 2 #define VMM_SetPointerShape 3 #define VMM_AcknowledgeEvents 41 #define VMM_ReportGuestInfo 50 #define VMM_GetDisplayChangeRequest 51 #define VMM_ReportGuestCapabilities 55 #define VMM_VideoSetVisibleRegion 72 #define VMMCAP_SeamlessMode (1 << 0) #define VMMCAP_HostWindows (1 << 1) #define VMMCAP_Graphics (1 << 2) #define VMMDEV_VERSION 0x00010003 #define VBOX_REQUEST_HEADER_VERSION 0x10001 struct vbox_header { uint32_t size; uint32_t version; uint32_t requestType; int32_t rc; uint32_t reserved1; uint32_t reserved2; }; struct vbox_guest_info { struct vbox_header header; uint32_t version; uint32_t ostype; }; struct vbox_guest_caps { struct vbox_header header; uint32_t caps; }; struct vbox_ack_events { struct vbox_header header; uint32_t events; }; struct vbox_display_change { struct vbox_header header; uint32_t xres; uint32_t yres; uint32_t bpp; uint32_t eventack; }; struct vbox_mouse { struct vbox_header header; uint32_t features; int32_t x; int32_t y; }; struct vbox_rtrect { int32_t xLeft; int32_t yTop; int32_t xRight; int32_t yBottom; }; struct vbox_visibleregion { struct vbox_header header; uint32_t count; struct vbox_rtrect rect[1]; }; struct vbox_pointershape { struct vbox_header header; uint32_t flags; uint32_t xHot; uint32_t yHot; uint32_t width; uint32_t height; unsigned char data[]; }; #define EARLY_LOG_DEVICE 0x504 static size_t _vbox_write(size_t size, uint8_t * buffer) { for (unsigned int i = 0; i < size; ++i) { outportb(EARLY_LOG_DEVICE, buffer[i]); } return size; } void vbox_set_log(void) { printf_output = &_vbox_write; printf("Hello world, using VBox machine log for kernel output\n"); } static uint32_t vbox_device = 0; static uint32_t vbox_port = 0x0; static int vbox_irq = 0; static struct vbox_ack_events * vbox_irq_ack; static uint32_t vbox_phys_ack; static struct vbox_display_change * vbox_disp; static uint32_t vbox_phys_disp; static struct vbox_mouse * vbox_m; static uint32_t vbox_phys_mouse; static struct vbox_mouse * vbox_mg; static uint32_t vbox_phys_mouse_get; static struct vbox_visibleregion * vbox_visibleregion; static uint32_t vbox_phys_visibleregion; static struct vbox_pointershape * vbox_pointershape = NULL; static uint32_t vbox_phys_pointershape; static volatile uint32_t * vbox_vmmdev = 0; static fs_node_t * mouse_pipe; static fs_node_t * rect_pipe; static fs_node_t * pointer_pipe; static int mouse_state; #define PACKETS_IN_PIPE 1024 #define DISCARD_POINT 32 #define VMM_Event_DisplayChange (1 << 2) static void vbox_do_modeset(void) { outportl(vbox_port, vbox_phys_disp); outportl(vbox_port, vbox_phys_disp); if (lfb_resolution_x && vbox_disp->xres && (vbox_disp->xres != lfb_resolution_x || vbox_disp->yres != lfb_resolution_y)) { lfb_set_resolution(vbox_disp->xres, vbox_disp->yres); } } #define VMM_Event_Mouse (1 << 9) static void vbox_do_mouse(void) { outportl(vbox_port, vbox_phys_mouse_get); unsigned int x, y; if (lfb_vid_memory && lfb_resolution_x && lfb_resolution_y && vbox_mg->x && vbox_mg->y) { x = ((unsigned int)vbox_mg->x * lfb_resolution_x) / 0xFFFF; y = ((unsigned int)vbox_mg->y * lfb_resolution_y) / 0xFFFF; } else { x = vbox_mg->x; y = vbox_mg->y; } mouse_device_packet_t packet; packet.magic = MOUSE_MAGIC; packet.x_difference = x; packet.y_difference = y; packet.buttons = 0; mouse_device_packet_t bitbucket; while (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) { read_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket); } write_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet); } static int vbox_irq_handler(struct regs *r) { if (!vbox_vmmdev[2]) return 0; uint32_t events; events = vbox_irq_ack->events = vbox_vmmdev[2]; outportl(vbox_port, vbox_phys_ack); irq_ack(vbox_irq); if (events & VMM_Event_Mouse) vbox_do_mouse(); if (events & VMM_Event_DisplayChange) vbox_do_modeset(); return 1; } #define VBOX_MOUSE_ON (1 << 0) | (1 << 4) #define VBOX_MOUSE_OFF (0) static void mouse_on_off(unsigned int status) { mouse_state = status; vbox_m->header.size = sizeof(struct vbox_mouse); vbox_m->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_m->header.requestType = VMM_SetMouseState; vbox_m->header.rc = 0; vbox_m->header.reserved1 = 0; vbox_m->header.reserved2 = 0; vbox_m->features = status; vbox_m->x = 0; vbox_m->y = 0; outportl(vbox_port, vbox_phys_mouse); } static int ioctl_mouse(fs_node_t * node, unsigned long request, void * argp) { if (request == 1) { /* Disable */ mouse_on_off(VBOX_MOUSE_OFF); return 0; } if (request == 2) { /* Enable */ mouse_on_off(VBOX_MOUSE_ON); return 0; } if (request == 3) { return mouse_state == (VBOX_MOUSE_ON); } return -1; } static ssize_t write_pointer(fs_node_t * node, off_t offset, size_t size, uint8_t *buffer) { if (!mouse_state) { return -1; } memcpy(&vbox_pointershape->data[288], buffer, 48*48*4); outportl(vbox_port, vbox_phys_pointershape); return size; } static ssize_t write_rectpipe(fs_node_t *node, off_t offset, size_t size, uint8_t *buffer) { (void)node; (void)offset; /* TODO should check size */ /* This is kinda special and always assumes everything was written at once. */ uint32_t count = ((uint32_t *)buffer)[0]; vbox_visibleregion->count = count; if (count > 254) count = 254; /* enforce maximum */ buffer += sizeof(uint32_t); for (unsigned int i = 0; i < count; ++i) { memcpy(&vbox_visibleregion->rect[i], buffer, sizeof(struct vbox_rtrect)); buffer += sizeof(struct vbox_rtrect); } vbox_visibleregion->header.size = sizeof(struct vbox_header) + sizeof(uint32_t) + sizeof(int32_t) * count * 4; outportl(vbox_port, vbox_phys_visibleregion); return size; } static void * kvmalloc_p(size_t size, uint32_t * outphys) { uintptr_t index = mmu_allocate_n_frames(size / 0x1000) << 12; *outphys = index; return mmu_map_from_physical(index); } static int vbox_install(int argc, char * argv[]) { if (args_present("novbox")) return -ENODEV; pci_scan(vbox_scan_pci, -1, &vbox_device); if (!vbox_device) return -ENODEV; if (!args_present("novboxdebug")) { vbox_set_log(); } uintptr_t t = pci_read_field(vbox_device, PCI_BAR0, 4); if (t > 0) { vbox_port = (t & 0xFFFFFFF0); } uint16_t c = pci_read_field(vbox_device, PCI_COMMAND, 2); //fprintf(&vb, "Command register: 0x%4x\n", c); if (!!(c & (1 << 10))) { //fprintf(&vb, "Interrupts are disabled\n"); } mouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE); mouse_pipe->flags = FS_CHARDEVICE; mouse_pipe->ioctl = ioctl_mouse; vfs_mount("/dev/absmouse", mouse_pipe, "vbox-tablet", ""); vbox_irq = pci_get_interrupt(vbox_device); //fprintf(&vb, "irq line is %d\n", vbox_irq); irq_install_handler(vbox_irq, vbox_irq_handler, "vbox"); uint32_t vbox_phys = 0; struct vbox_guest_info * packet = (void*)kvmalloc_p(0x1000, &vbox_phys); packet->header.size = sizeof(struct vbox_guest_info); packet->header.version = VBOX_REQUEST_HEADER_VERSION; packet->header.requestType = VMM_ReportGuestInfo; packet->header.rc = 0; packet->header.reserved1 = 0; packet->header.reserved2 = 0; packet->version = VMMDEV_VERSION; packet->ostype = 0x00100; /* Unknown, x86-64 */ outportl(vbox_port, vbox_phys); struct vbox_guest_caps * caps = (void*)kvmalloc_p(0x1000, &vbox_phys); caps->header.size = sizeof(struct vbox_guest_caps); caps->header.version = VBOX_REQUEST_HEADER_VERSION; caps->header.requestType = VMM_ReportGuestCapabilities; caps->header.rc = 0; caps->header.reserved1 = 0; caps->header.reserved2 = 0; caps->caps = VMMCAP_Graphics | (args_present("novboxseamless") ? 0 : VMMCAP_SeamlessMode); outportl(vbox_port, vbox_phys); vbox_irq_ack = (void*)kvmalloc_p(0x1000, &vbox_phys_ack); vbox_irq_ack->header.size = sizeof(struct vbox_ack_events); vbox_irq_ack->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_irq_ack->header.requestType = VMM_AcknowledgeEvents; vbox_irq_ack->header.rc = 0; vbox_irq_ack->header.reserved1 = 0; vbox_irq_ack->header.reserved2 = 0; vbox_irq_ack->events = 0; vbox_disp = (void*)kvmalloc_p(0x1000, &vbox_phys_disp); vbox_disp->header.size = sizeof(struct vbox_display_change); vbox_disp->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_disp->header.requestType = VMM_GetDisplayChangeRequest; vbox_disp->header.rc = 0; vbox_disp->header.reserved1 = 0; vbox_disp->header.reserved2 = 0; vbox_disp->xres = 0; vbox_disp->yres = 0; vbox_disp->bpp = 0; vbox_disp->eventack = 1; vbox_m = (void*)kvmalloc_p(0x1000, &vbox_phys_mouse); mouse_on_off(VBOX_MOUSE_ON); /* For use with later receives */ vbox_mg = (void*)kvmalloc_p(0x1000, &vbox_phys_mouse_get); vbox_mg->header.size = sizeof(struct vbox_mouse); vbox_mg->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_mg->header.requestType = VMM_GetMouseState; vbox_mg->header.rc = 0; vbox_mg->header.reserved1 = 0; vbox_mg->header.reserved2 = 0; if (!args_present("novboxpointer")) { vbox_pointershape = (void*)kvmalloc_p(0x4000, &vbox_phys_pointershape); if (vbox_pointershape) { //fprintf(&vb, "Got a valid set of pages to load up a cursor.\n"); vbox_pointershape->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_pointershape->header.requestType = VMM_SetPointerShape; vbox_pointershape->header.rc = 0; vbox_pointershape->header.reserved1 = 0; vbox_pointershape->header.reserved2 = 0; vbox_pointershape->flags = (1 << 0) | (1 << 1) | (1 << 2); /* visible, alpha, shape */ vbox_pointershape->xHot = 26; vbox_pointershape->yHot = 26; vbox_pointershape->width = 48; vbox_pointershape->height = 48; unsigned int mask_bytes = ((vbox_pointershape->width + 7) / 8) * vbox_pointershape->height; for (uint32_t i = 0; i < mask_bytes; ++i) { vbox_pointershape->data[i] = 0x00; } while (mask_bytes & 3) { mask_bytes++; } int base = mask_bytes; //fprintf(&vb, "mask_bytes = %d\n", mask_bytes); vbox_pointershape->header.size = sizeof(struct vbox_pointershape) + (48*48*4)+mask_bytes; /* update later */ for (int i = 0; i < 48 * 48; ++i) { vbox_pointershape->data[base+i*4] = 0x00; /* blue */ vbox_pointershape->data[base+i*4+1] = 0x00; /* red */ vbox_pointershape->data[base+i*4+2] = 0x00; /* green */ vbox_pointershape->data[base+i*4+3] = 0x00; /* alpha */ } outportl(vbox_port, vbox_phys_pointershape); if (vbox_pointershape->header.rc < 0) { //fprintf(&vb, "Bad response code: -%d\n", -vbox_pointershape->header.rc); } else { /* Success, let's install the device file */ //fprintf(&vb, "Successfully initialized cursor, going to allow compositor to set it.\n"); pointer_pipe = malloc(sizeof(fs_node_t)); memset(pointer_pipe, 0, sizeof(fs_node_t)); pointer_pipe->mask = 0666; pointer_pipe->flags = FS_CHARDEVICE; pointer_pipe->write = write_pointer; vfs_mount("/dev/vboxpointer", pointer_pipe, "vbox-pointer", ""); } } } if (!args_present("novboxseamless")) { vbox_visibleregion = (void*)kvmalloc_p(0x1000, &vbox_phys_visibleregion); vbox_visibleregion->header.size = sizeof(struct vbox_header) + sizeof(uint32_t) + sizeof(int32_t) * 4; /* TODO + more for additional rects? */ vbox_visibleregion->header.version = VBOX_REQUEST_HEADER_VERSION; vbox_visibleregion->header.requestType = VMM_VideoSetVisibleRegion; vbox_visibleregion->header.rc = 0; vbox_visibleregion->header.reserved1 = 0; vbox_visibleregion->header.reserved2 = 0; vbox_visibleregion->count = 1; vbox_visibleregion->rect[0].xLeft = 0; vbox_visibleregion->rect[0].yTop = 0; vbox_visibleregion->rect[0].xRight = 1440; vbox_visibleregion->rect[0].yBottom = 900; outportl(vbox_port, vbox_phys_visibleregion); rect_pipe = malloc(sizeof(fs_node_t)); memset(rect_pipe, 0, sizeof(fs_node_t)); rect_pipe->mask = 0666; rect_pipe->flags = FS_CHARDEVICE; rect_pipe->write = write_rectpipe; vfs_mount("/dev/vboxrects", rect_pipe, "vbox-rects", ""); } /* device memory region mapping? */ { uintptr_t t = pci_read_field(vbox_device, PCI_BAR1, 4); //fprintf(&vb, "mapping vmm_dev = 0x%x\n", t); if (t > 0) { vbox_vmmdev = mmu_map_from_physical(t & 0xFFFFFFF0); printf("Setting vbox mem device at %p\n", (void*)vbox_vmmdev); } } /* Try a mode set */ vbox_do_modeset(); vbox_vmmdev[3] = 0xFFFFFFFF; /* Enable all for now */ return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "vbox", .init = vbox_install, .fini = fini, }; ================================================ FILE: modules/vmware.c ================================================ /** * @file kernel/arch/x86_64/vmware.c * @brief VMware/QEMU mouse and VMWare backdoor driver. * @package x86_64 * * Supports absolute mouse cursor and resolution setting. * * Mouse: * Toggle off / on with ioctl 1 and 2 respectively to /dev/vmmouse. * Supports mouse buttons, unlike the one in VirtualBox. * This device is also available by default in QEMU. * * Resolution setting: * Enabled when the "vmware" LFB driver is active. Automatically * resizes the display when the window size changes. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2017-2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define VMWARE_MAGIC 0x564D5868 /* hXMV */ #define VMWARE_PORT 0x5658 #define VMWARE_PORTHB 0x5659 #define PACKETS_IN_PIPE 1024 #define DISCARD_POINT 32 #define CMD_GETVERSION 10 #define CMD_MESSAGE 30 #define CMD_ABSPOINTER_DATA 39 #define CMD_ABSPOINTER_STATUS 40 #define CMD_ABSPOINTER_COMMAND 41 #define ABSPOINTER_ENABLE 0x45414552 /* Q E A E */ #define ABSPOINTER_RELATIVE 0xF5 #define ABSPOINTER_ABSOLUTE 0x53424152 /* R A B S */ #define MESSAGE_RPCI 0x49435052 /* R P C I */ #define MESSAGE_TCLO 0x4f4c4354 /* T C L O */ /* -Wpedantic complains about unnamed unions */ #pragma GCC diagnostic ignored "-Wpedantic" extern void (*ps2_mouse_alternate)(uint8_t); /* modules/mouse.c */ static fs_node_t * mouse_pipe; typedef struct { union { uint32_t ax; uint32_t magic; }; union { uint32_t bx; size_t size; }; union { uint32_t cx; uint16_t command; }; union { uint32_t dx; uint16_t port; }; uintptr_t si; uintptr_t di; } vmware_cmd; /** Low bandwidth backdoor */ static void vmware_send(vmware_cmd * cmd) { cmd->magic = VMWARE_MAGIC; cmd->port = VMWARE_PORT; asm volatile("in %%dx, %0" : "+a"(cmd->ax), "+b"(cmd->bx), "+c"(cmd->cx), "+d"(cmd->dx), "+S"(cmd->si), "+D"(cmd->di)); } /** Output to high bandwidth backdoor */ static void vmware_send_hb(vmware_cmd * cmd) { cmd->magic = VMWARE_MAGIC; cmd->port = VMWARE_PORTHB; asm volatile("cld; rep; outsb" : "+a"(cmd->ax), "+b"(cmd->bx), "+c"(cmd->cx), "+d"(cmd->dx), "+S"(cmd->si), "+D"(cmd->di)); } /** Input from high bandwidth backdoor */ static void vmware_get_hb(vmware_cmd * cmd) { cmd->magic = VMWARE_MAGIC; cmd->port = VMWARE_PORTHB; asm volatile("cld; rep; insb" : "+a"(cmd->ax), "+b"(cmd->bx), "+c"(cmd->cx), "+d"(cmd->dx), "+S"(cmd->si), "+D"(cmd->di)); } static void mouse_off(void) { /* Disable the absolute mouse */ vmware_cmd cmd; cmd.bx = ABSPOINTER_RELATIVE; cmd.command = CMD_ABSPOINTER_COMMAND; vmware_send(&cmd); } static void mouse_absolute(void) { /* * Set the mouse to absolute. * * You can also set a relative mode, but there's not * a lot of use in that as disabling the device just * falls back to the PS/2 (or USB, I guess) device anyway, * so instead of using that we just... turn it off. */ vmware_cmd cmd; /* Enable */ cmd.bx = ABSPOINTER_ENABLE; cmd.command = CMD_ABSPOINTER_COMMAND; vmware_send(&cmd); /* Status */ cmd.bx = 0; cmd.command = CMD_ABSPOINTER_STATUS; vmware_send(&cmd); /* Read data (1) */ cmd.bx = 1; cmd.command = CMD_ABSPOINTER_DATA; vmware_send(&cmd); /* Enable absolute */ cmd.bx = ABSPOINTER_ABSOLUTE; cmd.command = CMD_ABSPOINTER_COMMAND; vmware_send(&cmd); } volatile int8_t vmware_mouse_byte = 0; static void vmware_mouse(uint8_t byte) { /* unused, but we need to read the fake mouse event bytes from the PS/2 device. */ vmware_mouse_byte = byte; /* Read status byte. */ vmware_cmd cmd; cmd.bx = 0; cmd.command = CMD_ABSPOINTER_STATUS; vmware_send(&cmd); if (cmd.ax == 0xffff0000) { /* Device error; turn it off and back on again. */ mouse_off(); mouse_absolute(); return; } int words = cmd.ax & 0xFFFF; if (!words || words % 4) { /* If we don't have data, or for some reason data isn't a multiple of 4... bail */ return; } /* Read 4 bytes of data */ cmd.bx = 4; /* how many */ cmd.command = CMD_ABSPOINTER_DATA; /* read */ vmware_send(&cmd); /* * I guess the flags tell you if this was relative or absolute, so if we * actually used the relative mode, we'd want to check that, but... */ //int flags = (cmd.ax & 0xFFFF0000) >> 16; int buttons = (cmd.ax & 0x0000FFFF); unsigned int x = 0; unsigned int y = 0; if (lfb_vid_memory && lfb_resolution_x && lfb_resolution_y) { /* * Just like the virtualbox stuff, this is based on a mapping * to the display resolution, independently scaled in * each dimension... */ x = ((unsigned int)cmd.bx * lfb_resolution_x) / 0xFFFF; y = ((unsigned int)cmd.cx * lfb_resolution_y) / 0xFFFF; } else { x = cmd.bx; y = cmd.cx; } mouse_device_packet_t packet; packet.magic = MOUSE_MAGIC; packet.x_difference = x; packet.y_difference = y; packet.buttons = 0; /* The particular bits for the buttons seem weird, but okay... */ if (buttons & 0x20) { packet.buttons |= LEFT_CLICK; } if (buttons & 0x10) { packet.buttons |= RIGHT_CLICK; } if (buttons & 0x08) { packet.buttons |= MIDDLE_CLICK; } /* dx = z = scroll amount */ if ((int8_t)cmd.dx > 0) { packet.buttons |= MOUSE_SCROLL_DOWN; } else if ((int8_t)cmd.dx < 0) { packet.buttons |= MOUSE_SCROLL_UP; } mouse_device_packet_t bitbucket; while (pipe_size(mouse_pipe) > (int)(DISCARD_POINT * sizeof(packet))) { read_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&bitbucket); } write_fs(mouse_pipe, 0, sizeof(packet), (uint8_t *)&packet); } static int detect_device(void) { vmware_cmd cmd; /* read version */ cmd.bx = ~VMWARE_MAGIC; cmd.command = CMD_GETVERSION; vmware_send(&cmd); if (cmd.bx != VMWARE_MAGIC || cmd.ax == 0xFFFFFFFF) { /* Not a vmware device... */ return 0; } /* Good to go! */ return 1; } static int open_msg_channel(uint32_t proto) { vmware_cmd cmd; cmd.cx = CMD_MESSAGE | 0x00000000; /* CMD_MESSAGE */ cmd.bx = proto; vmware_send(&cmd); if ((cmd.cx & 0x10000) == 0) { return -1; } return cmd.dx >> 16; } static void msg_close(int channel) { vmware_cmd cmd = {0}; cmd.cx = CMD_MESSAGE | 0x00060000; cmd.bx = 0; cmd.dx = channel << 16; vmware_send(&cmd); } static int open_rpci_channel(void) { return open_msg_channel(MESSAGE_RPCI); } static int tclo_channel = -1; static int open_tclo_channel(void) { if (tclo_channel != -1) { msg_close(tclo_channel); } tclo_channel = open_msg_channel(MESSAGE_TCLO); return tclo_channel; } static int msg_send(int channel, const char * msg, size_t size) { { vmware_cmd cmd = {0}; cmd.cx = CMD_MESSAGE | 0x00010000; /* CMD_MESSAGE size */ cmd.size = size; cmd.dx = channel << 16; vmware_send(&cmd); if (size == 0) return 0; if (((cmd.cx >> 16) & 0x0081) != 0x0081) { return -2; } } { vmware_cmd cmd = {0}; cmd.bx = 0x0010000; cmd.cx = size; cmd.dx = channel << 16; cmd.si = (uintptr_t)msg; vmware_send_hb(&cmd); if (!(cmd.bx & 0x0010000)) { return -3; } } return 0; } static int msg_recv(int channel, char * buf, size_t bufsize) { size_t size; { vmware_cmd cmd = {0}; cmd.cx = CMD_MESSAGE | 0x00030000; /* CMD_MESSAGE receive ize */ cmd.dx = channel << 16; vmware_send(&cmd); size = cmd.bx; if (size == 0) return 0; if (((cmd.cx >> 16) & 0x0083) != 0x0083) { return -2; } if (size > bufsize) return -1; } { vmware_cmd cmd = {0}; cmd.bx = 0x00010000; cmd.cx = size; cmd.dx = channel << 16; cmd.di = (uintptr_t)buf; vmware_get_hb(&cmd); if (!(cmd.bx & 0x00010000)) { return -3; } } { vmware_cmd cmd = {0}; cmd.cx = CMD_MESSAGE | 0x00050000; cmd.bx = 0x0001; cmd.dx = channel << 16; vmware_send(&cmd); } return size; } static int rpci_string(const char * request) { /* Open channel */ int channel = open_rpci_channel(); if (channel < 0) return channel; size_t size = strlen(request) + 1; msg_send(channel, request, size); char buf[16]; int recv_size = msg_recv(channel, buf, 16); msg_close(channel); if (recv_size < 0) return recv_size; return 0; } static int attempt_scale(void) { int i; int c = open_tclo_channel(); if (c < 0) { return 1; } char buf[256]; if ((i = msg_send(c, buf, 0)) < 0) { return 1; } int resend = 0; while (1) { i = msg_recv(c, buf, 256); if (i < 0) { return 1; } else if (i == 0) { if (resend) { if ((i = rpci_string("tools.capability.resolution_set 1")) < 0) { return 1; } if ((i = rpci_string("tools.capability.resolution_server toolbox 1")) < 0) { return 1; } if ((i = rpci_string("tools.capability.display_topology_set 1")) < 0) { return 1; } if ((i = rpci_string("tools.capability.color_depth_set 1")) < 0) { return 1; } if ((i = rpci_string("tools.capability.resolution_min 0 0")) < 0) { return 1; } if ((i = rpci_string("tools.capability.unity 1")) < 0) { return 1; } resend = 0; } else { unsigned long s, ss; relative_time(0, 10000, &s, &ss); sleep_until((process_t *)this_core->current_process, s, ss); switch_task(0); } if ((i = msg_send(c, buf, 0)) < 0) { return 1; } } else { buf[i] = '\0'; if (startswith(buf, "reset")) { if ((i = msg_send(c, "OK ATR toolbox", strlen("OK ATR toolbox"))) < 0) { return 1; } } else if (startswith(buf, "ping")) { if ((i = msg_send(c, "OK ", strlen("OK "))) < 0) { return 1; } } else if (startswith(buf, "Capabilities_Register")) { if ((i = msg_send(c, "OK ", strlen("OK "))) < 0) { return 1; } resend = 1; } else if (startswith(buf, "Resolution_Set")) { char * x = &buf[15]; char * y = strstr(x," "); if (!y) { return 1; } *y = '\0'; y++; int _x = atoi(x); int _y = atoi(y); if (lfb_resolution_x && _x && (_x != lfb_resolution_x || _y != lfb_resolution_y)) { lfb_set_resolution(_x, _y); } if ((i = msg_send(c, "OK ", strlen("OK "))) < 0) { return 1; } msg_close(c); return 0; } else { if ((i = msg_send(c, "ERROR Unknown command", strlen("ERROR Unknown command"))) < 0) { return 1; } } } } } static void vmware_resize(void * data) { while (1) { attempt_scale(); unsigned long s, ss; relative_time(1, 0, &s, &ss); sleep_until((process_t *)this_core->current_process, s, ss); switch_task(0); } } static int ioctl_mouse(fs_node_t * node, unsigned long request, void * argp) { switch (request) { case 1: /* Disable */ mouse_off(); ps2_mouse_alternate = NULL; return 0; case 2: /* Enable */ ps2_mouse_alternate = vmware_mouse; mouse_absolute(); return 0; case 3: return ps2_mouse_alternate == vmware_mouse; default: return -EINVAL; } } static int vmware_initialize(int argc, char * argv[]) { if (!detect_device()) return -ENODEV; mouse_pipe = make_pipe(sizeof(mouse_device_packet_t) * PACKETS_IN_PIPE); mouse_pipe->flags = FS_CHARDEVICE; vfs_mount("/dev/vmmouse", mouse_pipe, "vmware-mouse", ""); mouse_pipe->flags = FS_CHARDEVICE; mouse_pipe->ioctl = ioctl_mouse; /* * We have a hack in the PS/2 mouse driver that lets us * take over for the normal mouse driver and essential * intercept the interrputs when they are valid. */ ps2_mouse_alternate = vmware_mouse; mouse_absolute(); if (lfb_driver_name && !strcmp(lfb_driver_name, "vmware") && !args_present("novmwareresset")) { spawn_worker_thread(vmware_resize, "[vmware]", NULL); } return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "vmware", .init = vmware_initialize, .fini = fini, }; ================================================ FILE: modules/xhci.c ================================================ /** * @brief xHCI Host Controller Driver * @file modules/xhci.c * @package x86_64 * * @warning This is a stub driver. * * @copyright * This file is part of ToaruOS and is released under the terms * of the NCSA / University of Illinois License - see LICENSE.md * Copyright (C) 2021 K. Lange */ #include #include #include #include #include #include #include #include #include #include static void delay_yield(size_t subticks) { #ifdef __aarch64__ asm volatile ("dsb sy\nisb" ::: "memory"); #endif unsigned long s, ss; relative_time(0, subticks, &s, &ss); sleep_until((process_t *)this_core->current_process, s, ss); switch_task(0); #ifdef __aarch64__ asm volatile ("dmb sy\n" ::: "memory"); #endif } struct xhci_cap_regs { volatile uint32_t cap_caplen_version; volatile uint32_t cap_hcsparams1; volatile uint32_t cap_hcsparams2; volatile uint32_t cap_hcsparams3; volatile uint32_t cap_hccparams1; volatile uint32_t cap_dboff; volatile uint32_t cap_rtsoff; volatile uint32_t cap_hccparams2; } __attribute__((packed)); struct xhci_port_regs { volatile uint32_t port_status; volatile uint32_t port_pm_status; volatile uint32_t port_link_info; volatile uint32_t port_lpm_control; } __attribute__((packed)); struct xhci_op_regs { volatile uint32_t op_usbcmd; /* 0 */ volatile uint32_t op_usbsts; // 4 volatile uint32_t op_pagesize; // 8h volatile uint32_t op__pad1[2]; // ch 10h volatile uint32_t op_dnctrl; // 14h volatile uint32_t op_crcr[2]; // 18h 1ch volatile uint32_t op__pad2[4]; // 20h 24h 28h 2ch volatile uint32_t op_dcbaap[2]; // 30h 34h volatile uint32_t op_config; // 38h volatile uint8_t op_more_padding[964]; // 3ch-400h struct xhci_port_regs op_portregs[256]; } __attribute__((packed)); struct xhci_trb { uint32_t trb_thing_a; uint32_t trb_thing_b; uint32_t trb_status; uint32_t trb_control; } __attribute__((packed)); struct XHCIControllerData { uintptr_t mmio; uint32_t device; uint64_t pcie_offset; struct xhci_cap_regs * cregs; struct xhci_op_regs * oregs; process_t * thread; volatile struct xhci_trb * cr_trbs; volatile struct xhci_trb * er_trbs; spin_lock_t command_queue; uint32_t command_queue_cycle; int command_queue_enq; volatile uint32_t * doorbells; }; static uint64_t pci_addr_map(struct XHCIControllerData * controller, uint64_t addr) { return addr + controller->pcie_offset; } static uintptr_t pci_to_cpu(struct XHCIControllerData * controller, uint64_t addr) { return addr - controller->pcie_offset; } static uintptr_t allocate_page(uint64_t * phys_out) { uint64_t phys = mmu_allocate_a_frame() << 12; uintptr_t virt = (uintptr_t)mmu_map_mmio_region(phys, 4096); memset((void*)virt,0,4096); *phys_out = phys; return virt; } static int xhci_command(struct XHCIControllerData * controller, uint32_t p1, uint32_t p2, uint32_t status, uint32_t control) { spin_lock(controller->command_queue); control &= ~1; control |= controller->command_queue_cycle; controller->cr_trbs[controller->command_queue_enq].trb_thing_a = p1; controller->cr_trbs[controller->command_queue_enq].trb_thing_b = p2; controller->cr_trbs[controller->command_queue_enq].trb_status = status; controller->cr_trbs[controller->command_queue_enq].trb_control = control; controller->command_queue_enq++; if (controller->command_queue_enq == 63) { controller->cr_trbs[controller->command_queue_enq].trb_control ^= 1; if (controller->cr_trbs[controller->command_queue_enq].trb_control & (1 << 1)) { controller->command_queue_cycle ^= 1; } controller->command_queue_enq = 0; } /* ring doorbell */ controller->doorbells[0] = 0; spin_unlock(controller->command_queue); return 0; } static ssize_t xhci_write(fs_node_t * node, off_t offset, size_t size, uint8_t * buffer) { struct XHCIControllerData * controller = node->device; if (size != sizeof(struct xhci_trb)) return -EINVAL; struct xhci_trb * data = (void*)buffer; xhci_command(controller, data->trb_thing_a, data->trb_thing_b, data->trb_status, data->trb_control); return sizeof(struct xhci_trb); } static struct XHCIControllerData * _irq_owner = NULL; #include static int irq_handler(struct regs *r) { int irq = r->int_no - 32; if (_irq_owner) { /* Is it ours? */ uint32_t status = _irq_owner->oregs->op_usbsts; if (status & (1 << 3)) { _irq_owner->oregs->op_usbsts = (1 << 3); dprintf("xhci: irq\n"); uintptr_t rts = (uintptr_t)_irq_owner->cregs + _irq_owner->cregs->cap_rtsoff; volatile uint32_t * irs0_32 = (uint32_t*)(rts + 0x20); irs0_32[0] |= 1; make_process_ready(_irq_owner->thread); irq_ack(irq); return 1; } } return 0; } void xhci_thread(void * arg) { struct XHCIControllerData * controller = arg; controller->thread = (process_t*)this_core->current_process; spin_init(controller->command_queue); /* Begin generic XHCi */ dprintf("xhci: available slots: %d\n", controller->cregs->cap_hcsparams1 & 0xFF); dprintf("xhci: available ports: %d\n", controller->cregs->cap_hcsparams1 >> 24); dprintf("xhci: resetting controller\n"); dprintf("xhci: waiting for controller to stop...\n"); uint32_t cmd = controller->oregs->op_usbcmd; cmd &= ~(1); controller->oregs->op_usbcmd = cmd; while (!(controller->oregs->op_usbsts & (1 << 0))); dprintf("xhci: restarting controller...\n"); cmd = controller->oregs->op_usbcmd; cmd |= (1 << 1); controller->oregs->op_usbcmd = cmd; while ((controller->oregs->op_usbcmd & (1 << 1))); while ((controller->oregs->op_usbsts & (1 << 11))); dprintf("xhci: controller is ready: %#x\n", controller->oregs->op_usbsts); dprintf("xhci: slot config %#x -> %#x\n", controller->oregs->op_config, controller->cregs->cap_hcsparams1 & 0xFF); controller->oregs->op_config = controller->cregs->cap_hcsparams1 & 0xFF; /* TODO We may need to clear interrupts here by writing status back */ uint32_t sts = controller->oregs->op_usbsts; (void)sts; dprintf("xhci: context size is %d\n", (controller->cregs->cap_hccparams1 & (1 << 2)) ? 64 : 32); uintptr_t ext_off = (controller->cregs->cap_hccparams1 >> 16) << 2; volatile uint32_t * ext_caps = (void*)((uintptr_t)controller->cregs + ext_off); /** * Verify port configurations; * should be port 1 is usb 2.0 * port 2, 3, 4, 5 are 3.0 * port 1 has a hub with 4 ports? */ while (1) { uint32_t cap_val = *ext_caps; dprintf("xhci: ecap = %#x\n", cap_val); /* Bottom byte is type */ if ((cap_val & 0xFF) == 2) { uint8_t rev_minor = ext_caps[0] >> 16; uint8_t rev_major = ext_caps[0] >> 24; //uint32_t name_str = ext_caps[1]; uint8_t port_offset = ext_caps[2]; uint8_t port_count = ext_caps[2] >> 8; uint8_t psic = ext_caps[2] >> 28; dprintf("xhci: protocol %d.%d %d port%s starting from port %d has %d speed%s\n", rev_major, rev_minor, port_count, &"s"[port_count==1], port_offset, psic, &"s"[psic==1]); } if (cap_val == 0xFFFFffff) break; if ((cap_val & 0xFF00) == 0) break; ext_caps = (void*)((uintptr_t)ext_caps + ((cap_val & 0xFF00) >> 6)); } /* Device Context Base Address Array */ uint64_t dcbaap; uint64_t * baseCtx = (void*)allocate_page(&dcbaap); dprintf("xhci: DCBAAP at %#zx (phys=%#zx)\n", (uintptr_t)baseCtx, dcbaap); controller->oregs->op_dcbaap[0] = pci_addr_map(controller, dcbaap); controller->oregs->op_dcbaap[1] = pci_addr_map(controller, dcbaap) >> 32; /* Enable slots */ uint32_t cfg = controller->oregs->op_config; cfg &= ~0xFF; cfg |= 32; dprintf("xhci: set cfg = %#x\n", cfg); controller->oregs->op_config = cfg; /* trbs for event ring */ uint64_t er_trbs_phys; void * er_trbs_virt = (void*)allocate_page(&er_trbs_phys); dprintf("xhci: er trbs = %#zx (phys=%#zx)\n", (uintptr_t)er_trbs_virt, er_trbs_phys); /* erst */ uint64_t er_erst_phys; void * er_erst_virt = (void*)allocate_page(&er_erst_phys); dprintf("xhci: er erst = %#zx (phys=%#zx)\n", (uintptr_t)er_erst_virt, er_erst_phys); ((volatile uint64_t*)er_erst_virt)[0] = pci_addr_map(controller, er_trbs_phys); ((volatile uint64_t*)er_erst_virt)[1] = 64; dprintf("xhci: rtsoff = %#x\n", controller->cregs->cap_rtsoff); uintptr_t rts = (uintptr_t)controller->cregs + controller->cregs->cap_rtsoff; /* Interrupter points to event ring */ volatile uint32_t * irs0_32 = (uint32_t*)(rts + 0x20); irs0_32[2] = 1; /* Size = 1 */ irs0_32[6] = pci_addr_map(controller, er_trbs_phys) | (1 << 3); irs0_32[7] = (pci_addr_map(controller, er_trbs_phys) | (1 << 3)) >> 32; irs0_32[1] = 500; /* IMOD */ irs0_32[0] = 2; /* enable interrupts */ irs0_32[4] = pci_addr_map(controller, er_erst_phys); irs0_32[5] = pci_addr_map(controller, er_erst_phys) >> 32; /* trbs for control ring */ uint64_t cr_trbs_phys; void * cr_trbs_virt = (void*)allocate_page(&cr_trbs_phys); ((volatile uint64_t*)cr_trbs_virt)[63*2] = pci_addr_map(controller, cr_trbs_phys); ((volatile uint64_t*)cr_trbs_virt)[63*2+1] = ((0x2UL | (6UL << 10)) << 32); controller->oregs->op_crcr[0] = (pci_addr_map(controller, cr_trbs_phys) | 1); controller->oregs->op_crcr[1] = (pci_addr_map(controller, cr_trbs_phys) | 1) >> 32; /* Scratchpad buffers, if needed */ uint32_t hcs2 = controller->cregs->cap_hcsparams2; uint32_t sb_hi = (hcs2 >> 21) & 0x1f; uint32_t sb_lo = (hcs2 >> 27) & 0x1f; uint32_t sb_max = (sb_hi << 5) | sb_lo; /* should be 31 */ if (sb_max) { dprintf("num scratchpad buffers = %u\n", sb_max); /* Allocate buffer for array */ uint64_t scratch_phys; uint64_t *scratch_virt = (uint64_t*)allocate_page(&scratch_phys); dprintf("xhci: scratch at %#zx (phys=%#zx)\n", (uintptr_t)scratch_virt, scratch_phys); /* Our DMA mapping should be 1:1, so, uh, yolo */ for (unsigned int i = 0; i < sb_max; ++i) { uint64_t sb_phys; allocate_page(&sb_phys); scratch_virt[i] = pci_addr_map(controller, sb_phys); } baseCtx[0] = pci_addr_map(controller, scratch_phys); dprintf("xhci: assigned scratchpad buffer array\n"); } /* TODO This irq API sucks */ int irq_number = pci_get_interrupt(controller->device); irq_install_handler(irq_number, irq_handler, "xhci"); _irq_owner = controller; dprintf("xhci: Starting command ring...\n"); { uint32_t cmd = controller->oregs->op_usbcmd; dprintf("cmd before = %#x\n", cmd); cmd |= (1 << 0) | (1 << 2); controller->oregs->op_usbcmd = cmd; } dprintf("xhci: status = %#x\n", controller->oregs->op_usbsts); delay_yield(50000); dprintf("xhci: status = %#x\n", controller->oregs->op_usbsts); if (controller->oregs->op_usbsts & (1 << 2)) goto _bail; dprintf("xhci: doorbells at %#x\n", controller->cregs->cap_dboff); controller->doorbells = (void*)((uintptr_t)controller->cregs + controller->cregs->cap_dboff); /* Just want to enable the hub for now, see if we can id it */ controller->cr_trbs = cr_trbs_virt; controller->er_trbs = er_trbs_virt; controller->command_queue_cycle = 1; controller->command_queue_enq = 0; dprintf("xhci: status before ring = %#x\n", controller->oregs->op_usbsts); xhci_command(controller, 0, 0, 0, (23 << 10)); char devName[20] = "/dev/xhciN"; snprintf(devName, 19, "/dev/xhci%d", 0); fs_node_t * fnode = calloc(sizeof(fs_node_t), 1); snprintf(fnode->name, 100, "xhci%d", 0); fnode->flags = FS_BLOCKDEVICE; fnode->mask = 0660; /* Only accessible to root user/group */ fnode->read = NULL; fnode->write = xhci_write; fnode->device = controller; vfs_mount(devName, fnode, "xhci", fnode->name); int event_deq = 0; uint32_t event_cycle_state = 1; while (1) { while ((controller->er_trbs[event_deq].trb_control & 1) != event_cycle_state) { switch_task(0); } uint32_t thing_a = controller->er_trbs[event_deq].trb_thing_a; uint32_t thing_b = controller->er_trbs[event_deq].trb_thing_a; uint32_t status = controller->er_trbs[event_deq].trb_status; uint32_t control = controller->er_trbs[event_deq].trb_control; dprintf("xhci: event %d [%#x %#x %#x %#x]\n", event_deq, thing_a, thing_b, status, control); event_deq++; if (event_deq == 64) { event_deq = 0; event_cycle_state = !event_cycle_state; } /* Write new event dequeue pointer */ uint64_t new_deq_phys = pci_addr_map(controller, er_trbs_phys + sizeof(struct xhci_trb) * event_deq) | (1 << 3); irs0_32[6] = new_deq_phys; irs0_32[7] = new_deq_phys >> 32; } _bail: task_exit(1); __builtin_unreachable(); } static void find_xhci(uint32_t device, uint16_t v, uint16_t d, void * extra) { if (pci_find_type(device) != 0x0C03) return; if (pci_read_field(device, PCI_PROG_IF, 1) != 0x30) return; fs_node_t * stderr = extra; uint16_t command_reg = pci_read_field(device, PCI_COMMAND, 2); command_reg |= (1 << 2); command_reg |= (1 << 1); pci_write_field(device, PCI_COMMAND, 2, command_reg); /* The mmio address is 64 bits and combines BAR0 and BAR1... */ uint64_t addr_low = pci_read_field(device, PCI_BAR0, 4) & 0xFFFFFFF0; uint64_t addr_high = pci_read_field(device, PCI_BAR1, 4) & 0xFFFFFFFF; /* I think this is right? */ uint64_t mmio_addr = (addr_high << 32) | addr_low; if (mmio_addr == 0) { /* Need to map... */ fprintf(stderr, "xhci: Device is unmapped. TODO: Check if this is behind a PCI bridge...\n"); return; #if 0 mmio_addr = mmu_allocate_n_frames(2) << 12; pci_write_field(device, PCI_BAR0, 4, (mmio_addr & 0xFFFFFFF0) | (1 << 2)); pci_write_field(device, PCI_BAR1, 4, (mmio_addr >> 32)); #endif } fprintf(stderr, "xhci: controller found\n"); struct XHCIControllerData * controller = calloc(sizeof(struct XHCIControllerData), 1); controller->device = device; /* Map mmio space... */ uintptr_t xhci_regs = (uintptr_t)mmu_map_mmio_region(mmio_addr, 0x1000 * 4); /* I don't know. */ controller->mmio = mmio_addr; controller->cregs = (struct xhci_cap_regs*)xhci_regs; controller->oregs = (struct xhci_op_regs*)(xhci_regs + (controller->cregs->cap_caplen_version & 0xFF)); controller->pcie_offset = 0; spawn_worker_thread(xhci_thread, "[xhci]", controller); } static int init(int argc, char * argv[]) { fs_node_t * node = FD_ENTRY(1); /* Get the stdout for the process that loaded the module */ pci_scan(find_xhci, -1, node); return 0; } static int fini(void) { return 0; } struct Module metadata = { .name = "xhci", .init = init, .fini = fini, }; ================================================ FILE: util/__init__.krk ================================================ import fileio def to_int(little: bool, data: bytes): let out = 0 if little: data = reversed(data) for x in data: out *= 0x100 out += x return out def to_bytes(little: bool, val: int, length: int): let out = [0] * length let i = 0 while val and i < length: out[i] = val & 0xFF val >>= 8 i++ if not little: out = reversed(out) return bytes(out) def read_struct(fmt: str, data: bytes, offset: int): if not fmt or not isinstance(fmt,str): raise ValueError # First, read the endianness let littleEndian = True if fmt[0] in '@<>#!': if fmt[0] == '>': littleEndian = False else if fmt[0] == '!': littleEndian = False fmt = fmt[1:] # Then read length let length = None if fmt[0] in '0123456789': length = int(fmt[0]) fmt = fmt[1:] while fmt[0] in '012345679': length *= 10 length += int(fmt[0]) fmt = fmt[1:] # Then read type if fmt[0] == 'B': return int(data[offset]), offset + 1 else if fmt[0] == 'b': let out = int(data[offset]) if out > 0x7F: out = -out return out, offset + 1 else if fmt[0] == 's': return bytes([data[x] for x in range(offset,offset+length)]), offset + length else if fmt[0] == 'I': return to_int(littleEndian, bytes([data[x] for x in range(offset,offset+4)])), offset + 4 else if fmt[0] == 'H': return to_int(littleEndian, bytes([data[x] for x in range(offset,offset+2)])), offset + 2 raise ValueError("Huh") def pack_into(fmt: str, data: bytes, offset: int, val: any): if not fmt or not isinstance(fmt,str): raise ValueError # First, read the endianness let littleEndian = True if fmt[0] in '@<>#!': if fmt[0] == '>': littleEndian = False else if fmt[0] == '!': littleEndian = False fmt = fmt[1:] # Then read length let length = None if fmt[0] in '0123456789': length = int(fmt[0]) fmt = fmt[1:] while fmt[0] in '012345679': length *= 10 length += int(fmt[0]) fmt = fmt[1:] # Then read type if fmt[0] == 'B': data[offset] = val return offset + 1 else if fmt[0] == 'b': data[offset] = val return offset + 1 else if fmt[0] == 's': for x in range(length): data[offset+x] = val[x] return offset + length else if fmt[0] == 'I': for x in to_bytes(littleEndian, val, 4): data[offset] = x offset++ return offset else if fmt[0] == 'H': for x in to_bytes(littleEndian, val, 2): data[offset] = x offset++ return offset raise ValueError("Huh") class ISO(object): def __init__(self, path): let data with fileio.open(path, 'rb') as f: self.data = bytearray(f.read()) self.sector_size = 2048 let o = 0x10 * self.sector_size let _unused self.type, o = read_struct('B',self.data,o) self.id, o = read_struct('5s',self.data,o) self.version, o = read_struct('B',self.data,o) _unused, o = read_struct('B',self.data,o) self.system_id, o = read_struct('32s',self.data,o) self.volume_id, o = read_struct('32s',self.data,o) _unused, o = read_struct('8s',self.data,o) self.volume_space_lsb, o = read_struct('I',self.data,o) _unused, o = read_struct('32s',self.data,o) self.volume_set_lsb, o = read_struct('H',self.data,o) self.volume_seq_lsb, o = read_struct('H',self.data,o) self.logical_block_size_lsb, o = read_struct('H',self.data,o) self.path_table_size_lsb, o = read_struct('I',self.data,o) self.path_table_lsb, o = read_struct('I',self.data,o) self.optional_path_table_msb, o = read_struct('>I',self.data,o) let _offset = o self.root_dir_entry, o = read_struct('34s',self.data,o) self.root = ISOFile(self,_offset) self._cache = {} def get_file(self, path): if path == '/': return self.root else: if path in self._cache: return self._cache[path] let units = path.split('/') units = units[1:] # remove root let me = self.root for i in units: let next_file = me.find(i) if not next_file: me = None break else: me = next_file self._cache[path] = me return me class ISOFile(object): def __init__(self, iso, offset): self.iso = iso self.offset = offset let o = offset self.length, o = read_struct('B', self.iso.data, o) if not self.length: return self.ext_length, o = read_struct('B', self.iso.data, o) self.extent_start_lsb, o = read_struct('I',self.iso.data, o) self.extent_length_lsb, o = read_struct('I',self.iso.data, o) self.date_data, o = read_struct('7s', self.iso.data, o) self.flags, o = read_struct('b', self.iso.data, o) self.interleave_units, o = read_struct('b', self.iso.data, o) self.interleave_gap, o = read_struct('b', self.iso.data, o) self.volume_seq_lsb, o = read_struct('H',self.iso.data, o) self.name_len, o = read_struct('b', self.iso.data, o) self.name, o = read_struct('{}s'.format(self.name_len), self.iso.data, o) self.name = self.name.decode() def write_extents(self): pack_into('I', self.iso.data, self.offset + 6, self.extent_start_lsb) pack_into('I', self.iso.data, self.offset + 14, self.extent_length_lsb) def readable_name(self): if not ';' in self.name: return self.name.lower() else: let tmp, _ tmp, _ = self.name.split(';') return tmp.lower() def list(self): 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] let offset = 0 while 1: let f = ISOFile(self.iso, self.extent_start_lsb * self.iso.sector_size + offset) yield f offset += f.length if not f.length: break def find(self, name): 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] let offset = 0 if '.' in name and len(name.split('.')[0]) > 8: let a, b a, b = name.split('.') name = a[:8] + '.' + b if '-' in name: name = name.replace('-','_') while 1: let f = ISOFile(self.iso, self.extent_start_lsb * self.iso.sector_size + offset) if not f.length: if offset < self.extent_length_lsb: offset += 1 continue else: break if ';' in f.name: let tmp, _ tmp, _ = f.name.split(';') if tmp.endswith('.'): tmp = tmp[:-1] if tmp.lower() == name.lower(): return f elif f.name.lower() == name.lower(): return f offset += f.length return None class FAT(object): def __init__(self, iso, offset): self.iso = iso self.offset = offset let _ self.bytespersector, _ = read_struct('H', self.iso.data, offset + 11) self.sectorspercluster, _ = read_struct('B', self.iso.data, offset + 13) self.reservedsectors, _ = read_struct('H', self.iso.data, offset + 14) self.numberoffats, _ = read_struct('B', self.iso.data, offset + 16) self.numberofdirs, _ = read_struct('H', self.iso.data, offset + 17) self.fatsize, _ = read_struct('H', self.iso.data, offset + 22) self.root_dir_sectors = (self.numberofdirs * 32 + (self.bytespersector - 1)) // self.bytespersector self.first_data_sector = self.reservedsectors + (self.numberoffats * self.fatsize) + self.root_dir_sectors self.root_sector= self.first_data_sector - self.root_dir_sectors self.root = FATDirectory(self, self.offset + self.root_sector * self.bytespersector) def get_offset(self, cluster): return self.offset + ((cluster - 2) * self.sectorspercluster + self.first_data_sector) * self.bytespersector def get_file(self, path): let units = path.split('/') units = units[1:] let me = self.root let out = None for i in units: for fatfile in me.list(): if fatfile.readable_name() == i: me = fatfile.to_dir() out = fatfile break return out class FATDirectory(object): def __init__(self, fat, offset): self.fat = fat self.offset = offset def list(self): let o = self.offset while 1: let out = FATFile(self.fat, o) if out.name != '\0\0\0\0\0\0\0\0': yield out else: break o += out.size class FATFile(object): def __init__(self, fat, offset): self.fat = fat self.offset = offset self.magic_long = None self.size = 0 self.long_name = '' let o = self.offset self.actual_offset = o let _ self.attrib, _ = read_struct('B',self.fat.iso.data,o+11) while (self.attrib & 0x0F) == 0x0F: # Long file name entry let tmp = read_struct('10s',self.fat.iso.data,o+1)[0] tmp += read_struct('12s',self.fat.iso.data,o+14)[0] tmp += read_struct('4s',self.fat.iso.data,o+28)[0] let s = [] for i = 0; i < len(tmp); i += 2: if tmp[x] != '\xFF': s.append(chr(tmp[x])) tmp = "".join(s).strip('\x00') self.long_name = tmp + self.long_name self.size += 32 o = self.offset + self.size self.actual_offset = o self.attrib, _ = read_struct('B',self.fat.iso.data,o+11) o = self.offset + self.size self.name, o = read_struct('8s',self.fat.iso.data,o) self.ext, o = read_struct('3s',self.fat.iso.data,o) self.attrib, o = read_struct('B',self.fat.iso.data,o) self.userattrib, o = read_struct('B',self.fat.iso.data,o) self.undelete, o = read_struct('b',self.fat.iso.data,o) self.createtime, o = read_struct('H',self.fat.iso.data,o) self.createdate, o = read_struct('H',self.fat.iso.data,o) self.accessdate, o = read_struct('H',self.fat.iso.data,o) self.clusterhi, o = read_struct('H',self.fat.iso.data,o) self.modifiedti, o = read_struct('H',self.fat.iso.data,o) self.modifiedda, o = read_struct('H',self.fat.iso.data,o) self.clusterlow, o = read_struct('H',self.fat.iso.data,o) self.filesize, o = read_struct('I',self.fat.iso.data,o) self.name = self.name.decode() self.ext = self.ext.decode() self.size += 32 self.cluster = (self.clusterhi << 16) + self.clusterlow def is_dir(self): return bool(self.attrib & 0x10) def is_long(self): return bool((self.attrib & 0x0F) == 0x0F) def to_dir(self): return FATDirectory(self.fat, self.fat.get_offset(self.cluster)) def get_offset(self): return self.fat.get_offset(self.cluster) def readable_name(self): if self.long_name: return self.long_name if self.ext.strip(): return (self.name.strip() + '.' + self.ext.strip()).lower() else: return self.name.strip().lower() ================================================ FILE: util/activate.sh ================================================ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" export PATH="$DIR/local/bin:$PATH" export TOOLCHAIN="$DIR" echo "$PATH" ================================================ FILE: util/arch.sh ================================================ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ -e "$DIR/../.arch" ]; then cat "$DIR/../.arch" exit 0 fi echo "x86_64" ================================================ FILE: util/auto-dep.krk ================================================ #!/bin/kuroko import os, kuroko, fileio let cflags = "-O2 -g -I. -Iapps -fplan9-extensions -Wall -Wextra -Wno-unused-parameter" def basename(path: str) -> str: return path.strip('/').split('/')[-1] class Classifier: dependency_hints = { # Toaru Standard Library '': (None, '-ltoaru_kbd', []), '': (None, '-ltoaru_list', []), '': (None, '-ltoaru_hashmap', ['']), '': (None, '-ltoaru_tree', ['']), '': (None, '-ltoaru_pex', []), '': (None, '-ltoaru_auth', []), '': (None, '-ltoaru_graphics', []), '': (None, '-ltoaru_inflate', []), '': (None, '-ltoaru_drawstring', ['']), '': (None, '-ltoaru_jpeg', ['']), '': (None, '-ltoaru_png', ['','']), '': (None, '-ltoaru_rline', ['']), '': (None, '-ltoaru_confreader', ['']), '': (None, '-ltoaru_markup', ['']), '': (None, '-ltoaru_json', ['']), '': (None, '-ltoaru_yutani', ['', '', '', '', '']), '': (None, '-ltoaru_decorations', ['', '', '', '']), '': (None, '-ltoaru_termemu', ['']), '': (None, '-ltoaru_icon_cache', ['', '']), '': (None, '-ltoaru_menu', ['', '', '', '']), '': (None, '-ltoaru_button', ['','', '']), '': (None, '-ltoaru_text', ['', '']), '': (None, '-ltoaru_markup_text', ['', '', '']), # Kuroko '': ('../../../kuroko/src', '-lkuroko', []), } def __init__(self, filename: str): self.export_dynamic_hint = False self.filename = filename self.includes, self.libs = self._depends() def _calculate(self, depends, new): """Calculate all dependencies for the given set of new elements.""" for k in new: if not k in depends: depends.append(k) let other = self.dependency_hints[k][2] depends = self._calculate(depends, other) return depends def _sort(self, depends: list[str]) -> list[str]: """Sort the list of dependencies so that elements appearing first depend on elements following.""" let satisfied = [] let a = depends[:] while set(satisfied) != set(depends): let b = [] for k in a: if all(x in satisfied for x in self.dependency_hints[k][2]): satisfied.append(k) else: b.append(k) a = b[:] return reversed(satisfied) def _depends(self) -> (list[str],list[str]): """Calculate include and library dependencies.""" let lines = [] let depends = [] with fileio.open(self.filename,'r') as f: lines = f.readlines() for l in lines: if l.startswith('#include'): depends.extend(k for k in list(self.dependency_hints.keys()) if l.startswith('#include ' + k)) elif l.startswith('/* auto-dep: export-dynamic */'): self.export_dynamic_hint = True depends = self._calculate([], depends) depends = self._sort(depends) let includes = [] let libraries = [] for k in depends: let dep = self.dependency_hints[k] if dep[0]: includes.append('-I' + 'base/usr/include/' + dep[0]) if dep[1]: libraries.append(dep[1]) return includes, libraries def todep(name: str) -> (bool, str): """Convert a library name to an archive path or object file name.""" if name.startswith("-l"): name = name.replace("-l","",1) if name.startswith('toaru'): return (True, "{}/lib{}.so".format('base/lib', name)) elif name.startswith('kuroko'): return (True, "{}/lib{}.so".format('base/lib', name)) else: return (True, "{}/lib{}.so".format('base/usr/lib', name)) else: return (False, name) def toheader(name: str) -> str: if name.startswith('-ltoaru_'): return name.replace('-ltoaru_','base/usr/include/toaru/') + '.h' else: return '' if __name__ == "__main__": if len(kuroko.argv) < 3: print("usage: util/auto-dep.krk command filename") return 1 let command = kuroko.argv[1] let filename = kuroko.argv[2] let c = Classifier(filename) if command == "--cflags": print(" ".join([x for x in c.includes])) elif command == "--libs": print(" ".join([x for x in c.libs])) elif command == "--deps": let results = [todep(x) for x in c.libs] let normal = [x[1] for x in results if not x[0]] let order_only = [x[1] for x in results if x[0]] print(" ".join(normal) + " | " + " ".join(order_only)) elif command == "--build": os.system("gcc {cflags} {extra} {includes} -o {app} {source} {libraries}".format( cflags=cflags, app=basename(filename).replace('.c++','').replace(".c",""), source=filename, headers=" ".join([toheader(x) for x in c.libs]), libraries=" ".join([x for x in c.libs]), includes=" ".join([x for x in c.includes if x is not None]), extra="-rdynamic" if c.export_dynamic_hint else "", )) elif command == "--buildlib": let libname = basename(filename).replace('.c++','').replace(".c","") let _libs = [x for x in c.libs if not x.startswith('-ltoaru_') or x.replace("-ltoaru_","") != libname] os.system("gcc {cflags} {includes} -shared -fPIC -olibtoaru_{lib}.so {source} {libraries}".format( cflags=cflags, lib=libname, source=filename, headers=" ".join([toheader(x) for x in c.libs]), libraryfiles=" ".join([todep(x)[1] for x in _libs]), libraries=" ".join([x for x in _libs]), includes=" ".join([x for x in c.includes if x is not None]) )) elif command == "--make": print("base/bin/{app}: {source} {headers} util/auto-dep.krk | {libraryfiles} $(LC)\n\t{comp} {extra} {includes} -o $@ $< {libraries}".format( app=basename(filename).replace('.c++','').replace(".c",""), source=filename, headers=" ".join([toheader(x) for x in c.libs]), libraryfiles=" ".join([todep(x)[1] for x in c.libs]), libraries=" ".join([x for x in c.libs]), includes=" ".join([x for x in c.includes if x is not None]), comp="$(CC) $(CFLAGS)" if '.c++' not in filename else "$(CXX) $(CXXFLAGS)", extra="-rdynamic" if c.export_dynamic_hint else "" )) elif command == "--makelib": let libname = basename(filename).replace('.c++','').replace(".c","") let _libs = [x for x in c.libs if not x.startswith('-ltoaru_') or x.replace("-ltoaru_","") != libname] print("base/lib/libtoaru_{lib}.so: {source} {headers} util/auto-dep.krk | {libraryfiles} $(LC)\n\t{comp} {includes} -shared -fPIC -o $@ $< {libraries}".format( lib=libname, source=filename, headers=" ".join([toheader(x) for x in c.libs]), libraryfiles=" ".join([todep(x)[1] for x in _libs]), libraries=" ".join([x for x in _libs]), includes=" ".join([x for x in c.includes if x is not None]), comp="$(CC) $(CFLAGS)" if '.c++' not in filename else "$(CXX) $(CXXFLAGS)", )) elif command == "--makekurokomod": let libname = basename(filename).replace('.c++','').replace(".c","").replace("module_","") let _libs = [x for x in c.libs if not x.startswith('-ltoaru_') or x.replace("-ltoaru_","") != libname] print("base/lib/kuroko/{lib}.so: {source} {headers} util/auto-dep.krk | {libraryfiles} $(LC)\n\t$(CC) $(CFLAGS) {includes} -shared -fPIC -o $@ $< {libraries}".format( lib=libname, source=filename, headers=" ".join([toheader(x) for x in c.libs]), libraryfiles=" ".join([todep(x)[1] for x in _libs]), libraries=" ".join([x for x in _libs]), includes=" ".join([x for x in c.includes if x is not None]) )) ================================================ FILE: util/bochsrc.txt ================================================ # configuration file generated by Bochs plugin_ctrl: unmapped=true, biosdev=true, speaker=true, extfpuirq=true, parallel=true, serial=true, gameport=true, iodebug=true config_interface: textconfig display_library: x memory: host=512, guest=512 romimage: file="/usr/share/bochs/BIOS-bochs-latest", address=0x00000000, options=none vgaromimage: file="/usr/share/bochs/VGABIOS-lgpl-latest" boot: cdrom floppy_bootsig_check: disabled=0 # no floppya # no floppyb ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 ata0-master: type=cdrom, path="image.iso", status=inserted, model="Generic 1234", biosdetect=auto ata0-slave: type=none ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15 ata1-master: type=none ata1-slave: type=none ata2: enabled=false ata3: enabled=false optromimage1: file=none optromimage2: file=none optromimage3: file=none optromimage4: file=none optramimage1: file=none optramimage2: file=none optramimage3: file=none optramimage4: file=none pci: enabled=1, chipset=i440fx, slot1=pcivga vga: extension=vbe, update_freq=5, realtime=1 cpu: 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 cpuid: level=6, stepping=3, model=3, family=6, vendor_string="GenuineIntel", brand_string=" Intel(R) Pentium(R) 4 CPU " cpuid: mmx=true, apic=xapic, simd=sse2, sse4a=false, misaligned_sse=false, sep=true cpuid: movbe=false, adx=false, aes=false, sha=false, xsave=false, xsaveopt=false, avx_f16c=false cpuid: avx_fma=false, bmi=0, xop=false, fma4=false, tbm=false, x86_64=true, 1g_pages=false cpuid: pcid=false, fsgsbase=false, smep=false, smap=false, mwait=true, vmx=1, svm=false print_timestamps: enabled=0 debugger_log: - magic_break: enabled=0 port_e9_hack: enabled=0 private_colormap: enabled=0 clock: sync=realtime, time0=utc, rtc_sync=1 # no cmosimage log: - logprefix: %t%e%d debug: action=ignore info: action=report error: action=report panic: action=ask keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none mouse: type=ps2, enabled=false, toggle=ctrl+mbutton sound: waveoutdrv=alsa, waveout=none, waveindrv=alsa, wavein=none, midioutdrv=alsa, midiout=none speaker: enabled=true, mode=sound parport1: enabled=true, file=none parport2: enabled=false com1: enabled=true, mode=null com2: enabled=false com3: enabled=false com4: enabled=false ================================================ FILE: util/build-in-docker-aarch64.sh ================================================ #!/bin/bash # Give other users access to /root # (We probably should have just built the build tools somewhere else...) chmod o+x /root chmod -R o+rw /root/gcc_local/bin # Who owns this directory? NEWUID=`stat -c '%u' .` if [[ "$NEWUID" == "0" ]]; then echo "Are you running this on Docker for Mac? Owner UID is 0, going to use 501 instead." NEWUID=501 fi # Create a fake user with this name useradd -u $NEWUID local # Map the build tools ln -s /root/gcc_local util/local # Run make as local runuser -u local -- sh -c 'make ARCH=aarch64 util/local/bin/kuroko && make ARCH=aarch64 base/lib/libc.so && make ARCH=aarch64 -j4' || exit 1 # Remove the build tools rm util/local ================================================ FILE: util/build-in-docker.sh ================================================ #!/bin/bash # Give other users access to /root # (We probably should have just built the build tools somewhere else...) chmod o+x /root chmod -R o+rw /root/gcc_local/bin # Who owns this directory? NEWUID=`stat -c '%u' .` if [[ "$NEWUID" == "0" ]]; then echo "Are you running this on Docker for Mac? Owner UID is 0, going to use 501 instead." NEWUID=501 fi # Create a fake user with this name useradd -u $NEWUID local # Map the build tools ln -s /root/gcc_local util/local # Run make as local runuser -u local -- sh -c 'make util/local/bin/kuroko && make base/lib/libc.so && make -j4' || exit 1 # Remove the build tools rm util/local ================================================ FILE: util/build-toolchain.sh ================================================ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" ARCH=x86_64 TARGET=x86_64-pc-toaru PREFIX="$DIR/local" SYSROOT="$DIR/../base" # --disable-multilib cd $DIR mkdir -p $PREFIX/bin mkdir -p $DIR/build/binutils cd $DIR/build/binutils ../../binutils-gdb/configure --target=$TARGET --prefix="$PREFIX" --with-sysroot="$SYSROOT" --disable-werror --enable-shared make -j8 make install mkdir -p $DIR/build/gcc cd $DIR/build/gcc ../../gcc/configure --target=$TARGET --prefix="$PREFIX" --with-sysroot="$SYSROOT" --enable-languages=c,c++ --enable-shared make -j8 all-gcc make install-gcc make -j8 $TARGET/libgcc/{libgcc.a,crtbegin.o,crtend.o,crtbeginS.o,crtendS.o} cp $TARGET/libgcc/{libgcc.a,crtbegin.o,crtend.o,crtbeginS.o,crtendS.o} ../../local/lib/gcc/$TARGET/10.3.0/ cd $DIR/../ make ARCH=$ARCH base/lib/libc.so cd $DIR/build/gcc make -j8 all-target-libgcc make install-target-libgcc cd $DIR/../ rm base/lib/libc.so make ARCH=$ARCH base/lib/libc.so make ARCH=$ARCH base/lib/libm.so #cd $DIR/build/gcc #make -j8 all-target-libstdc++-v3 #make install-target-libstdc++-v3 ================================================ FILE: util/createramdisk.py ================================================ #!/usr/bin/python3 """ Generates, from this source repository, a "tarramdisk" - a ustar archive suitable for booting ToaruOS. """ import os import tarfile users = { 'root': 0, 'local': 1000, 'guest': 1001, } restricted_files = { 'etc/master.passwd': 0o600, 'etc/sudoers': 0o600, 'tmp': 0o777, 'var': 0o755, 'bin/sudo': 0o4555, 'bin/gsudo': 0o4555, } def file_filter(tarinfo): # Root owns files by default. tarinfo.uid = 0 tarinfo.gid = 0 if tarinfo.name.startswith('home/'): # Home directory contents are owned by their users. user = tarinfo.name.split('/')[1] tarinfo.uid = users.get(user,0) tarinfo.gid = tarinfo.uid elif tarinfo.name in restricted_files: tarinfo.mode = restricted_files[tarinfo.name] if tarinfo.name.startswith('usr/include/kuroko') and tarinfo.type == tarfile.SYMTYPE: return None if tarinfo.name.startswith('src'): # Let local own the files here tarinfo.uid = users.get('local') tarinfo.gid = tarinfo.uid # Skip object files if tarinfo.name.endswith('.so') or tarinfo.name.endswith('.o') or tarinfo.name.endswith('.sys'): return None return tarinfo def symlink(file,target): ti = tarfile.TarInfo(file) ti.type = tarfile.SYMTYPE ti.linkname = target return ti with tarfile.open('ramdisk.igz','w:gz') as ramdisk: ramdisk.add('base',arcname='/',filter=file_filter) ramdisk.add('.',arcname='/src',filter=file_filter,recursive=False) # Add a src directory ramdisk.add('apps',arcname='/src/apps',filter=file_filter) ramdisk.add('kernel',arcname='/src/kernel',filter=file_filter) ramdisk.add('linker',arcname='/src/linker',filter=file_filter) ramdisk.add('lib',arcname='/src/lib',filter=file_filter) ramdisk.add('libc',arcname='/src/libc',filter=file_filter) ramdisk.add('boot',arcname='/src/boot',filter=file_filter) ramdisk.add('modules',arcname='/src/modules',filter=file_filter) if os.path.exists('tags'): ramdisk.add('tags',arcname='/src/tags',filter=file_filter) ramdisk.add('util/auto-dep.krk',arcname='/bin/auto-dep.krk',filter=file_filter) ramdisk.add('kuroko/src/kuroko',arcname='/usr/include/kuroko',filter=file_filter) ramdisk.addfile(symlink('bin/sh','esh')) ramdisk.addfile(symlink('bin/mandelbrot','julia')) ramdisk.addfile(symlink('bin/fgrep','grep')) ================================================ FILE: util/docker/Dockerfile ================================================ FROM ubuntu:20.04 ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && 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 \ && rm -rf /var/lib/apt/lists/* /var/cache/apt/apt-file/* ================================================ FILE: util/docker/README.md ================================================ 1. Build the base image from the Dockerfile. ```bash docker build . ``` 2. Run the docker image with `-it bash`. 3. Clone the repository over `https`: ```bash cd git clone --recurse-submodules https://github.com/toaruos/misaka cd misaka util/build-toolchain.sh cd util/build/binutils while [[ -e confdir3/confdir3 ]]; do mv confdir3/confdir3 confdir3a; rmdir confdir3; mv confdir3a confdir3; done; rmdir confdir3 cd ../../.. mv local /root/gcc_local cd /root rm -rf misaka ``` ================================================ FILE: util/gen_wcwidth.krk ================================================ import fileio import os let eaw_txt = '/tmp/EastAsianWidth.txt' os.system(f"wget -O '{eaw_txt}' https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt") let lines with fileio.open(eaw_txt,'r') as f: lines = f.readlines() def classify(cp, gr, ct): # U+00AD: Soft hyphen - other things seem to want this to be 1 if cp == 0xAD: return 1 # Low control codes if cp < 0x20: return -1 # Del, higher control codes if cp >= 0x7f and cp < 0xa0: return -1 # Surrogates if cp >= 0xd800 and cp <= 0xdfff: return -1 # Combining characters if ct in ['Cf','Me','Mn']: return 0 # Hangul jamo if cp >= 0x1160 and cp <= 0x11FF: return 0 # Zero-width space if cp == 0x200b: return 0 # Mark neutral, narrow, ambigus, and half-width as 1 if gr in ['N','Na','A','H']: return 1 # Mark wide and full-width as 2 if gr in ['W','F']: return 2 # Mark everything else as invalid return -1 let classes = [None] * 0x110000 for line in lines: if !line or line.startswith('#') or ';' not in line: continue line = line.strip() let codepoint, rest = line.split(';',1) let group, comment = rest.split('#',1) group = group.strip() comment = comment.strip() let ctype = comment.split(' ')[0] # Is this a range? if '..' in codepoint: let start, end = codepoint.split('..',1) start = int(f'0x{start}',0) end = int(f'0x{end}',0) for i = start; i <= end; i++: classes[i] = classify(i, group, ctype) else: codepoint = int(f'0x{codepoint}',0) classes[codepoint] = classify(codepoint, group, ctype) for i in range(1,0x110000): if classes[i] is None: classes[i] = -1 print('''/* Generated by util/gen_wcwidth.krk */ #include int wcwidth(wchar_t wc) { \tif (wc == 0) return 0;''') let last = None for i in range(1,0x110000): if last is not None and classes[i] != last: print(f'\telse if (wc < {hex(i)}) return {last};') last = classes[i] print(f'\telse if (wc < 0x110000) return {last};\n\treturn -1;\n}}') ================================================ FILE: util/generate-etc-issue.sh ================================================ #!/bin/bash MAJOR=$(grep __kernel_version_major kernel/sys/version.c | sed s'/.*= \(.*\);/\1/') MINOR=$(grep __kernel_version_minor kernel/sys/version.c | sed s'/.*= \(.*\);/\1/') cat << EOF ToaruOS ${MAJOR}.${MINOR} \n \l EOF ================================================ FILE: util/generate-etc-os-release.sh ================================================ #!/bin/bash MAJOR=$(grep __kernel_version_major kernel/sys/version.c | sed s'/.*= \(.*\);/\1/') MINOR=$(grep __kernel_version_minor kernel/sys/version.c | sed s'/.*= \(.*\);/\1/') LOWER=$(grep __kernel_version_lower kernel/sys/version.c | sed s'/.*= \(.*\);/\1/') cat << EOF PRETTY_NAME="ToaruOS ${MAJOR}.${MINOR}" NAME="ToaruOS" VERSION_ID="${MAJOR}.${MINOR}.${LOWER}" VERSION="${MAJOR}.${MINOR}.${LOWER}" ID=toaru HOME_URL="https://toaruos.org/" SUPPORT_URL="https://github.com/klange/toaruos" BUG_REPORT_URL="https://github.com/klange/toaruos" EOF ================================================ FILE: util/generate-release-notes.sh ================================================ #!/bin/bash VERSION=$(git describe --exact-match --tags) LAST=$(git describe --abbrev=0 --tags ${VERSION}^) CHANGELOG=$(git log --pretty=format:%s ${LAST}..HEAD | grep ':' | sed -re 's/([^:]*)\:/- \`\1\`\:/' | sort) cat <