From ececac65c23ef06243814725c49553ca94f676a2 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 12 Jan 2021 11:57:58 +0100 Subject: Userland: Move command-line utilities to Userland/Utilities/ --- Userland/CMakeLists.txt | 51 +- Userland/Utilities/CMakeLists.txt | 49 + Userland/Utilities/adjtime.cpp | 76 + Userland/Utilities/allocate.cpp | 130 ++ Userland/Utilities/aplay.cpp | 77 + Userland/Utilities/arp.cpp | 57 + Userland/Utilities/avol.cpp | 70 + Userland/Utilities/base64.cpp | 80 + Userland/Utilities/basename.cpp | 46 + Userland/Utilities/beep.cpp | 33 + Userland/Utilities/cal.cpp | 168 ++ Userland/Utilities/cat.cpp | 97 + Userland/Utilities/checksum.cpp | 98 + Userland/Utilities/chgrp.cpp | 79 + Userland/Utilities/chmod.cpp | 256 +++ Userland/Utilities/chown.cpp | 94 + Userland/Utilities/chroot.cpp | 128 ++ Userland/Utilities/clear.cpp | 39 + Userland/Utilities/copy.cpp | 87 + Userland/Utilities/cp.cpp | 221 ++ Userland/Utilities/crash.cpp | 329 +++ Userland/Utilities/cut.cpp | 248 +++ Userland/Utilities/date.cpp | 99 + Userland/Utilities/ddate.cpp | 132 ++ Userland/Utilities/df.cpp | 103 + Userland/Utilities/dirname.cpp | 39 + Userland/Utilities/disasm.cpp | 156 ++ Userland/Utilities/disk_benchmark.cpp | 208 ++ Userland/Utilities/dmesg.cpp | 57 + Userland/Utilities/du.cpp | 205 ++ Userland/Utilities/echo.cpp | 54 + Userland/Utilities/env.cpp | 75 + Userland/Utilities/expr.cpp | 620 ++++++ Userland/Utilities/false.cpp | 32 + Userland/Utilities/fgrep.cpp | 48 + Userland/Utilities/find.cpp | 492 +++++ Userland/Utilities/flock.cpp | 52 + Userland/Utilities/functrace.cpp | 180 ++ Userland/Utilities/gml-format.cpp | 105 + Userland/Utilities/grep.cpp | 214 ++ Userland/Utilities/gron.cpp | 137 ++ Userland/Utilities/gunzip.cpp | 80 + Userland/Utilities/head.cpp | 162 ++ Userland/Utilities/hexdump.cpp | 94 + Userland/Utilities/host.cpp | 76 + Userland/Utilities/hostname.cpp | 57 + Userland/Utilities/html.cpp | 84 + Userland/Utilities/id.cpp | 180 ++ Userland/Utilities/ifconfig.cpp | 193 ++ Userland/Utilities/ini.cpp | 69 + Userland/Utilities/jp.cpp | 137 ++ Userland/Utilities/js.cpp | 918 ++++++++ Userland/Utilities/keymap.cpp | 61 + Userland/Utilities/kill.cpp | 96 + Userland/Utilities/killall.cpp | 91 + Userland/Utilities/ln.cpp | 72 + Userland/Utilities/ls.cpp | 504 +++++ Userland/Utilities/lsirq.cpp | 75 + Userland/Utilities/lsof.cpp | 203 ++ Userland/Utilities/lspci.cpp | 104 + Userland/Utilities/man.cpp | 121 ++ Userland/Utilities/md.cpp | 99 + Userland/Utilities/misbehaving-application.cpp | 43 + Userland/Utilities/mkdir.cpp | 92 + Userland/Utilities/mkfifo.cpp | 57 + Userland/Utilities/mknod.cpp | 91 + Userland/Utilities/modload.cpp | 45 + Userland/Utilities/modunload.cpp | 45 + Userland/Utilities/more.cpp | 75 + Userland/Utilities/mount.cpp | 219 ++ Userland/Utilities/mv.cpp | 94 + Userland/Utilities/nc.cpp | 234 ++ Userland/Utilities/nl.cpp | 117 + Userland/Utilities/notify.cpp | 53 + Userland/Utilities/ntpquery.cpp | 344 +++ Userland/Utilities/open.cpp | 68 + Userland/Utilities/pape.cpp | 96 + Userland/Utilities/passwd.cpp | 144 ++ Userland/Utilities/paste.cpp | 64 + Userland/Utilities/pidof.cpp | 92 + Userland/Utilities/ping.cpp | 242 +++ Userland/Utilities/pmap.cpp | 106 + Userland/Utilities/printf.cpp | 299 +++ Userland/Utilities/pro.cpp | 297 +++ Userland/Utilities/profile.cpp | 94 + Userland/Utilities/ps.cpp | 159 ++ Userland/Utilities/purge.cpp | 60 + Userland/Utilities/readelf.cpp | 452 ++++ Userland/Utilities/readlink.cpp | 58 + Userland/Utilities/realpath.cpp | 55 + Userland/Utilities/reboot.cpp | 37 + Userland/Utilities/rm.cpp | 102 + Userland/Utilities/rmdir.cpp | 51 + Userland/Utilities/seq.cpp | 129 ++ Userland/Utilities/shutdown.cpp | 48 + Userland/Utilities/sleep.cpp | 67 + Userland/Utilities/sort.cpp | 68 + Userland/Utilities/stat.cpp | 134 ++ Userland/Utilities/strace.cpp | 177 ++ Userland/Utilities/su.cpp | 101 + Userland/Utilities/sync.cpp | 34 + Userland/Utilities/syscall.cpp | 119 ++ Userland/Utilities/sysctl.cpp | 134 ++ Userland/Utilities/tail.cpp | 129 ++ Userland/Utilities/tar.cpp | 135 ++ Userland/Utilities/tee.cpp | 144 ++ Userland/Utilities/test-bindtodevice.cpp | 157 ++ Userland/Utilities/test-compress.cpp | 224 ++ Userland/Utilities/test-crypto.cpp | 2736 ++++++++++++++++++++++++ Userland/Utilities/test-gfx-font.cpp | 170 ++ Userland/Utilities/test-js.cpp | 765 +++++++ Userland/Utilities/test-pthread.cpp | 60 + Userland/Utilities/test-unveil.cpp | 86 + Userland/Utilities/test-web.cpp | 709 ++++++ Userland/Utilities/test.cpp | 537 +++++ Userland/Utilities/test_efault.cpp | 105 + Userland/Utilities/test_env.cpp | 113 + Userland/Utilities/test_io.cpp | 337 +++ Userland/Utilities/top.cpp | 238 +++ Userland/Utilities/touch.cpp | 85 + Userland/Utilities/tr.cpp | 59 + Userland/Utilities/tree.cpp | 154 ++ Userland/Utilities/true.cpp | 32 + Userland/Utilities/truncate.cpp | 131 ++ Userland/Utilities/tt.cpp | 393 ++++ Userland/Utilities/tty.cpp | 39 + Userland/Utilities/umount.cpp | 44 + Userland/Utilities/uname.cpp | 82 + Userland/Utilities/uniq.cpp | 100 + Userland/Utilities/unzip.cpp | 203 ++ Userland/Utilities/uptime.cpp | 79 + Userland/Utilities/useradd.cpp | 148 ++ Userland/Utilities/userdel.cpp | 172 ++ Userland/Utilities/utmpupdate.cpp | 118 + Userland/Utilities/w.cpp | 130 ++ Userland/Utilities/watch.cpp | 172 ++ Userland/Utilities/wc.cpp | 146 ++ Userland/Utilities/which.cpp | 52 + Userland/Utilities/whoami.cpp | 46 + Userland/Utilities/xargs.cpp | 299 +++ Userland/Utilities/yes.cpp | 47 + Userland/adjtime.cpp | 76 - Userland/allocate.cpp | 130 -- Userland/aplay.cpp | 77 - Userland/arp.cpp | 57 - Userland/avol.cpp | 70 - Userland/base64.cpp | 80 - Userland/basename.cpp | 46 - Userland/beep.cpp | 33 - Userland/cal.cpp | 168 -- Userland/cat.cpp | 97 - Userland/checksum.cpp | 98 - Userland/chgrp.cpp | 79 - Userland/chmod.cpp | 256 --- Userland/chown.cpp | 94 - Userland/chroot.cpp | 128 -- Userland/clear.cpp | 39 - Userland/copy.cpp | 87 - Userland/cp.cpp | 221 -- Userland/crash.cpp | 329 --- Userland/cut.cpp | 248 --- Userland/date.cpp | 99 - Userland/ddate.cpp | 132 -- Userland/df.cpp | 103 - Userland/dirname.cpp | 39 - Userland/disasm.cpp | 156 -- Userland/disk_benchmark.cpp | 208 -- Userland/dmesg.cpp | 57 - Userland/du.cpp | 205 -- Userland/echo.cpp | 54 - Userland/env.cpp | 75 - Userland/expr.cpp | 620 ------ Userland/false.cpp | 32 - Userland/fgrep.cpp | 48 - Userland/find.cpp | 492 ----- Userland/flock.cpp | 52 - Userland/functrace.cpp | 180 -- Userland/gml-format.cpp | 105 - Userland/grep.cpp | 214 -- Userland/gron.cpp | 137 -- Userland/gunzip.cpp | 80 - Userland/head.cpp | 162 -- Userland/hexdump.cpp | 94 - Userland/host.cpp | 76 - Userland/hostname.cpp | 57 - Userland/html.cpp | 84 - Userland/id.cpp | 180 -- Userland/ifconfig.cpp | 193 -- Userland/ini.cpp | 69 - Userland/jp.cpp | 137 -- Userland/js.cpp | 918 -------- Userland/keymap.cpp | 61 - Userland/kill.cpp | 96 - Userland/killall.cpp | 91 - Userland/ln.cpp | 72 - Userland/ls.cpp | 504 ----- Userland/lsirq.cpp | 75 - Userland/lsof.cpp | 203 -- Userland/lspci.cpp | 104 - Userland/man.cpp | 121 -- Userland/md.cpp | 99 - Userland/misbehaving-application.cpp | 43 - Userland/mkdir.cpp | 92 - Userland/mkfifo.cpp | 57 - Userland/mknod.cpp | 91 - Userland/modload.cpp | 45 - Userland/modunload.cpp | 45 - Userland/more.cpp | 75 - Userland/mount.cpp | 219 -- Userland/mv.cpp | 94 - Userland/nc.cpp | 234 -- Userland/nl.cpp | 117 - Userland/notify.cpp | 53 - Userland/ntpquery.cpp | 344 --- Userland/open.cpp | 68 - Userland/pape.cpp | 96 - Userland/passwd.cpp | 144 -- Userland/paste.cpp | 64 - Userland/pidof.cpp | 92 - Userland/ping.cpp | 242 --- Userland/pmap.cpp | 106 - Userland/printf.cpp | 299 --- Userland/pro.cpp | 297 --- Userland/profile.cpp | 94 - Userland/ps.cpp | 159 -- Userland/purge.cpp | 60 - Userland/readelf.cpp | 452 ---- Userland/readlink.cpp | 58 - Userland/realpath.cpp | 55 - Userland/reboot.cpp | 37 - Userland/rm.cpp | 102 - Userland/rmdir.cpp | 51 - Userland/seq.cpp | 129 -- Userland/shutdown.cpp | 48 - Userland/sleep.cpp | 67 - Userland/sort.cpp | 68 - Userland/stat.cpp | 134 -- Userland/strace.cpp | 177 -- Userland/su.cpp | 101 - Userland/sync.cpp | 34 - Userland/syscall.cpp | 119 -- Userland/sysctl.cpp | 134 -- Userland/tail.cpp | 129 -- Userland/tar.cpp | 135 -- Userland/tee.cpp | 144 -- Userland/test-bindtodevice.cpp | 157 -- Userland/test-compress.cpp | 224 -- Userland/test-crypto.cpp | 2736 ------------------------ Userland/test-gfx-font.cpp | 170 -- Userland/test-js.cpp | 765 ------- Userland/test-pthread.cpp | 60 - Userland/test-unveil.cpp | 86 - Userland/test-web.cpp | 709 ------ Userland/test.cpp | 537 ----- Userland/test_efault.cpp | 105 - Userland/test_env.cpp | 113 - Userland/test_io.cpp | 337 --- Userland/top.cpp | 238 --- Userland/touch.cpp | 85 - Userland/tr.cpp | 59 - Userland/tree.cpp | 154 -- Userland/true.cpp | 32 - Userland/truncate.cpp | 131 -- Userland/tt.cpp | 393 ---- Userland/tty.cpp | 39 - Userland/umount.cpp | 44 - Userland/uname.cpp | 82 - Userland/uniq.cpp | 100 - Userland/unzip.cpp | 203 -- Userland/uptime.cpp | 79 - Userland/useradd.cpp | 148 -- Userland/userdel.cpp | 172 -- Userland/utmpupdate.cpp | 118 - Userland/w.cpp | 130 -- Userland/watch.cpp | 172 -- Userland/wc.cpp | 146 -- Userland/which.cpp | 52 - Userland/whoami.cpp | 46 - Userland/xargs.cpp | 299 --- Userland/yes.cpp | 47 - 280 files changed, 23319 insertions(+), 23319 deletions(-) create mode 100644 Userland/Utilities/CMakeLists.txt create mode 100644 Userland/Utilities/adjtime.cpp create mode 100644 Userland/Utilities/allocate.cpp create mode 100644 Userland/Utilities/aplay.cpp create mode 100644 Userland/Utilities/arp.cpp create mode 100644 Userland/Utilities/avol.cpp create mode 100644 Userland/Utilities/base64.cpp create mode 100644 Userland/Utilities/basename.cpp create mode 100644 Userland/Utilities/beep.cpp create mode 100644 Userland/Utilities/cal.cpp create mode 100644 Userland/Utilities/cat.cpp create mode 100644 Userland/Utilities/checksum.cpp create mode 100644 Userland/Utilities/chgrp.cpp create mode 100644 Userland/Utilities/chmod.cpp create mode 100644 Userland/Utilities/chown.cpp create mode 100644 Userland/Utilities/chroot.cpp create mode 100644 Userland/Utilities/clear.cpp create mode 100644 Userland/Utilities/copy.cpp create mode 100644 Userland/Utilities/cp.cpp create mode 100644 Userland/Utilities/crash.cpp create mode 100644 Userland/Utilities/cut.cpp create mode 100644 Userland/Utilities/date.cpp create mode 100644 Userland/Utilities/ddate.cpp create mode 100644 Userland/Utilities/df.cpp create mode 100644 Userland/Utilities/dirname.cpp create mode 100644 Userland/Utilities/disasm.cpp create mode 100644 Userland/Utilities/disk_benchmark.cpp create mode 100644 Userland/Utilities/dmesg.cpp create mode 100644 Userland/Utilities/du.cpp create mode 100644 Userland/Utilities/echo.cpp create mode 100644 Userland/Utilities/env.cpp create mode 100644 Userland/Utilities/expr.cpp create mode 100644 Userland/Utilities/false.cpp create mode 100644 Userland/Utilities/fgrep.cpp create mode 100644 Userland/Utilities/find.cpp create mode 100644 Userland/Utilities/flock.cpp create mode 100644 Userland/Utilities/functrace.cpp create mode 100644 Userland/Utilities/gml-format.cpp create mode 100644 Userland/Utilities/grep.cpp create mode 100644 Userland/Utilities/gron.cpp create mode 100644 Userland/Utilities/gunzip.cpp create mode 100644 Userland/Utilities/head.cpp create mode 100644 Userland/Utilities/hexdump.cpp create mode 100644 Userland/Utilities/host.cpp create mode 100644 Userland/Utilities/hostname.cpp create mode 100644 Userland/Utilities/html.cpp create mode 100644 Userland/Utilities/id.cpp create mode 100644 Userland/Utilities/ifconfig.cpp create mode 100644 Userland/Utilities/ini.cpp create mode 100644 Userland/Utilities/jp.cpp create mode 100644 Userland/Utilities/js.cpp create mode 100644 Userland/Utilities/keymap.cpp create mode 100644 Userland/Utilities/kill.cpp create mode 100644 Userland/Utilities/killall.cpp create mode 100644 Userland/Utilities/ln.cpp create mode 100644 Userland/Utilities/ls.cpp create mode 100644 Userland/Utilities/lsirq.cpp create mode 100644 Userland/Utilities/lsof.cpp create mode 100644 Userland/Utilities/lspci.cpp create mode 100644 Userland/Utilities/man.cpp create mode 100644 Userland/Utilities/md.cpp create mode 100644 Userland/Utilities/misbehaving-application.cpp create mode 100644 Userland/Utilities/mkdir.cpp create mode 100644 Userland/Utilities/mkfifo.cpp create mode 100644 Userland/Utilities/mknod.cpp create mode 100644 Userland/Utilities/modload.cpp create mode 100644 Userland/Utilities/modunload.cpp create mode 100644 Userland/Utilities/more.cpp create mode 100644 Userland/Utilities/mount.cpp create mode 100644 Userland/Utilities/mv.cpp create mode 100644 Userland/Utilities/nc.cpp create mode 100644 Userland/Utilities/nl.cpp create mode 100644 Userland/Utilities/notify.cpp create mode 100644 Userland/Utilities/ntpquery.cpp create mode 100644 Userland/Utilities/open.cpp create mode 100644 Userland/Utilities/pape.cpp create mode 100644 Userland/Utilities/passwd.cpp create mode 100644 Userland/Utilities/paste.cpp create mode 100644 Userland/Utilities/pidof.cpp create mode 100644 Userland/Utilities/ping.cpp create mode 100644 Userland/Utilities/pmap.cpp create mode 100644 Userland/Utilities/printf.cpp create mode 100644 Userland/Utilities/pro.cpp create mode 100644 Userland/Utilities/profile.cpp create mode 100644 Userland/Utilities/ps.cpp create mode 100644 Userland/Utilities/purge.cpp create mode 100644 Userland/Utilities/readelf.cpp create mode 100644 Userland/Utilities/readlink.cpp create mode 100644 Userland/Utilities/realpath.cpp create mode 100644 Userland/Utilities/reboot.cpp create mode 100644 Userland/Utilities/rm.cpp create mode 100644 Userland/Utilities/rmdir.cpp create mode 100644 Userland/Utilities/seq.cpp create mode 100644 Userland/Utilities/shutdown.cpp create mode 100644 Userland/Utilities/sleep.cpp create mode 100644 Userland/Utilities/sort.cpp create mode 100644 Userland/Utilities/stat.cpp create mode 100644 Userland/Utilities/strace.cpp create mode 100644 Userland/Utilities/su.cpp create mode 100644 Userland/Utilities/sync.cpp create mode 100644 Userland/Utilities/syscall.cpp create mode 100644 Userland/Utilities/sysctl.cpp create mode 100644 Userland/Utilities/tail.cpp create mode 100644 Userland/Utilities/tar.cpp create mode 100644 Userland/Utilities/tee.cpp create mode 100644 Userland/Utilities/test-bindtodevice.cpp create mode 100644 Userland/Utilities/test-compress.cpp create mode 100644 Userland/Utilities/test-crypto.cpp create mode 100644 Userland/Utilities/test-gfx-font.cpp create mode 100644 Userland/Utilities/test-js.cpp create mode 100644 Userland/Utilities/test-pthread.cpp create mode 100644 Userland/Utilities/test-unveil.cpp create mode 100644 Userland/Utilities/test-web.cpp create mode 100644 Userland/Utilities/test.cpp create mode 100644 Userland/Utilities/test_efault.cpp create mode 100644 Userland/Utilities/test_env.cpp create mode 100644 Userland/Utilities/test_io.cpp create mode 100644 Userland/Utilities/top.cpp create mode 100644 Userland/Utilities/touch.cpp create mode 100644 Userland/Utilities/tr.cpp create mode 100644 Userland/Utilities/tree.cpp create mode 100644 Userland/Utilities/true.cpp create mode 100644 Userland/Utilities/truncate.cpp create mode 100644 Userland/Utilities/tt.cpp create mode 100644 Userland/Utilities/tty.cpp create mode 100644 Userland/Utilities/umount.cpp create mode 100644 Userland/Utilities/uname.cpp create mode 100644 Userland/Utilities/uniq.cpp create mode 100644 Userland/Utilities/unzip.cpp create mode 100644 Userland/Utilities/uptime.cpp create mode 100644 Userland/Utilities/useradd.cpp create mode 100644 Userland/Utilities/userdel.cpp create mode 100644 Userland/Utilities/utmpupdate.cpp create mode 100644 Userland/Utilities/w.cpp create mode 100644 Userland/Utilities/watch.cpp create mode 100644 Userland/Utilities/wc.cpp create mode 100644 Userland/Utilities/which.cpp create mode 100644 Userland/Utilities/whoami.cpp create mode 100644 Userland/Utilities/xargs.cpp create mode 100644 Userland/Utilities/yes.cpp delete mode 100644 Userland/adjtime.cpp delete mode 100644 Userland/allocate.cpp delete mode 100644 Userland/aplay.cpp delete mode 100644 Userland/arp.cpp delete mode 100644 Userland/avol.cpp delete mode 100644 Userland/base64.cpp delete mode 100644 Userland/basename.cpp delete mode 100644 Userland/beep.cpp delete mode 100644 Userland/cal.cpp delete mode 100644 Userland/cat.cpp delete mode 100644 Userland/checksum.cpp delete mode 100644 Userland/chgrp.cpp delete mode 100644 Userland/chmod.cpp delete mode 100644 Userland/chown.cpp delete mode 100644 Userland/chroot.cpp delete mode 100644 Userland/clear.cpp delete mode 100644 Userland/copy.cpp delete mode 100644 Userland/cp.cpp delete mode 100644 Userland/crash.cpp delete mode 100644 Userland/cut.cpp delete mode 100644 Userland/date.cpp delete mode 100644 Userland/ddate.cpp delete mode 100644 Userland/df.cpp delete mode 100644 Userland/dirname.cpp delete mode 100644 Userland/disasm.cpp delete mode 100644 Userland/disk_benchmark.cpp delete mode 100644 Userland/dmesg.cpp delete mode 100644 Userland/du.cpp delete mode 100644 Userland/echo.cpp delete mode 100644 Userland/env.cpp delete mode 100644 Userland/expr.cpp delete mode 100644 Userland/false.cpp delete mode 100644 Userland/fgrep.cpp delete mode 100644 Userland/find.cpp delete mode 100644 Userland/flock.cpp delete mode 100644 Userland/functrace.cpp delete mode 100644 Userland/gml-format.cpp delete mode 100644 Userland/grep.cpp delete mode 100644 Userland/gron.cpp delete mode 100644 Userland/gunzip.cpp delete mode 100644 Userland/head.cpp delete mode 100644 Userland/hexdump.cpp delete mode 100644 Userland/host.cpp delete mode 100644 Userland/hostname.cpp delete mode 100644 Userland/html.cpp delete mode 100644 Userland/id.cpp delete mode 100644 Userland/ifconfig.cpp delete mode 100644 Userland/ini.cpp delete mode 100644 Userland/jp.cpp delete mode 100644 Userland/js.cpp delete mode 100644 Userland/keymap.cpp delete mode 100644 Userland/kill.cpp delete mode 100644 Userland/killall.cpp delete mode 100644 Userland/ln.cpp delete mode 100644 Userland/ls.cpp delete mode 100644 Userland/lsirq.cpp delete mode 100644 Userland/lsof.cpp delete mode 100644 Userland/lspci.cpp delete mode 100644 Userland/man.cpp delete mode 100644 Userland/md.cpp delete mode 100644 Userland/misbehaving-application.cpp delete mode 100644 Userland/mkdir.cpp delete mode 100644 Userland/mkfifo.cpp delete mode 100644 Userland/mknod.cpp delete mode 100644 Userland/modload.cpp delete mode 100644 Userland/modunload.cpp delete mode 100644 Userland/more.cpp delete mode 100644 Userland/mount.cpp delete mode 100644 Userland/mv.cpp delete mode 100644 Userland/nc.cpp delete mode 100644 Userland/nl.cpp delete mode 100644 Userland/notify.cpp delete mode 100644 Userland/ntpquery.cpp delete mode 100644 Userland/open.cpp delete mode 100644 Userland/pape.cpp delete mode 100644 Userland/passwd.cpp delete mode 100644 Userland/paste.cpp delete mode 100644 Userland/pidof.cpp delete mode 100644 Userland/ping.cpp delete mode 100644 Userland/pmap.cpp delete mode 100644 Userland/printf.cpp delete mode 100644 Userland/pro.cpp delete mode 100644 Userland/profile.cpp delete mode 100644 Userland/ps.cpp delete mode 100644 Userland/purge.cpp delete mode 100644 Userland/readelf.cpp delete mode 100644 Userland/readlink.cpp delete mode 100644 Userland/realpath.cpp delete mode 100644 Userland/reboot.cpp delete mode 100644 Userland/rm.cpp delete mode 100644 Userland/rmdir.cpp delete mode 100644 Userland/seq.cpp delete mode 100644 Userland/shutdown.cpp delete mode 100644 Userland/sleep.cpp delete mode 100644 Userland/sort.cpp delete mode 100644 Userland/stat.cpp delete mode 100644 Userland/strace.cpp delete mode 100644 Userland/su.cpp delete mode 100644 Userland/sync.cpp delete mode 100644 Userland/syscall.cpp delete mode 100644 Userland/sysctl.cpp delete mode 100644 Userland/tail.cpp delete mode 100644 Userland/tar.cpp delete mode 100644 Userland/tee.cpp delete mode 100644 Userland/test-bindtodevice.cpp delete mode 100644 Userland/test-compress.cpp delete mode 100644 Userland/test-crypto.cpp delete mode 100644 Userland/test-gfx-font.cpp delete mode 100644 Userland/test-js.cpp delete mode 100644 Userland/test-pthread.cpp delete mode 100644 Userland/test-unveil.cpp delete mode 100644 Userland/test-web.cpp delete mode 100644 Userland/test.cpp delete mode 100644 Userland/test_efault.cpp delete mode 100644 Userland/test_env.cpp delete mode 100644 Userland/test_io.cpp delete mode 100644 Userland/top.cpp delete mode 100644 Userland/touch.cpp delete mode 100644 Userland/tr.cpp delete mode 100644 Userland/tree.cpp delete mode 100644 Userland/true.cpp delete mode 100644 Userland/truncate.cpp delete mode 100644 Userland/tt.cpp delete mode 100644 Userland/tty.cpp delete mode 100644 Userland/umount.cpp delete mode 100644 Userland/uname.cpp delete mode 100644 Userland/uniq.cpp delete mode 100644 Userland/unzip.cpp delete mode 100644 Userland/uptime.cpp delete mode 100644 Userland/useradd.cpp delete mode 100644 Userland/userdel.cpp delete mode 100644 Userland/utmpupdate.cpp delete mode 100644 Userland/w.cpp delete mode 100644 Userland/watch.cpp delete mode 100644 Userland/wc.cpp delete mode 100644 Userland/which.cpp delete mode 100644 Userland/whoami.cpp delete mode 100644 Userland/xargs.cpp delete mode 100644 Userland/yes.cpp (limited to 'Userland') diff --git a/Userland/CMakeLists.txt b/Userland/CMakeLists.txt index 2551a923c0..33717025e3 100644 --- a/Userland/CMakeLists.txt +++ b/Userland/CMakeLists.txt @@ -1,53 +1,4 @@ -file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") -list(APPEND SPECIAL_TARGETS "test" "install") - -foreach(CMD_SRC ${CMD_SOURCES}) - get_filename_component(CMD_NAME ${CMD_SRC} NAME_WE) - if (CMD_NAME IN_LIST SPECIAL_TARGETS) - add_executable("${CMD_NAME}-bin" ${CMD_SRC}) - target_link_libraries("${CMD_NAME}-bin" LibCore) - install(TARGETS "${CMD_NAME}-bin" RUNTIME DESTINATION bin) - install(CODE "execute_process(COMMAND mv ${CMD_NAME}-bin ${CMD_NAME} WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin)") - else () - add_executable(${CMD_NAME} ${CMD_SRC}) - target_link_libraries(${CMD_NAME} LibCore) - install(TARGETS ${CMD_NAME} RUNTIME DESTINATION bin) - endif() -endforeach() - -target_link_libraries(aplay LibAudio) -target_link_libraries(avol LibAudio) -target_link_libraries(checksum LibCrypto) -target_link_libraries(copy LibGUI) -target_link_libraries(disasm LibX86) -target_link_libraries(expr LibRegex) -target_link_libraries(functrace LibDebug LibX86) -target_link_libraries(gml-format LibGUI) -target_link_libraries(html LibWeb) -target_link_libraries(js LibJS LibLine) -target_link_libraries(keymap LibKeyboard) -target_link_libraries(lspci LibPCIDB) -target_link_libraries(man LibMarkdown) -target_link_libraries(md LibMarkdown) -target_link_libraries(misbehaving-application LibCore) -target_link_libraries(notify LibGUI) -target_link_libraries(open LibDesktop) -target_link_libraries(pape LibGUI) -target_link_libraries(passwd LibCrypt) -target_link_libraries(paste LibGUI) -target_link_libraries(pro LibProtocol) -target_link_libraries(su LibCrypt) -target_link_libraries(tar LibTar LibCompress) -target_link_libraries(test-crypto LibCrypto LibTLS LibLine) -target_link_libraries(test-compress LibCompress) -target_link_libraries(test-gfx-font LibGUI LibCore) -target_link_libraries(test-js LibJS LibLine LibCore) -target_link_libraries(test-pthread LibThread) -target_link_libraries(test-web LibWeb) -target_link_libraries(tt LibPthread) -target_link_libraries(grep LibRegex) -target_link_libraries(gunzip LibCompress) - add_subdirectory(DynamicLoader) add_subdirectory(Shell) add_subdirectory(Tests) +add_subdirectory(Utilities) diff --git a/Userland/Utilities/CMakeLists.txt b/Userland/Utilities/CMakeLists.txt new file mode 100644 index 0000000000..6dc2c78079 --- /dev/null +++ b/Userland/Utilities/CMakeLists.txt @@ -0,0 +1,49 @@ +file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") +list(APPEND SPECIAL_TARGETS "test" "install") + +foreach(CMD_SRC ${CMD_SOURCES}) + get_filename_component(CMD_NAME ${CMD_SRC} NAME_WE) + if (CMD_NAME IN_LIST SPECIAL_TARGETS) + add_executable("${CMD_NAME}-bin" ${CMD_SRC}) + target_link_libraries("${CMD_NAME}-bin" LibCore) + install(TARGETS "${CMD_NAME}-bin" RUNTIME DESTINATION bin) + install(CODE "execute_process(COMMAND mv ${CMD_NAME}-bin ${CMD_NAME} WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin)") + else () + add_executable(${CMD_NAME} ${CMD_SRC}) + target_link_libraries(${CMD_NAME} LibCore) + install(TARGETS ${CMD_NAME} RUNTIME DESTINATION bin) + endif() +endforeach() + +target_link_libraries(aplay LibAudio) +target_link_libraries(avol LibAudio) +target_link_libraries(checksum LibCrypto) +target_link_libraries(copy LibGUI) +target_link_libraries(disasm LibX86) +target_link_libraries(expr LibRegex) +target_link_libraries(functrace LibDebug LibX86) +target_link_libraries(gml-format LibGUI) +target_link_libraries(html LibWeb) +target_link_libraries(js LibJS LibLine) +target_link_libraries(keymap LibKeyboard) +target_link_libraries(lspci LibPCIDB) +target_link_libraries(man LibMarkdown) +target_link_libraries(md LibMarkdown) +target_link_libraries(misbehaving-application LibCore) +target_link_libraries(notify LibGUI) +target_link_libraries(open LibDesktop) +target_link_libraries(pape LibGUI) +target_link_libraries(passwd LibCrypt) +target_link_libraries(paste LibGUI) +target_link_libraries(pro LibProtocol) +target_link_libraries(su LibCrypt) +target_link_libraries(tar LibTar LibCompress) +target_link_libraries(test-crypto LibCrypto LibTLS LibLine) +target_link_libraries(test-compress LibCompress) +target_link_libraries(test-gfx-font LibGUI LibCore) +target_link_libraries(test-js LibJS LibLine LibCore) +target_link_libraries(test-pthread LibThread) +target_link_libraries(test-web LibWeb) +target_link_libraries(tt LibPthread) +target_link_libraries(grep LibRegex) +target_link_libraries(gunzip LibCompress) diff --git a/Userland/Utilities/adjtime.cpp b/Userland/Utilities/adjtime.cpp new file mode 100644 index 0000000000..810421bc98 --- /dev/null +++ b/Userland/Utilities/adjtime.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020, Nico Weber + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ +#ifdef __serenity__ + if (pledge("stdio settime", nullptr) < 0) { + perror("pledge"); + return 1; + } +#endif + + Core::ArgsParser args_parser; + double delta = __builtin_nan(""); + args_parser.add_option(delta, "Adjust system time by this many seconds", "set", 's', "delta_seconds"); + args_parser.parse(argc, argv); + + if (__builtin_isnan(delta)) { +#ifdef __serenity__ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } +#endif + } else { + long delta_us = static_cast(round(delta * 1'000'000)); + timeval delta_timeval; + delta_timeval.tv_sec = delta_us / 1'000'000; + delta_timeval.tv_usec = delta_us % 1'000'000; + if (delta_timeval.tv_usec < 0) { + delta_timeval.tv_sec--; + delta_timeval.tv_usec += 1'000'000; + } + if (adjtime(&delta_timeval, nullptr) < 0) { + perror("adjtime set"); + return 1; + } + } + + timeval remaining_delta_timeval; + if (adjtime(nullptr, &remaining_delta_timeval) < 0) { + perror("adjtime get"); + return 1; + } + double remaining_delta = remaining_delta_timeval.tv_sec + remaining_delta_timeval.tv_usec / 1'000'000.0; + printf("%f\n", remaining_delta); + + return 0; +} diff --git a/Userland/Utilities/allocate.cpp b/Userland/Utilities/allocate.cpp new file mode 100644 index 0000000000..9e906121a4 --- /dev/null +++ b/Userland/Utilities/allocate.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +static void usage() +{ + printf("usage: allocate [number [unit (B/KiB/MiB)]]\n"); + exit(1); +} + +enum class Unit { + Bytes, + KiB, + MiB, +}; + +int main(int argc, char** argv) +{ + int count = 50; + auto unit = Unit::MiB; + + if (argc >= 2) { + auto number = String(argv[1]).to_uint(); + if (!number.has_value()) { + usage(); + } + count = number.value(); + } + + if (argc >= 3) { + if (strcmp(argv[2], "B") == 0) + unit = Unit::Bytes; + else if (strcmp(argv[2], "KiB") == 0) + unit = Unit::KiB; + else if (strcmp(argv[2], "MiB") == 0) + unit = Unit::MiB; + else + usage(); + } + + switch (unit) { + case Unit::Bytes: + break; + case Unit::KiB: + count *= KiB; + break; + case Unit::MiB: + count *= MiB; + break; + } + + Core::ElapsedTimer timer; + + printf("allocating memory (%d bytes)...\n", count); + timer.start(); + char* ptr = (char*)malloc(count); + if (!ptr) { + printf("failed.\n"); + return 1; + } + printf("done in %dms\n", timer.elapsed()); + + auto pages = count / PAGE_SIZE; + auto step = pages / 10; + + Core::ElapsedTimer timer2; + + printf("writing one byte to each page of allocated memory...\n"); + timer.start(); + timer2.start(); + for (int i = 0; i < pages; ++i) { + ptr[i * PAGE_SIZE] = 1; + + if (i != 0 && (i % step) == 0) { + auto ms = timer2.elapsed(); + if (ms == 0) + ms = 1; + + auto bps = double(step * PAGE_SIZE) / (double(ms) / 1000); + + printf("step took %dms (%fMiB/s)\n", ms, bps / MiB); + + timer2.start(); + } + } + printf("done in %dms\n", timer.elapsed()); + + printf("sleeping for ten seconds...\n"); + for (int i = 0; i < 10; i++) { + printf("%d\n", i); + sleep(1); + } + printf("done.\n"); + + printf("freeing memory...\n"); + timer.start(); + free(ptr); + printf("done in %dms\n", timer.elapsed()); + + return 0; +} diff --git a/Userland/Utilities/aplay.cpp b/Userland/Utilities/aplay.cpp new file mode 100644 index 0000000000..5c5d221bf5 --- /dev/null +++ b/Userland/Utilities/aplay.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + const char* path = nullptr; + bool should_loop = false; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(path, "Path to audio file", "path"); + args_parser.add_option(should_loop, "Loop playback", "loop", 'l'); + args_parser.parse(argc, argv); + + Core::EventLoop loop; + + auto audio_client = Audio::ClientConnection::construct(); + audio_client->handshake(); + NonnullRefPtr loader = Audio::Loader::create(path); + if (loader->has_error()) { + fprintf(stderr, "Failed to load audio file: %s\n", loader->error_string()); + return 1; + } + + printf("\033[34;1m Playing\033[0m: %s\n", path); + printf("\033[34;1m Format\033[0m: %u Hz, %u-bit, %s\n", + loader->sample_rate(), + loader->bits_per_sample(), + loader->num_channels() == 1 ? "Mono" : "Stereo"); + printf("\033[34;1mProgress\033[0m: \033[s"); + for (;;) { + auto samples = loader->get_more_samples(); + if (samples) { + printf("\033[u"); + printf("%d/%d", loader->loaded_samples(), loader->total_samples()); + fflush(stdout); + audio_client->enqueue(*samples); + } else if (should_loop) { + loader->reset(); + } else if (audio_client->get_remaining_samples()) { + sleep(1); + } else { + break; + } + } + printf("\n"); + return 0; +} diff --git a/Userland/Utilities/arp.cpp b/Userland/Utilities/arp.cpp new file mode 100644 index 0000000000..2be5ce7a26 --- /dev/null +++ b/Userland/Utilities/arp.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main() +{ + auto file = Core::File::construct("/proc/net/arp"); + if (!file->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Error: %s\n", file->error_string()); + return 1; + } + + printf("Address HWaddress\n"); + auto file_contents = file->read_all(); + auto json = JsonValue::from_string(file_contents); + ASSERT(json.has_value()); + json.value().as_array().for_each([](auto& value) { + auto if_object = value.as_object(); + + auto ip_address = if_object.get("ip_address").to_string(); + auto mac_address = if_object.get("mac_address").to_string(); + + printf("%-15s ", ip_address.characters()); + printf("%-17s ", mac_address.characters()); + printf("\n"); + }); + + return 0; +} diff --git a/Userland/Utilities/avol.cpp b/Userland/Utilities/avol.cpp new file mode 100644 index 0000000000..cc38a6b0dc --- /dev/null +++ b/Userland/Utilities/avol.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + Core::EventLoop loop; + auto audio_client = Audio::ClientConnection::construct(); + audio_client->handshake(); + + bool mute = false; + bool unmute = false; + // FIXME: What is a good way to have an optional int argument? + const char* volume = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(mute, "Mute volume", "mute", 'm'); + args_parser.add_option(unmute, "Unmute volume", "unmute", 'M'); + args_parser.add_positional_argument(volume, "Volume to set", "volume", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (!mute && !unmute && !volume) { + auto volume = audio_client->get_main_mix_volume(); + printf("Volume: %d\n", volume); + return 0; + } + if (!(mute ^ unmute ^ (volume != nullptr))) { + fprintf(stderr, "Only one of mute, unmute or volume must be used\n"); + return 1; + } + if (mute) { + audio_client->set_muted(true); + printf("Muted.\n"); + } else if (unmute) { + audio_client->set_muted(false); + printf("Unmuted.\n"); + } else { + auto new_volume = atoi(volume); + audio_client->set_main_mix_volume(new_volume); + } + return 0; +} diff --git a/Userland/Utilities/base64.cpp b/Userland/Utilities/base64.cpp new file mode 100644 index 0000000000..f8e0fc6ff9 --- /dev/null +++ b/Userland/Utilities/base64.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, Tom Lebreux + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool decode = false; + const char* filepath = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(decode, "Decode data", "decode", 'd'); + args_parser.add_positional_argument(filepath, "", "file", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + ByteBuffer buffer; + if (filepath == nullptr || strcmp(filepath, "-") == 0) { + auto file = Core::File::construct(); + bool success = file->open( + STDIN_FILENO, + Core::IODevice::OpenMode::ReadOnly, + Core::File::ShouldCloseFileDescriptor::Yes); + ASSERT(success); + buffer = file->read_all(); + } else { + auto result = Core::File::open(filepath, Core::IODevice::OpenMode::ReadOnly); + ASSERT(!result.is_error()); + auto file = result.value(); + buffer = file->read_all(); + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (decode) { + auto decoded = decode_base64(StringView(buffer)); + fwrite(decoded.data(), sizeof(u8), decoded.size(), stdout); + return 0; + } + + auto encoded = encode_base64(buffer); + printf("%s\n", encoded.characters()); +} diff --git a/Userland/Utilities/basename.cpp b/Userland/Utilities/basename.cpp new file mode 100644 index 0000000000..65862f1072 --- /dev/null +++ b/Userland/Utilities/basename.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* path = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(path, "Path to get basename from", "path"); + args_parser.parse(argc, argv); + + printf("%s\n", LexicalPath(path).basename().characters()); + return 0; +} diff --git a/Userland/Utilities/beep.cpp b/Userland/Utilities/beep.cpp new file mode 100644 index 0000000000..7f183151cc --- /dev/null +++ b/Userland/Utilities/beep.cpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +int main() +{ + sysbeep(); + return 0; +} diff --git a/Userland/Utilities/cal.cpp b/Userland/Utilities/cal.cpp new file mode 100644 index 0000000000..c5b436eb9c --- /dev/null +++ b/Userland/Utilities/cal.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +const int line_width = 70; +const int line_count = 8; +const int column_width = 22; + +char print_buffer[line_width * line_count]; +char temp_buffer[line_width * 8]; + +int target_year; +int target_month; +int target_day; + +int current_year; +int current_month; + +static void append_to_print(char* buffer, int row, int column, char* text) +{ + int starting_point = (line_width * row) + (column * column_width); + for (int i = 0; text[i] != '\0'; i++) { + buffer[starting_point + i] = text[i]; + } +} + +static void insert_month_to_print(int column, int month, int year) +{ + int printing_column = column; + int printing_row = 0; + + // FIXME: Both the month name and month header text should be provided by a locale + sprintf(temp_buffer, " %02u - %04u ", month, year); + append_to_print(print_buffer, printing_row, printing_column, temp_buffer); + printing_row++; + + sprintf(temp_buffer, "Su Mo Tu We Th Fr Sa"); + append_to_print(print_buffer, printing_row, printing_column, temp_buffer); + printing_row++; + int day_to_print = 1; + auto date_time = Core::DateTime::create(year, month, 1); + int first_day_of_week_for_month = date_time.weekday(); + int days_in_the_month = date_time.days_in_month(); + int last_written_chars = 0; + for (int i = 1; day_to_print <= days_in_the_month; ++i) { + if (i - 1 < first_day_of_week_for_month) { + last_written_chars += sprintf(temp_buffer + last_written_chars, " "); + } else { + if (year == current_year && month == current_month && target_day == day_to_print) { + // FIXME: To replicate Unix cal it would be better to use "\x1b[30;47m%2d\x1b[0m " in here instead of *. + // However, doing that messes up the layout. + last_written_chars += sprintf(temp_buffer + last_written_chars, "%2d*", day_to_print); + } else { + last_written_chars += sprintf(temp_buffer + last_written_chars, "%2d ", day_to_print); + } + day_to_print++; + } + + append_to_print(print_buffer, printing_row, printing_column, temp_buffer); + + if (i % 7 == 0) { + printing_row++; + memset(temp_buffer, ' ', line_width * 8); + temp_buffer[line_width * 8 - 1] = '\0'; + last_written_chars = 0; + } + } +} + +static void clean_buffers() +{ + for (int i = 1; i < line_width * line_count; ++i) { + print_buffer[i - 1] = i % line_width == 0 ? '\n' : ' '; + } + print_buffer[line_width * line_count - 1] = '\0'; + + for (int i = 0; i < line_width; ++i) { + temp_buffer[i] = ' '; + } + temp_buffer[line_width - 1] = '\0'; +} + +int main(int argc, char** argv) +{ + int day = 0; + int month = 0; + int year = 0; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Display a nice overview of a month or year, defaulting to the current month."); + // FIXME: This should ensure two values get parsed as month + year + args_parser.add_positional_argument(day, "Day of year", "day", Core::ArgsParser::Required::No); + args_parser.add_positional_argument(month, "Month", "month", Core::ArgsParser::Required::No); + args_parser.add_positional_argument(year, "Year", "year", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + time_t now = time(nullptr); + auto* tm = localtime(&now); + + // Hack: workaround two values parsing as day + month. + if (day && month && !year) { + year = month; + month = day; + day = 0; + } + + bool year_mode = !day && !month && year; + + if (!year) + year = tm->tm_year + 1900; + if (!month) + month = tm->tm_mon + 1; + if (!day) + day = tm->tm_mday; + + current_year = year; + current_month = month; + + clean_buffers(); + + if (year_mode) { + printf(" "); + printf("Year %4d", year); + printf(" \n\n"); + + for (int i = 1; i < 12; ++i) { + insert_month_to_print(0, i++, year); + insert_month_to_print(1, i++, year); + insert_month_to_print(2, i, year); + printf("%s\n", print_buffer); + clean_buffers(); + } + } else { + insert_month_to_print(0, month, year); + printf("%s\n\n", print_buffer); + clean_buffers(); + } + + return 0; +} diff --git a/Userland/Utilities/cat.cpp b/Userland/Utilities/cat.cpp new file mode 100644 index 0000000000..496eb890bc --- /dev/null +++ b/Userland/Utilities/cat.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector paths; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Concatenate files or pipes to stdout."); + args_parser.add_positional_argument(paths, "File path", "path", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + Vector fds; + if (!paths.is_empty()) { + for (const char* path : paths) { + int fd; + if (StringView { path } == "-") { + fd = 0; + } else if ((fd = open(path, O_RDONLY)) == -1) { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + continue; + } + fds.append(fd); + } + } else { + fds.append(0); + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + for (auto& fd : fds) { + for (;;) { + char buf[32768]; + ssize_t nread = read(fd, buf, sizeof(buf)); + if (nread == 0) + break; + if (nread < 0) { + perror("read"); + return 2; + } + size_t already_written = 0; + while (already_written < (size_t)nread) { + ssize_t nwritten = write(1, buf + already_written, nread - already_written); + if (nwritten < 0) { + perror("write"); + return 3; + } + already_written += nwritten; + } + } + close(fd); + } + + return 0; +} diff --git a/Userland/Utilities/checksum.cpp b/Userland/Utilities/checksum.cpp new file mode 100644 index 0000000000..d2c6e59e5b --- /dev/null +++ b/Userland/Utilities/checksum.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020, Linus Groh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto program_name = StringView { argv[0] }; + auto hash_kind { Crypto::Hash::HashKind::None }; + + if (program_name == "md5sum") + hash_kind = Crypto::Hash::HashKind::MD5; + else if (program_name == "sha1sum") + hash_kind = Crypto::Hash::HashKind::SHA1; + else if (program_name == "sha256sum") + hash_kind = Crypto::Hash::HashKind::SHA256; + else if (program_name == "sha512sum") + hash_kind = Crypto::Hash::HashKind::SHA512; + + if (hash_kind == Crypto::Hash::HashKind::None) { + fprintf(stderr, "Error: program must be executed as 'md5sum', 'sha1sum', 'sha256sum' or 'sha512sum'; got '%s'\n", argv[0]); + exit(1); + } + + auto hash_name = program_name.substring_view(0, program_name.length() - 3).to_string().to_uppercase(); + auto paths_help_string = String::format("File(s) to print %s checksum of", hash_name.characters()); + + Vector paths; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(paths, paths_help_string.characters(), "path", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (paths.is_empty()) + paths.append("-"); + + Crypto::Hash::Manager hash; + hash.initialize(hash_kind); + + bool success; + auto has_error = false; + auto file = Core::File::construct(); + + for (auto path : paths) { + if (StringView { path } == "-") { + success = file->open(STDIN_FILENO, Core::IODevice::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No); + } else { + file->set_filename(path); + success = file->open(Core::IODevice::OpenMode::ReadOnly); + } + if (!success) { + fprintf(stderr, "%s: %s: %s\n", argv[0], path, file->error_string()); + has_error = true; + continue; + } + hash.update(file->read_all()); + auto digest = hash.digest(); + auto digest_data = digest.immutable_data(); + StringBuilder builder; + for (size_t i = 0; i < hash.digest_size(); ++i) + builder.appendf("%02x", digest_data[i]); + auto hash_sum_hex = builder.build(); + printf("%s %s\n", hash_sum_hex.characters(), path); + } + return has_error ? 1 : 0; +} diff --git a/Userland/Utilities/chgrp.cpp b/Userland/Utilities/chgrp.cpp new file mode 100644 index 0000000000..18cb0475ab --- /dev/null +++ b/Userland/Utilities/chgrp.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath chown", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* gid_arg = nullptr; + const char* path = nullptr; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Change the owning group for a file or directory."); + args_parser.add_positional_argument(gid_arg, "Group ID", "gid"); + args_parser.add_positional_argument(path, "Path to file", "path"); + args_parser.parse(argc, argv); + + gid_t new_gid = -1; + + if (String(gid_arg).is_empty()) { + fprintf(stderr, "Empty gid option\n"); + return 1; + } + + auto number = String(gid_arg).to_uint(); + if (number.has_value()) { + new_gid = number.value(); + } else { + auto* group = getgrnam(gid_arg); + if (!group) { + fprintf(stderr, "Unknown group '%s'\n", gid_arg); + return 1; + } + new_gid = group->gr_gid; + } + + int rc = chown(path, -1, new_gid); + if (rc < 0) { + perror("chgrp"); + return 1; + } + + return 0; +} diff --git a/Userland/Utilities/chmod.cpp b/Userland/Utilities/chmod.cpp new file mode 100644 index 0000000000..4b3dd20089 --- /dev/null +++ b/Userland/Utilities/chmod.cpp @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +/* the new mode will be computed using the boolean function(for each bit): + + |current mode|removal mask|applying mask|result | + | 0 | 0 | 0 | 0 | + | 0 | 0 | 1 | 1 | + | 0 | 1 | 0 | 0 | + | 0 | 1 | 1 | 1 | ---> find the CNF --> find the minimal CNF + | 1 | 0 | 0 | 1 | + | 1 | 0 | 1 | 1 | + | 1 | 1 | 0 | 0 | + | 1 | 1 | 1 | 1 | +*/ + +class Mask { +private: + mode_t removal_mask; // the bits that will be removed + mode_t applying_mask; // the bits that will be set + +public: + Mask() + : removal_mask(0) + , applying_mask(0) + { + } + + Mask& operator|=(const Mask& other) + { + removal_mask |= other.removal_mask; + applying_mask |= other.applying_mask; + + return *this; + } + + mode_t& get_removal_mask() { return removal_mask; } + mode_t& get_applying_mask() { return applying_mask; } +}; + +Optional string_to_mode(char access_scope, const char*& access_string); +Optional apply_permission(char access_scope, char permission, char operation); + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath fattr", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (argc < 3) { + printf("usage: chmod \n" + " chmod [[ugoa][+-=][rwx...],...] \n"); + return 1; + } + + Mask mask; + + /* compute a mask */ + if (argv[1][0] >= '0' && argv[1][0] <= '7') { + if (sscanf(argv[1], "%ho", &mask.get_applying_mask()) != 1) { + perror("sscanf"); + return 1; + } + mask.get_removal_mask() = ~mask.get_applying_mask(); + } else { + const char* access_string = argv[1]; + + while (*access_string != '\0') { + Optional tmp_mask; + switch (*access_string) { + case 'u': + tmp_mask = string_to_mode('u', ++access_string); + break; + case 'g': + tmp_mask = string_to_mode('g', ++access_string); + break; + case 'o': + tmp_mask = string_to_mode('o', ++access_string); + break; + case 'a': + tmp_mask = string_to_mode('a', ++access_string); + break; + case '=': + case '+': + case '-': + tmp_mask = string_to_mode('a', access_string); + break; + case ',': + ++access_string; + continue; + } + if (!tmp_mask.has_value()) { + fprintf(stderr, "chmod: invalid mode: %s\n", argv[1]); + return 1; + } + mask |= tmp_mask.value(); + } + } + + /* set the mask for each file's permissions */ + struct stat current_access; + int i = 2; + while (i < argc) { + if (stat(argv[i], ¤t_access) != 0) { + perror("stat"); + return 1; + } + /* found the minimal CNF by The Quine–McCluskey algorithm and use it */ + mode_t mode = mask.get_applying_mask() + | (current_access.st_mode & ~mask.get_removal_mask()); + if (chmod(argv[i++], mode) != 0) { + perror("chmod"); + } + } + + return 0; +} + +Optional string_to_mode(char access_scope, const char*& access_string) +{ + char operation = *access_string; + + if (operation != '+' && operation != '-' && operation != '=') { + return {}; + } + + Mask mask; + if (operation == '=') { + switch (access_scope) { + case 'u': + mask.get_removal_mask() = (S_IRUSR | S_IWUSR | S_IXUSR); + break; + case 'g': + mask.get_removal_mask() = (S_IRGRP | S_IWGRP | S_IXGRP); + break; + case 'o': + mask.get_removal_mask() = (S_IROTH | S_IWOTH | S_IXOTH); + break; + case 'a': + mask.get_removal_mask() = (S_IRUSR | S_IWUSR | S_IXUSR + | S_IRGRP | S_IWGRP | S_IXGRP + | S_IROTH | S_IWOTH | S_IXOTH); + break; + } + operation = '+'; + } + + access_string++; + while (*access_string != '\0' && *access_string != ',') { + Optional tmp_mask; + tmp_mask = apply_permission(access_scope, *access_string, operation); + if (!tmp_mask.has_value()) { + return {}; + } + mask |= tmp_mask.value(); + access_string++; + } + + return mask; +} + +Optional apply_permission(char access_scope, char permission, char operation) +{ + if (permission != 'r' && permission != 'w' && permission != 'x') { + return {}; + } + + Mask mask; + mode_t tmp_mask = 0; + switch (access_scope) { + case 'u': + switch (permission) { + case 'r': + tmp_mask = S_IRUSR; + break; + case 'w': + tmp_mask = S_IWUSR; + break; + case 'x': + tmp_mask = S_IXUSR; + break; + } + break; + case 'g': + switch (permission) { + case 'r': + tmp_mask = S_IRGRP; + break; + case 'w': + tmp_mask = S_IWGRP; + break; + case 'x': + tmp_mask = S_IXGRP; + break; + } + break; + case 'o': + switch (permission) { + case 'r': + tmp_mask = S_IROTH; + break; + case 'w': + tmp_mask = S_IWOTH; + break; + case 'x': + tmp_mask = S_IXOTH; + break; + } + break; + case 'a': + mask |= apply_permission('u', permission, operation).value(); + mask |= apply_permission('g', permission, operation).value(); + mask |= apply_permission('o', permission, operation).value(); + break; + } + + if (operation == '+') { + mask.get_applying_mask() |= tmp_mask; + } else { + mask.get_removal_mask() |= tmp_mask; + } + + return mask; +} diff --git a/Userland/Utilities/chown.cpp b/Userland/Utilities/chown.cpp new file mode 100644 index 0000000000..6e5040380b --- /dev/null +++ b/Userland/Utilities/chown.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath chown", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (argc < 3) { + printf("usage: chown \n"); + return 0; + } + + uid_t new_uid = -1; + gid_t new_gid = -1; + + auto parts = String(argv[1]).split(':', true); + if (parts.is_empty()) { + fprintf(stderr, "Empty uid/gid spec\n"); + return 1; + } + if (parts[0].is_empty() || (parts.size() == 2 && parts[1].is_empty()) || parts.size() > 2) { + fprintf(stderr, "Invalid uid/gid spec\n"); + return 1; + } + + auto number = parts[0].to_uint(); + if (number.has_value()) { + new_uid = number.value(); + } else { + auto* passwd = getpwnam(parts[0].characters()); + if (!passwd) { + fprintf(stderr, "Unknown user '%s'\n", parts[0].characters()); + return 1; + } + new_uid = passwd->pw_uid; + } + + if (parts.size() == 2) { + auto number = parts[1].to_uint(); + if (number.has_value()) { + new_gid = number.value(); + } else { + auto* group = getgrnam(parts[1].characters()); + if (!group) { + fprintf(stderr, "Unknown group '%s'\n", parts[1].characters()); + return 1; + } + new_gid = group->gr_gid; + } + } + + int rc = chown(argv[2], new_uid, new_gid); + if (rc < 0) { + perror("chown"); + return 1; + } + + return 0; +} diff --git a/Userland/Utilities/chroot.cpp b/Userland/Utilities/chroot.cpp new file mode 100644 index 0000000000..07c774ecda --- /dev/null +++ b/Userland/Utilities/chroot.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + int flags = -1; + uid_t chroot_user = 0; + gid_t chroot_group = 0; + const char* path = nullptr; + const char* program = "/bin/Shell"; + const char* userspec = "0:0"; + + Core::ArgsParser args_parser; + args_parser.set_general_help( + "Run a program in a chroot sandbox. During execution, the program " + "sees the given path as '/', and cannot access files outside of it."); + args_parser.add_positional_argument(path, "New root directory", "path"); + args_parser.add_positional_argument(program, "Program to run", "program", Core::ArgsParser::Required::No); + + Core::ArgsParser::Option userspec_option { + true, + "The uid:gid to use", + "userspec", + 'u', + "userpec", + [&userspec](const char* s) { + Vector parts = StringView(s).split_view(':', true); + if (parts.size() != 2) + return false; + userspec = s; + return true; + } + }; + args_parser.add_option(move(userspec_option)); + + Core::ArgsParser::Option mount_options { + true, + "Mount options", + "options", + 'o', + "options", + [&flags](const char* s) { + flags = 0; + Vector parts = StringView(s).split_view(','); + for (auto& part : parts) { + if (part == "defaults") + continue; + else if (part == "nodev") + flags |= MS_NODEV; + else if (part == "noexec") + flags |= MS_NOEXEC; + else if (part == "nosuid") + flags |= MS_NOSUID; + else if (part == "ro") + flags |= MS_RDONLY; + else if (part == "remount") + flags |= MS_REMOUNT; + else if (part == "bind") + fprintf(stderr, "Ignoring -o bind, as it doesn't make sense for chroot\n"); + else + return false; + } + return true; + } + }; + args_parser.add_option(move(mount_options)); + args_parser.parse(argc, argv); + + if (chroot_with_mount_flags(path, flags) < 0) { + perror("chroot"); + return 1; + } + + if (chdir("/") < 0) { + perror("chdir(/)"); + return 1; + } + + // Failed parsing will silently fail open (uid=0; gid=0); + // 0:0 is also the default when no --userspec argument is provided. + auto parts = String(userspec).split(':', true); + chroot_user = (uid_t)strtol(parts[0].characters(), nullptr, 10); + chroot_group = (uid_t)strtol(parts[1].characters(), nullptr, 10); + + if (setresgid(chroot_group, chroot_group, chroot_group)) { + perror("setgid"); + return 1; + } + + if (setresuid(chroot_user, chroot_user, chroot_user)) { + perror("setuid"); + return 1; + } + + execl(program, program, nullptr); + perror("execl"); + return 1; +} diff --git a/Userland/Utilities/clear.cpp b/Userland/Utilities/clear.cpp new file mode 100644 index 0000000000..fd3f0506a4 --- /dev/null +++ b/Userland/Utilities/clear.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +int main(int, char**) +{ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + printf("\033[3J\033[H\033[2J"); + fflush(stdout); + return 0; +} diff --git a/Userland/Utilities/copy.cpp b/Userland/Utilities/copy.cpp new file mode 100644 index 0000000000..90d2e04430 --- /dev/null +++ b/Userland/Utilities/copy.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Options { + String data; + StringView type; +}; + +static Options parse_options(int argc, char* argv[]) +{ + const char* type = "text/plain"; + Vector text; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Copy text from stdin or the command-line to the clipboard."); + args_parser.add_option(type, "Pick a type", "type", 't', "type"); + args_parser.add_positional_argument(text, "Text to copy", "text", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + Options options; + options.type = type; + + if (text.is_empty()) { + // Copy our stdin. + auto c_stdin = Core::File::construct(); + bool success = c_stdin->open( + STDIN_FILENO, + Core::IODevice::OpenMode::ReadOnly, + Core::File::ShouldCloseFileDescriptor::No); + ASSERT(success); + auto buffer = c_stdin->read_all(); + dbgln("Read size {}", buffer.size()); + options.data = String((char*)buffer.data(), buffer.size()); + } else { + // Copy the rest of our command-line args. + StringBuilder builder; + builder.join(' ', text); + options.data = builder.to_string(); + } + + return options; +} + +int main(int argc, char* argv[]) +{ + auto app = GUI::Application::construct(argc, argv); + + Options options = parse_options(argc, argv); + + auto& clipboard = GUI::Clipboard::the(); + clipboard.set_data(options.data.bytes(), options.type); + + return 0; +} diff --git a/Userland/Utilities/cp.cpp b/Userland/Utilities/cp.cpp new file mode 100644 index 0000000000..ee640f6f3d --- /dev/null +++ b/Userland/Utilities/cp.cpp @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool copy_file_or_directory(String, String, bool, bool); +bool copy_file(String, String, const struct stat&, int); +bool copy_directory(String, String, bool); + +static mode_t my_umask = 0; + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath wpath cpath fattr", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool link = false; + bool recursion_allowed = false; + bool verbose = false; + Vector sources; + const char* destination = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(link, "Link files instead of copying", "link", 'l'); + args_parser.add_option(recursion_allowed, "Copy directories recursively", "recursive", 'R'); + args_parser.add_option(recursion_allowed, "Same as -R", nullptr, 'r'); + args_parser.add_option(verbose, "Verbose", "verbose", 'v'); + args_parser.add_positional_argument(sources, "Source file path", "source"); + args_parser.add_positional_argument(destination, "Destination file path", "destination"); + args_parser.parse(argc, argv); + + auto my_umask = umask(0); + umask(my_umask); + + for (auto& source : sources) { + bool ok = copy_file_or_directory(source, destination, recursion_allowed, link); + if (!ok) + return 1; + if (verbose) + printf("'%s' -> '%s'\n", source, destination); + } + return 0; +} + +/** + * Copy a file or directory to a new location. Returns true if successful, false + * otherwise. If there is an error, its description is output to stderr. + * + * Directories should only be copied if recursion_allowed is set. + */ +bool copy_file_or_directory(String src_path, String dst_path, bool recursion_allowed, bool link) +{ + int src_fd = open(src_path.characters(), O_RDONLY); + if (src_fd < 0) { + perror("open src"); + return false; + } + + struct stat src_stat; + int rc = fstat(src_fd, &src_stat); + if (rc < 0) { + perror("stat src"); + return false; + } + + if (S_ISDIR(src_stat.st_mode)) { + if (!recursion_allowed) { + fprintf(stderr, "cp: -R not specified; omitting directory '%s'\n", src_path.characters()); + return false; + } + return copy_directory(src_path, dst_path, link); + } + if (link) { + if (::link(src_path.characters(), dst_path.characters()) < 0) { + perror("link"); + return false; + } + return true; + } + + return copy_file(src_path, dst_path, src_stat, src_fd); +} + +/** + * Copy a source file to a destination file. Returns true if successful, false + * otherwise. If there is an error, its description is output to stderr. + * + * To avoid repeated work, the source file's stat and file descriptor are required. + */ +bool copy_file(String src_path, String dst_path, const struct stat& src_stat, int src_fd) +{ + // NOTE: We don't copy the set-uid and set-gid bits. + mode_t mode = (src_stat.st_mode & ~my_umask) & ~06000; + + int dst_fd = creat(dst_path.characters(), mode); + if (dst_fd < 0) { + if (errno != EISDIR) { + perror("open dst"); + return false; + } + StringBuilder builder; + builder.append(dst_path); + builder.append('/'); + builder.append(LexicalPath(src_path).basename()); + dst_path = builder.to_string(); + dst_fd = creat(dst_path.characters(), 0666); + if (dst_fd < 0) { + perror("open dst"); + return false; + } + } + + if (src_stat.st_size > 0) { + if (ftruncate(dst_fd, src_stat.st_size) < 0) { + perror("cp: ftruncate"); + return false; + } + } + + for (;;) { + char buffer[32768]; + ssize_t nread = read(src_fd, buffer, sizeof(buffer)); + if (nread < 0) { + perror("read src"); + return false; + } + if (nread == 0) + break; + ssize_t remaining_to_write = nread; + char* bufptr = buffer; + while (remaining_to_write) { + ssize_t nwritten = write(dst_fd, bufptr, remaining_to_write); + if (nwritten < 0) { + perror("write dst"); + return false; + } + assert(nwritten > 0); + remaining_to_write -= nwritten; + bufptr += nwritten; + } + } + + close(src_fd); + close(dst_fd); + return true; +} + +/** + * Copy the contents of a source directory into a destination directory. + */ +bool copy_directory(String src_path, String dst_path, bool link) +{ + int rc = mkdir(dst_path.characters(), 0755); + if (rc < 0) { + perror("cp: mkdir"); + return false; + } + + String src_rp = Core::File::real_path_for(src_path); + src_rp = String::format("%s/", src_rp.characters()); + String dst_rp = Core::File::real_path_for(dst_path); + dst_rp = String::format("%s/", dst_rp.characters()); + + if (!dst_rp.is_empty() && dst_rp.starts_with(src_rp)) { + fprintf(stderr, "cp: Cannot copy %s into itself (%s)\n", + src_path.characters(), dst_path.characters()); + return false; + } + + Core::DirIterator di(src_path, Core::DirIterator::SkipDots); + if (di.has_error()) { + fprintf(stderr, "cp: DirIterator: %s\n", di.error_string()); + return false; + } + while (di.has_next()) { + String filename = di.next_path(); + bool is_copied = copy_file_or_directory( + String::format("%s/%s", src_path.characters(), filename.characters()), + String::format("%s/%s", dst_path.characters(), filename.characters()), + true, link); + if (!is_copied) { + return false; + } + } + return true; +} diff --git a/Userland/Utilities/crash.cpp b/Userland/Utilities/crash.cpp new file mode 100644 index 0000000000..5f4c763a4f --- /dev/null +++ b/Userland/Utilities/crash.cpp @@ -0,0 +1,329 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2019-2020, Shannon Booth + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma GCC optimize("O0") + +class Crash { +public: + enum class RunType { + UsingChildProcess, + UsingCurrentProcess, + }; + + enum class Failure { + DidNotCrash, + UnexpectedError, + }; + + Crash(String test_type, Function crash_function) + : m_type(test_type) + , m_crash_function(move(crash_function)) + { + } + + void run(RunType run_type) + { + printf("\x1B[33mTesting\x1B[0m: \"%s\"\n", m_type.characters()); + + auto run_crash_and_print_if_error = [this]() { + auto failure = m_crash_function(); + + // If we got here something went wrong + printf("\x1B[31mFAIL\x1B[0m: "); + switch (failure) { + case Failure::DidNotCrash: + printf("Did not crash!\n"); + break; + case Failure::UnexpectedError: + printf("Unexpected error!\n"); + break; + default: + ASSERT_NOT_REACHED(); + } + }; + + if (run_type == RunType::UsingCurrentProcess) { + run_crash_and_print_if_error(); + } else { + + // Run the test in a child process so that we do not crash the crash program :^) + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + ASSERT_NOT_REACHED(); + } else if (pid == 0) { + run_crash_and_print_if_error(); + exit(0); + } + + int status; + waitpid(pid, &status, 0); + if (WIFSIGNALED(status)) + printf("\x1B[32mPASS\x1B[0m: Terminated with signal %d\n", WTERMSIG(status)); + } + } + +private: + String m_type; + Function m_crash_function; +}; + +int main(int argc, char** argv) +{ + bool do_all_crash_types = false; + bool do_segmentation_violation = false; + bool do_division_by_zero = false; + bool do_illegal_instruction = false; + bool do_abort = false; + bool do_write_to_uninitialized_malloc_memory = false; + bool do_write_to_freed_memory = false; + bool do_write_to_read_only_memory = false; + bool do_read_from_uninitialized_malloc_memory = false; + bool do_read_from_freed_memory = false; + bool do_invalid_stack_pointer_on_syscall = false; + bool do_invalid_stack_pointer_on_page_fault = false; + bool do_syscall_from_writeable_memory = false; + bool do_execute_non_executable_memory = false; + bool do_trigger_user_mode_instruction_prevention = false; + bool do_use_io_instruction = false; + bool do_read_cpu_counter = false; + + auto args_parser = Core::ArgsParser(); + args_parser.set_general_help( + "Exercise error-handling paths of the execution environment " + "(i.e., Kernel or UE) by crashing in many different ways."); + args_parser.add_option(do_all_crash_types, "Test that all of the following crash types crash as expected", nullptr, 'A'); + args_parser.add_option(do_segmentation_violation, "Perform a segmentation violation by dereferencing an invalid pointer", nullptr, 's'); + args_parser.add_option(do_division_by_zero, "Perform a division by zero", nullptr, 'd'); + args_parser.add_option(do_illegal_instruction, "Execute an illegal CPU instruction", nullptr, 'i'); + args_parser.add_option(do_abort, "Call `abort()`", nullptr, 'a'); + args_parser.add_option(do_read_from_uninitialized_malloc_memory, "Read a pointer from uninitialized malloc memory, then read from it", nullptr, 'm'); + args_parser.add_option(do_read_from_freed_memory, "Read a pointer from memory freed using `free()`, then read from it", nullptr, 'f'); + args_parser.add_option(do_write_to_uninitialized_malloc_memory, "Read a pointer from uninitialized malloc memory, then write to it", nullptr, 'M'); + args_parser.add_option(do_write_to_freed_memory, "Read a pointer from memory freed using `free()`, then write to it", nullptr, 'F'); + args_parser.add_option(do_write_to_read_only_memory, "Write to read-only memory", nullptr, 'r'); + args_parser.add_option(do_invalid_stack_pointer_on_syscall, "Make a syscall while using an invalid stack pointer", nullptr, 'T'); + args_parser.add_option(do_invalid_stack_pointer_on_page_fault, "Trigger a page fault while using an invalid stack pointer", nullptr, 't'); + args_parser.add_option(do_syscall_from_writeable_memory, "Make a syscall from writeable memory", nullptr, 'S'); + args_parser.add_option(do_execute_non_executable_memory, "Attempt to execute non-executable memory (not mapped with PROT_EXEC)", nullptr, 'X'); + args_parser.add_option(do_trigger_user_mode_instruction_prevention, "Attempt to trigger an x86 User Mode Instruction Prevention fault", nullptr, 'U'); + args_parser.add_option(do_use_io_instruction, "Use an x86 I/O instruction in userspace", nullptr, 'I'); + args_parser.add_option(do_read_cpu_counter, "Read the x86 TSC (Time Stamp Counter) directly", nullptr, 'c'); + + if (argc != 2) { + args_parser.print_usage(stderr, argv[0]); + exit(1); + } + + args_parser.parse(argc, argv); + + Crash::RunType run_type = do_all_crash_types ? Crash::RunType::UsingChildProcess + : Crash::RunType::UsingCurrentProcess; + + if (do_segmentation_violation || do_all_crash_types) { + Crash("Segmentation violation", []() { + volatile int* crashme = nullptr; + *crashme = 0xbeef; + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_division_by_zero || do_all_crash_types) { + Crash("Division by zero", []() { + volatile int lala = 10; + volatile int zero = 0; + [[maybe_unused]] volatile int test = lala / zero; + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_illegal_instruction || do_all_crash_types) { + Crash("Illegal instruction", []() { + asm volatile("ud2"); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_abort || do_all_crash_types) { + Crash("Abort", []() { + abort(); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_read_from_uninitialized_malloc_memory || do_all_crash_types) { + Crash("Read from uninitialized malloc memory", []() { + auto* uninitialized_memory = (volatile u32**)malloc(1024); + if (!uninitialized_memory) + return Crash::Failure::UnexpectedError; + + [[maybe_unused]] volatile auto x = uninitialized_memory[0][0]; + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_read_from_uninitialized_malloc_memory || do_all_crash_types) { + Crash("Read from freed memory", []() { + auto* uninitialized_memory = (volatile u32**)malloc(1024); + if (!uninitialized_memory) + return Crash::Failure::UnexpectedError; + + free(uninitialized_memory); + [[maybe_unused]] volatile auto x = uninitialized_memory[4][0]; + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_write_to_uninitialized_malloc_memory || do_all_crash_types) { + Crash("Write to uninitialized malloc memory", []() { + auto* uninitialized_memory = (volatile u32**)malloc(1024); + if (!uninitialized_memory) + return Crash::Failure::UnexpectedError; + + uninitialized_memory[4][0] = 1; + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_write_to_freed_memory || do_all_crash_types) { + Crash("Write to freed memory", []() { + auto* uninitialized_memory = (volatile u32**)malloc(1024); + if (!uninitialized_memory) + return Crash::Failure::UnexpectedError; + + free(uninitialized_memory); + uninitialized_memory[4][0] = 1; + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_write_to_read_only_memory || do_all_crash_types) { + Crash("Write to read only memory", []() { + auto* ptr = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0); + if (ptr != MAP_FAILED) + return Crash::Failure::UnexpectedError; + + *ptr = 'x'; // This should work fine. + int rc = mprotect(ptr, 4096, PROT_READ); + if (rc != 0 || *ptr != 'x') + return Crash::Failure::UnexpectedError; + + *ptr = 'y'; // This should crash! + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_invalid_stack_pointer_on_syscall || do_all_crash_types) { + Crash("Invalid stack pointer on syscall", []() { + u8* makeshift_stack = (u8*)mmap(nullptr, 0, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, 0, 0); + if (!makeshift_stack) + return Crash::Failure::UnexpectedError; + + u8* makeshift_esp = makeshift_stack + 2048; + asm volatile("mov %%eax, %%esp" ::"a"(makeshift_esp)); + getuid(); + dbgln("Survived syscall with MAP_STACK stack"); + + u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (!bad_stack) + return Crash::Failure::UnexpectedError; + + u8* bad_esp = bad_stack + 2048; + asm volatile("mov %%eax, %%esp" ::"a"(bad_esp)); + getuid(); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_invalid_stack_pointer_on_page_fault || do_all_crash_types) { + Crash("Invalid stack pointer on page fault", []() { + u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (!bad_stack) + return Crash::Failure::UnexpectedError; + + u8* bad_esp = bad_stack + 2048; + asm volatile("mov %%eax, %%esp" ::"a"(bad_esp)); + asm volatile("pushl $0"); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_syscall_from_writeable_memory || do_all_crash_types) { + Crash("Syscall from writable memory", []() { + u8 buffer[] = { 0xb8, Syscall::SC_getuid, 0, 0, 0, 0xcd, 0x82 }; + ((void (*)())buffer)(); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_execute_non_executable_memory || do_all_crash_types) { + Crash("Execute non executable memory", []() { + auto* ptr = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + if (ptr == MAP_FAILED) + return Crash::Failure::UnexpectedError; + + ptr[0] = 0xc3; // ret + typedef void* (*CrashyFunctionPtr)(); + ((CrashyFunctionPtr)ptr)(); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_trigger_user_mode_instruction_prevention || do_all_crash_types) { + Crash("Trigger x86 User Mode Instruction Prevention", []() { + asm volatile("str %eax"); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_use_io_instruction || do_all_crash_types) { + Crash("Attempt to use an I/O instruction", [] { + u8 keyboard_status = IO::in8(0x64); + printf("Keyboard status: %#02x\n", keyboard_status); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + if (do_read_cpu_counter || do_all_crash_types) { + Crash("Read the CPU timestamp counter", [] { + asm volatile("rdtsc"); + return Crash::Failure::DidNotCrash; + }).run(run_type); + } + + return 0; +} diff --git a/Userland/Utilities/cut.cpp b/Userland/Utilities/cut.cpp new file mode 100644 index 0000000000..f3eee67e1c --- /dev/null +++ b/Userland/Utilities/cut.cpp @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2019-2020, Marios Prokopakis + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +struct Index { + enum class Type { + SingleIndex, + SliceIndex, + RangedIndex + }; + ssize_t m_from { -1 }; + ssize_t m_to { -1 }; + Type m_type { Type::SingleIndex }; + + bool intersects(const Index& other) + { + if (m_type != Type::RangedIndex) + return m_from == other.m_from; + + return !(other.m_from > m_to || other.m_to < m_from); + } +}; + +static void print_usage_and_exit(int ret) +{ + printf("Usage: cut -b list [File]\n"); + exit(ret); +} + +static void add_if_not_exists(Vector& indexes, Index data) +{ + bool append_to_vector = true; + for (auto& index : indexes) { + if (index.intersects(data)) { + if (index.m_type == Index::Type::RangedIndex) { + index.m_from = AK::min(index.m_from, data.m_from); + index.m_to = AK::max(index.m_to, data.m_to); + } + append_to_vector = false; + } + } + + if (append_to_vector) { + indexes.append(data); + } +} + +static void expand_list(Vector& tokens, Vector& indexes) +{ + for (auto& token : tokens) { + if (token.length() == 0) { + fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); + print_usage_and_exit(1); + } + + if (token == "-") { + fprintf(stderr, "cut: invalid range with no endpoint: %s\n", token.characters()); + print_usage_and_exit(1); + } + + if (token[0] == '-') { + auto index = token.substring(1, token.length() - 1).to_int(); + if (!index.has_value()) { + fprintf(stderr, "cut: invalid byte/character position '%s'\n", token.characters()); + print_usage_and_exit(1); + } + + if (index.value() == 0) { + fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); + print_usage_and_exit(1); + } + + Index tmp = { 1, index.value(), Index::Type::RangedIndex }; + add_if_not_exists(indexes, tmp); + } else if (token[token.length() - 1] == '-') { + auto index = token.substring(0, token.length() - 1).to_int(); + if (!index.has_value()) { + fprintf(stderr, "cut: invalid byte/character position '%s'\n", token.characters()); + print_usage_and_exit(1); + } + + if (index.value() == 0) { + fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); + print_usage_and_exit(1); + } + Index tmp = { index.value(), -1, Index::Type::SliceIndex }; + add_if_not_exists(indexes, tmp); + } else { + auto range = token.split('-'); + if (range.size() == 2) { + auto index1 = range[0].to_int(); + if (!index1.has_value()) { + fprintf(stderr, "cut: invalid byte/character position '%s'\n", range[0].characters()); + print_usage_and_exit(1); + } + + auto index2 = range[1].to_int(); + if (!index2.has_value()) { + fprintf(stderr, "cut: invalid byte/character position '%s'\n", range[1].characters()); + print_usage_and_exit(1); + } + + if (index1.value() > index2.value()) { + fprintf(stderr, "cut: invalid decreasing range\n"); + print_usage_and_exit(1); + } else if (index1.value() == 0 || index2.value() == 0) { + fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); + print_usage_and_exit(1); + } + + Index tmp = { index1.value(), index2.value(), Index::Type::RangedIndex }; + add_if_not_exists(indexes, tmp); + } else if (range.size() == 1) { + auto index = range[0].to_int(); + if (!index.has_value()) { + fprintf(stderr, "cut: invalid byte/character position '%s'\n", range[0].characters()); + print_usage_and_exit(1); + } + + if (index.value() == 0) { + fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); + print_usage_and_exit(1); + } + + Index tmp = { index.value(), index.value(), Index::Type::SingleIndex }; + add_if_not_exists(indexes, tmp); + } else { + fprintf(stderr, "cut: invalid byte or character range\n"); + print_usage_and_exit(1); + } + } + } +} + +static void cut_file(const String& file, const Vector& byte_vector) +{ + FILE* fp = stdin; + if (!file.is_null()) { + fp = fopen(file.characters(), "r"); + if (!fp) { + fprintf(stderr, "cut: Could not open file '%s'\n", file.characters()); + return; + } + } + + char* line = nullptr; + ssize_t line_length = 0; + size_t line_capacity = 0; + while ((line_length = getline(&line, &line_capacity, fp)) != -1) { + line[line_length - 1] = '\0'; + line_length--; + for (auto& i : byte_vector) { + if (i.m_type == Index::Type::SliceIndex && i.m_from < line_length) + printf("%s", line + i.m_from - 1); + else if (i.m_type == Index::Type::SingleIndex && i.m_from <= line_length) + printf("%c", line[i.m_from - 1]); + else if (i.m_type == Index::Type::RangedIndex && i.m_from <= line_length) { + auto to = i.m_to > line_length ? line_length : i.m_to; + auto sub_string = String(line).substring(i.m_from - 1, to - i.m_from + 1); + printf("%s", sub_string.characters()); + } else + break; + } + printf("\n"); + } + + if (line) + free(line); + + if (!file.is_null()) + fclose(fp); +} + +int main(int argc, char** argv) +{ + String byte_list = ""; + Vector tokens; + Vector files; + if (argc == 1) { + print_usage_and_exit(1); + } + + for (int i = 1; i < argc;) { + if (!strcmp(argv[i], "-b")) { + /* The next argument should be a list of bytes. */ + byte_list = (i + 1 < argc) ? argv[i + 1] : ""; + + if (byte_list == "") { + print_usage_and_exit(1); + } + tokens = byte_list.split(','); + i += 2; + } else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { + print_usage_and_exit(1); + } else if (argv[i][0] != '-') { + files.append(argv[i++]); + } else { + fprintf(stderr, "cut: invalid argument %s\n", argv[i]); + print_usage_and_exit(1); + } + } + + if (byte_list == "") + print_usage_and_exit(1); + + Vector byte_vector; + expand_list(tokens, byte_vector); + quick_sort(byte_vector, [](auto& a, auto& b) { return a.m_from < b.m_from; }); + + if (files.is_empty()) + files.append(String()); + + /* Process each file */ + for (auto& file : files) + cut_file(file, byte_vector); + + return 0; +} diff --git a/Userland/Utilities/date.cpp b/Userland/Utilities/date.cpp new file mode 100644 index 0000000000..057112a4c3 --- /dev/null +++ b/Userland/Utilities/date.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio settime", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool print_unix_date = false; + bool print_iso_8601 = false; + bool print_rfc_3339 = false; + bool print_rfc_5322 = false; + const char* set_date = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(set_date, "Set system date and time", "set", 's', "date"); + args_parser.add_option(print_unix_date, "Print date as Unix timestamp", "unix", 'u'); + args_parser.add_option(print_iso_8601, "Print date in ISO 8601 format", "iso-8601", 'i'); + args_parser.add_option(print_rfc_3339, "Print date in RFC 3339 format", "rfc-3339", 'r'); + args_parser.add_option(print_rfc_5322, "Print date in RFC 5322 format", "rfc-5322", 'R'); + args_parser.parse(argc, argv); + + if (set_date != nullptr) { + auto number = String(set_date).to_uint(); + + if (!number.has_value()) { + fprintf(stderr, "date: Invalid timestamp value"); + return 1; + } + + timespec ts = { number.value(), 0 }; + if (clock_settime(CLOCK_REALTIME, &ts) < 0) { + perror("clock_settime"); + return 1; + } + + return 0; + } + + // FIXME: this should be improved and will need to be cleaned up + // when additional output formats and formatting is supported + if (print_unix_date && print_iso_8601 && print_rfc_3339 && print_rfc_5322) { + fprintf(stderr, "date: multiple output formats specified\n"); + return 1; + } + + time_t now = time(nullptr); + auto date = Core::DateTime::from_timestamp(now); + + if (print_unix_date) { + printf("%lld\n", (long long)now); + return 0; + } else if (print_iso_8601) { + printf("%s\n", date.to_string("%Y-%m-%dT%H:%M:%S-00:00").characters()); + return 0; + } else if (print_rfc_5322) { + printf("%s\n", date.to_string("%a, %d %b %Y %H:%M:%S -0000").characters()); + return 0; + } else if (print_rfc_3339) { + printf("%s\n", date.to_string("%Y-%m-%d %H:%M:%S-00:00").characters()); + return 0; + } else { + printf("%s\n", date.to_string().characters()); + return 0; + } +} diff --git a/Userland/Utilities/ddate.cpp b/Userland/Utilities/ddate.cpp new file mode 100644 index 0000000000..763a5a3493 --- /dev/null +++ b/Userland/Utilities/ddate.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +class DiscordianDate { +public: + explicit DiscordianDate(Core::DateTime date) + { + m_gregorian_date = date; + m_yold = date.year() + 1166; + + uint16_t day = day_of_yold() + 1; + if (is_leap_year() && day > m_st_tibs_day_of_yold) + --day; + + m_day_of_week = day_of_week_from_day_of_yold(day); + m_season = season_from_day_of_yold(day); + m_date = date_from_day_of_yold(day); + } + + const char* day_of_week() { return m_day_of_week.characters(); }; + const char* season() { return m_season.characters(); }; + uint16_t year() { return yold(); }; + uint16_t yold() { return m_yold; }; + uint16_t day_of_year() { return day_of_yold(); }; + uint16_t day_of_yold() { return m_gregorian_date.day_of_year(); }; + bool is_leap_year() { return m_gregorian_date.is_leap_year(); }; + bool is_st_tibs_day() { return (is_leap_year() && (day_of_yold() + 1) == m_st_tibs_day_of_yold); }; + + String to_string() + { + if (is_st_tibs_day()) + return String::formatted("St. Tib's Day, in the YOLD {}", m_yold); + return String::formatted("{}, day {} of {}, in the YOLD {}", m_day_of_week, m_date, m_season, m_yold); + } + +private: + static const int m_days_in_week = 5; + static const int m_days_in_season = 73; + static const int m_st_tibs_day_of_yold = 60; + Core::DateTime m_gregorian_date; + String m_day_of_week; + String m_season; + int m_date; + uint64_t m_yold; // 64-bit for use after X-Day in the YOLD 8661 + + int date_from_day_of_yold(uint16_t day) + { + return (day % m_days_in_season == 0 ? m_days_in_season : day % m_days_in_season); + } + + const char* day_of_week_from_day_of_yold(uint16_t day) + { + if (is_st_tibs_day()) + return nullptr; + + switch ((day % m_days_in_week) == 0 ? m_days_in_week : (day % m_days_in_week)) { + case 1: + return "Sweetmorn"; + case 2: + return "Boomtime"; + case 3: + return "Pungenday"; + case 4: + return "Prickle-Prickle"; + case 5: + return "Setting Orange"; + default: + return nullptr; + } + } + + const char* season_from_day_of_yold(uint16_t day) + { + if (is_st_tibs_day()) + return nullptr; + + switch (((day % m_days_in_season) == 0 ? day - 1 : day) / m_days_in_season) { + case 0: + return "Chaos"; + case 1: + return "Discord"; + case 2: + return "Confusion"; + case 3: + return "Bureaucracy"; + case 4: + return "The Aftermath"; + default: + return nullptr; + } + } +}; + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto date = Core::DateTime::from_timestamp(time(nullptr)); + printf("Today is %s\n", DiscordianDate(date).to_string().characters()); + + return 0; +} diff --git a/Userland/Utilities/df.cpp b/Userland/Utilities/df.cpp new file mode 100644 index 0000000000..53a79eb7aa --- /dev/null +++ b/Userland/Utilities/df.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool flag_human_readable = false; + +struct FileSystem { + String fs; + size_t total_block_count { 0 }; + size_t free_block_count { 0 }; + size_t total_inode_count { 0 }; + size_t free_inode_count { 0 }; + size_t block_size { 0 }; + String mount_point; +}; + +int main(int argc, char** argv) +{ + Core::ArgsParser args_parser; + args_parser.set_general_help("Display free disk space of each partition."); + args_parser.add_option(flag_human_readable, "Print human-readable sizes", "human-readable", 'h'); + args_parser.parse(argc, argv); + + auto file = Core::File::construct("/proc/df"); + if (!file->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Failed to open /proc/df: %s\n", file->error_string()); + return 1; + } + + if (flag_human_readable) { + printf("Filesystem Size Used Available Mount point\n"); + } else { + printf("Filesystem Blocks Used Available Mount point\n"); + } + + auto file_contents = file->read_all(); + auto json_result = JsonValue::from_string(file_contents); + ASSERT(json_result.has_value()); + auto json = json_result.value().as_array(); + json.for_each([](auto& value) { + auto fs_object = value.as_object(); + auto fs = fs_object.get("class_name").to_string(); + auto total_block_count = fs_object.get("total_block_count").to_u32(); + auto free_block_count = fs_object.get("free_block_count").to_u32(); + [[maybe_unused]] auto total_inode_count = fs_object.get("total_inode_count").to_u32(); + [[maybe_unused]] auto free_inode_count = fs_object.get("free_inode_count").to_u32(); + auto block_size = fs_object.get("block_size").to_u32(); + auto mount_point = fs_object.get("mount_point").to_string(); + + printf("%-10s", fs.characters()); + + if (flag_human_readable) { + printf("%10s ", human_readable_size(total_block_count * block_size).characters()); + printf("%10s ", human_readable_size((total_block_count - free_block_count) * block_size).characters()); + printf("%10s ", human_readable_size(free_block_count * block_size).characters()); + } else { + printf("%10u ", total_block_count); + printf("%10u ", total_block_count - free_block_count); + printf("%10u ", free_block_count); + } + + printf("%s", mount_point.characters()); + printf("\n"); + }); + + return 0; +} diff --git a/Userland/Utilities/dirname.cpp b/Userland/Utilities/dirname.cpp new file mode 100644 index 0000000000..2d13c9eb15 --- /dev/null +++ b/Userland/Utilities/dirname.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +int main(int argc, char** argv) +{ + const char* path = nullptr; + Core::ArgsParser args_parser; + args_parser.add_positional_argument(path, "Path", "path"); + args_parser.parse(argc, argv); + + outln("{}", LexicalPath(path).dirname()); + return 0; +} diff --git a/Userland/Utilities/disasm.cpp b/Userland/Utilities/disasm.cpp new file mode 100644 index 0000000000..781caf0796 --- /dev/null +++ b/Userland/Utilities/disasm.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DISASM_DUMP + +int main(int argc, char** argv) +{ + const char* path = nullptr; + + Core::ArgsParser args_parser; + args_parser.set_general_help( + "Disassemble an executable, and show human-readable " + "assembly code for each function."); + args_parser.add_positional_argument(path, "Path to i386 binary file", "path"); + args_parser.parse(argc, argv); + + auto file_or_error = MappedFile::map(path); + if (file_or_error.is_error()) { + warnln("Could not map file: {}", file_or_error.error().string()); + return 1; + } + + auto& file = *file_or_error.value(); + + struct Symbol { + size_t value; + size_t size; + StringView name; + + size_t address() const { return value; } + size_t address_end() const { return value + size; } + + bool contains(size_t virtual_address) { return address() <= virtual_address && virtual_address < address_end(); } + }; + Vector symbols; + + const u8* asm_data = (const u8*)file.data(); + size_t asm_size = file.size(); + size_t file_offset = 0; + Vector::Iterator current_symbol = symbols.begin(); + OwnPtr symbol_provider; // nullptr for non-ELF disassembly. + OwnPtr elf; + if (asm_size >= 4 && strncmp((const char*)asm_data, "\u007fELF", 4) == 0) { + elf = make(asm_data, asm_size); + if (elf->is_valid()) { + symbol_provider = make(*elf); + elf->for_each_section_of_type(SHT_PROGBITS, [&](const ELF::Image::Section& section) { + // FIXME: Disassemble all SHT_PROGBITS sections, not just .text. + if (section.name() != ".text") + return IterationDecision::Continue; + asm_data = (const u8*)section.raw_data(); + asm_size = section.size(); + file_offset = section.address(); + return IterationDecision::Break; + }); + symbols.ensure_capacity(elf->symbol_count() + 1); + symbols.append({ 0, 0, StringView() }); // Sentinel. + elf->for_each_symbol([&](const ELF::Image::Symbol& symbol) { + symbols.append({ symbol.value(), symbol.size(), symbol.name() }); + return IterationDecision::Continue; + }); + quick_sort(symbols, [](auto& a, auto& b) { + if (a.value != b.value) + return a.value < b.value; + if (a.size != b.size) + return a.size < b.size; + return a.name < b.name; + }); +#ifdef DISASM_DUMP + for (size_t i = 0; i < symbols.size(); ++i) + dbg() << symbols[i].name << ": " << (void*)(uintptr_t)symbols[i].value << ", " << symbols[i].size; +#endif + } + } + + X86::SimpleInstructionStream stream(asm_data, asm_size); + X86::Disassembler disassembler(stream); + + bool is_first_symbol = true; + bool current_instruction_is_in_symbol = false; + + for (;;) { + auto offset = stream.offset(); + auto insn = disassembler.next(); + if (!insn.has_value()) + break; + + // Prefix regions of instructions belonging to a symbol with the symbol's name. + // Separate regions of instructions belonging to distinct symbols with newlines, + // and separate regions of instructions not belonging to symbols from regions belonging to symbols with newlines. + // Interesting cases: + // - More than 1 symbol covering a region of instructions (ICF, D1/D2) + // - Symbols of size 0 that don't cover any instructions but are at an address (want to print them, separated from instructions both before and after) + // Invariant: current_symbol is the largest instruction containing insn, or it is the largest instruction that has an address less than the instruction's address. + size_t virtual_offset = file_offset + offset; + if (current_symbol < symbols.end() && !current_symbol->contains(virtual_offset)) { + if (!is_first_symbol && current_instruction_is_in_symbol) { + // The previous instruction was part of a symbol that doesn't cover the current instruction, so separate it from the current instruction with a newline. + outln(); + current_instruction_is_in_symbol = (current_symbol + 1 < symbols.end() && (current_symbol + 1)->contains(virtual_offset)); + } + + // Try to find symbol covering current instruction, if one exists. + while (current_symbol + 1 < symbols.end() && !(current_symbol + 1)->contains(virtual_offset) && (current_symbol + 1)->address() <= virtual_offset) { + ++current_symbol; + if (!is_first_symbol) + outln("\n({} ({:p}-{:p}))\n", current_symbol->name, current_symbol->address(), current_symbol->address_end()); + } + while (current_symbol + 1 < symbols.end() && (current_symbol + 1)->contains(virtual_offset)) { + if (!is_first_symbol && !current_instruction_is_in_symbol) + outln(); + ++current_symbol; + current_instruction_is_in_symbol = true; + outln("{} ({:p}-{:p}):", current_symbol->name, current_symbol->address(), current_symbol->address_end()); + } + + is_first_symbol = false; + } + + outln("{:p} {}", virtual_offset, insn.value().to_string(virtual_offset, symbol_provider)); + } +} diff --git a/Userland/Utilities/disk_benchmark.cpp b/Userland/Utilities/disk_benchmark.cpp new file mode 100644 index 0000000000..de3ff19849 --- /dev/null +++ b/Userland/Utilities/disk_benchmark.cpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Result { + u64 write_bps; + u64 read_bps; +}; + +static Result average_result(const Vector& results) +{ + Result average; + + for (auto& res : results) { + average.write_bps += res.write_bps; + average.read_bps += res.read_bps; + } + + average.write_bps /= results.size(); + average.read_bps /= results.size(); + + return average; +} + +static void exit_with_usage(int rc) +{ + fprintf(stderr, "Usage: disk_benchmark [-h] [-d directory] [-t time_per_benchmark] [-f file_size1,file_size2,...] [-b block_size1,block_size2,...]\n"); + exit(rc); +} + +static Result benchmark(const String& filename, int file_size, int block_size, ByteBuffer& buffer, bool allow_cache); + +int main(int argc, char** argv) +{ + char* directory = strdup("."); + int time_per_benchmark = 10; + Vector file_sizes; + Vector block_sizes; + bool allow_cache = false; + + int opt; + while ((opt = getopt(argc, argv, "chd:t:f:b:")) != -1) { + switch (opt) { + case 'h': + exit_with_usage(0); + break; + case 'c': + allow_cache = true; + break; + case 'd': + directory = strdup(optarg); + break; + case 't': + time_per_benchmark = atoi(optarg); + break; + case 'f': + for (auto size : String(optarg).split(',')) + file_sizes.append(atoi(size.characters())); + break; + case 'b': + for (auto size : String(optarg).split(',')) + block_sizes.append(atoi(size.characters())); + break; + } + } + + if (file_sizes.size() == 0) { + file_sizes = { 131072, 262144, 524288, 1048576, 5242880 }; + } + if (block_sizes.size() == 0) { + block_sizes = { 8192, 32768, 65536 }; + } + + umask(0644); + + auto filename = String::format("%s/disk_benchmark.tmp", directory); + + for (auto file_size : file_sizes) { + for (auto block_size : block_sizes) { + if (block_size > file_size) + continue; + + auto buffer = ByteBuffer::create_uninitialized(block_size); + + Vector results; + + printf("Running: file_size=%d block_size=%d\n", file_size, block_size); + Core::ElapsedTimer timer; + timer.start(); + while (timer.elapsed() < time_per_benchmark * 1000) { + printf("."); + fflush(stdout); + results.append(benchmark(filename, file_size, block_size, buffer, allow_cache)); + usleep(100); + } + auto average = average_result(results); + printf("\nFinished: runs=%zu time=%dms write_bps=%llu read_bps=%llu\n", results.size(), timer.elapsed(), average.write_bps, average.read_bps); + + sleep(1); + } + } + + if (isatty(0)) { + printf("Press any key to exit...\n"); + fgetc(stdin); + } +} + +Result benchmark(const String& filename, int file_size, int block_size, ByteBuffer& buffer, bool allow_cache) +{ + int flags = O_CREAT | O_TRUNC | O_RDWR; + if (!allow_cache) + flags |= O_DIRECT; + + int fd = open(filename.characters(), flags, 0644); + if (fd == -1) { + perror("open"); + exit(1); + } + + auto cleanup_and_exit = [fd, filename]() { + close(fd); + unlink(filename.characters()); + exit(1); + }; + + Result res; + + Core::ElapsedTimer timer; + + timer.start(); + int nwrote = 0; + for (int j = 0; j < file_size; j += block_size) { + int n = write(fd, buffer.data(), block_size); + if (n < 0) { + perror("write"); + cleanup_and_exit(); + } + nwrote += n; + } + + res.write_bps = (u64)(timer.elapsed() ? (file_size / timer.elapsed()) : file_size) * 1000; + + if (lseek(fd, 0, SEEK_SET) < 0) { + perror("lseek"); + cleanup_and_exit(); + } + + timer.start(); + int nread = 0; + while (nread < file_size) { + int n = read(fd, buffer.data(), block_size); + if (n < 0) { + perror("read"); + cleanup_and_exit(); + } + nread += n; + } + + res.read_bps = (u64)(timer.elapsed() ? (file_size / timer.elapsed()) : file_size) * 1000; + + if (close(fd) != 0) { + perror("close"); + cleanup_and_exit(); + } + + if (unlink(filename.characters()) != 0) { + perror("unlink"); + cleanup_and_exit(); + } + + return res; +} diff --git a/Userland/Utilities/dmesg.cpp b/Userland/Utilities/dmesg.cpp new file mode 100644 index 0000000000..415bad6dd6 --- /dev/null +++ b/Userland/Utilities/dmesg.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/proc/dmesg", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + auto f = Core::File::construct("/proc/dmesg"); + if (!f->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "open: failed to open /proc/dmesg: %s\n", f->error_string()); + return 1; + } + const auto& b = f->read_all(); + for (size_t i = 0; i < b.size(); ++i) + putchar(b[i]); + return 0; +} diff --git a/Userland/Utilities/du.cpp b/Userland/Utilities/du.cpp new file mode 100644 index 0000000000..fed2898eb4 --- /dev/null +++ b/Userland/Utilities/du.cpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2020, Fei Wu + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct DuOption { + enum class TimeType { + NotUsed, + Modification, + Access, + Status + }; + + bool all = false; + bool apparent_size = false; + int threshold = 0; + TimeType time_type = TimeType::NotUsed; + Vector excluded_patterns; +}; + +static int parse_args(int argc, char** argv, Vector& files, DuOption& du_option, int& max_depth); +static int print_space_usage(const String& path, const DuOption& du_option, int max_depth); + +int main(int argc, char** argv) +{ + Vector files; + DuOption du_option; + int max_depth = INT_MAX; + + if (parse_args(argc, argv, files, du_option, max_depth)) + return 1; + + for (const auto& file : files) { + if (print_space_usage(file, du_option, max_depth)) + return 1; + } + + return 0; +} + +int parse_args(int argc, char** argv, Vector& files, DuOption& du_option, int& max_depth) +{ + bool summarize = false; + const char* pattern = nullptr; + const char* exclude_from = nullptr; + Vector files_to_process; + + Core::ArgsParser::Option time_option { + true, + "Show time of type time-type of any file in the directory, or any of its subdirectories. " + "Available choices: mtime, modification, ctime, status, use, atime, access", + "time", + 0, + "time-type", + [&du_option](const char* s) { + if (!strcmp(s, "mtime") || !strcmp(s, "modification")) + du_option.time_type = DuOption::TimeType::Modification; + else if (!strcmp(s, "ctime") || !strcmp(s, "status") || !strcmp(s, "use")) + du_option.time_type = DuOption::TimeType::Status; + else if (!strcmp(s, "atime") || !strcmp(s, "access")) + du_option.time_type = DuOption::TimeType::Access; + else + return false; + + return true; + } + }; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Display actual or apparent disk usage of files or directories."); + args_parser.add_option(du_option.all, "Write counts for all files, not just directories", "all", 'a'); + args_parser.add_option(du_option.apparent_size, "Print apparent sizes, rather than disk usage", "apparent-size", 0); + args_parser.add_option(max_depth, "Print the total for a directory or file only if it is N or fewer levels below the command line argument", "max-depth", 'd', "N"); + args_parser.add_option(summarize, "Display only a total for each argument", "summarize", 's'); + args_parser.add_option(du_option.threshold, "Exclude entries smaller than size if positive, or entries greater than size if negative", "threshold", 't', "size"); + args_parser.add_option(move(time_option)); + args_parser.add_option(pattern, "Exclude files that match pattern", "exclude", 0, "pattern"); + args_parser.add_option(exclude_from, "Exclude files that match any pattern in file", "exclude_from", 'X', "file"); + args_parser.add_positional_argument(files_to_process, "File to process", "file", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (summarize) + max_depth = 0; + + if (pattern) + du_option.excluded_patterns.append(pattern); + if (exclude_from) { + auto file = Core::File::construct(exclude_from); + bool success = file->open(Core::IODevice::ReadOnly); + ASSERT(success); + if (const auto buff = file->read_all()) { + String patterns = String::copy(buff, Chomp); + du_option.excluded_patterns.append(patterns.split('\n')); + } + } + + for (auto* file : files_to_process) { + files.append(file); + } + + if (files.is_empty()) { + files.append("."); + } + + return 0; +} + +int print_space_usage(const String& path, const DuOption& du_option, int max_depth) +{ + struct stat path_stat; + if (lstat(path.characters(), &path_stat) < 0) { + perror("lstat"); + return 1; + } + + if (--max_depth >= 0 && S_ISDIR(path_stat.st_mode)) { + auto di = Core::DirIterator(path, Core::DirIterator::SkipParentAndBaseDir); + if (di.has_error()) { + fprintf(stderr, "DirIterator: %s\n", di.error_string()); + return 1; + } + while (di.has_next()) { + const auto child_path = di.next_full_path(); + if (du_option.all || Core::File::is_directory(child_path)) { + if (print_space_usage(child_path, du_option, max_depth)) + return 1; + } + } + } + + const auto basename = LexicalPath(path).basename(); + for (const auto& pattern : du_option.excluded_patterns) { + if (basename.matches(pattern, CaseSensitivity::CaseSensitive)) + return 0; + } + + long long size = path_stat.st_size; + if (du_option.apparent_size) { + const auto block_size = 512; + size = path_stat.st_blocks * block_size; + } + + if ((du_option.threshold > 0 && size < du_option.threshold) || (du_option.threshold < 0 && size > -du_option.threshold)) + return 0; + + const long long block_size = 1024; + size = size / block_size + (size % block_size != 0); + + if (du_option.time_type == DuOption::TimeType::NotUsed) + printf("%lld\t%s\n", size, path.characters()); + else { + auto time = path_stat.st_mtime; + switch (du_option.time_type) { + case DuOption::TimeType::Access: + time = path_stat.st_atime; + break; + case DuOption::TimeType::Status: + time = path_stat.st_ctime; + default: + break; + } + + const auto formatted_time = Core::DateTime::from_timestamp(time).to_string(); + printf("%lld\t%s\t%s\n", size, formatted_time.characters(), path.characters()); + } + + return 0; +} diff --git a/Userland/Utilities/echo.cpp b/Userland/Utilities/echo.cpp new file mode 100644 index 0000000000..0ac1ce6bb5 --- /dev/null +++ b/Userland/Utilities/echo.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector values; + bool no_trailing_newline = false; + + Core::ArgsParser args_parser; + args_parser.add_option(no_trailing_newline, "Do not output a trailing newline", nullptr, 'n'); + args_parser.add_positional_argument(values, "Values to print out", "string", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + for (size_t i = 0; i < values.size(); ++i) { + fputs(values[i], stdout); + if (i != values.size() - 1) + fputc(' ', stdout); + } + if (!no_trailing_newline) + printf("\n"); + return 0; +} diff --git a/Userland/Utilities/env.cpp b/Userland/Utilities/env.cpp new file mode 100644 index 0000000000..630ff81749 --- /dev/null +++ b/Userland/Utilities/env.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* filename = nullptr; + + for (int idx = 1; idx < argc; ++idx) { + if (idx == 1) { + if (StringView { argv[idx] } == "-i" || StringView { argv[idx] } == "--ignore-environment") { + clearenv(); + continue; + } + } + if (StringView { argv[idx] }.contains('=')) { + putenv(argv[idx]); + } else { + filename = argv[idx]; + argv += idx; + break; + } + } + + if (filename == nullptr) { + for (auto entry = environ; *entry != nullptr; ++entry) + printf("%s\n", *entry); + + return 0; + } + + String filepath = Core::find_executable_in_path(filename); + + if (filepath.is_null()) { + warnln("no {} in path", filename); + return 1; + } + + execv(filepath.characters(), argv); + + perror("execv"); + return 1; +} diff --git a/Userland/Utilities/expr.cpp b/Userland/Utilities/expr.cpp new file mode 100644 index 0000000000..896409a6d9 --- /dev/null +++ b/Userland/Utilities/expr.cpp @@ -0,0 +1,620 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void print_help_and_exit() +{ + outln(R"( +Usage: expr EXPRESSION + expr [--help] + +Print the value of EXPRESSION to standard output.)"); + exit(0); +} + +template +[[noreturn]] void fail(Args&&... args) +{ + warn("ERROR: \e[31m"); + warnln(args...); + warn("\e[0m"); + exit(1); +} + +class Expression { +public: + enum Precedence { + Or, + And, + Comp, + ArithS, + ArithM, + StringO, + Paren, + }; + static NonnullOwnPtr parse(Queue& args, Precedence prec = Or); + + enum class Type { + Integer, + String, + }; + + virtual bool truth() const = 0; + virtual int integer() const = 0; + virtual String string() const = 0; + virtual Type type() const = 0; + virtual ~Expression() { } +}; + +class ValueExpression : public Expression { +public: + ValueExpression(int v) + : as_integer(v) + , m_type(Type::Integer) + { + } + + ValueExpression(String&& v) + : as_string(move(v)) + , m_type(Type::String) + { + } + + virtual ~ValueExpression() { } + +private: + virtual bool truth() const override + { + if (m_type == Type::String) + return !as_string.is_empty(); + return integer() != 0; + } + virtual int integer() const override + { + switch (m_type) { + case Type::Integer: + return as_integer; + case Type::String: + if (auto converted = as_string.to_int(); converted.has_value()) + return converted.value(); + fail("Not an integer: '{}'", as_string); + } + ASSERT_NOT_REACHED(); + } + virtual String string() const override + { + switch (m_type) { + case Type::Integer: + return String::formatted("{}", as_integer); + case Type::String: + return as_string; + } + ASSERT_NOT_REACHED(); + } + virtual Type type() const override { return m_type; } + + union { + int as_integer; + String as_string; + }; + Type m_type { Type::String }; +}; + +class BooleanExpression : public Expression { +public: + enum class BooleanOperator { + And, + Or, + }; + static BooleanOperator op_from(const StringView& sv) + { + if (sv == "&") + return BooleanOperator::And; + return BooleanOperator::Or; + } + BooleanExpression(BooleanOperator op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) + : m_op(op) + , m_left(move(left)) + , m_right(move(right)) + { + if (m_op == BooleanOperator::Or) + m_left_truth = m_left->truth(); + else + m_right_truth = m_right->truth(); + } + +private: + virtual bool truth() const override + { + if (m_op == BooleanOperator::Or) + return m_left_truth ? true : m_right->truth(); + return m_right_truth ? m_left->truth() : false; + } + + virtual int integer() const override + { + switch (m_op) { + case BooleanOperator::And: + if (m_right_truth) + return m_left->integer(); + return 0; + case BooleanOperator::Or: + if (m_left_truth) + return m_left->integer(); + return m_right->integer(); + } + ASSERT_NOT_REACHED(); + } + + virtual String string() const override + { + switch (m_op) { + case BooleanOperator::And: + if (m_right_truth) + return m_left->string(); + return "0"; + case BooleanOperator::Or: + if (m_left_truth) + return m_left->string(); + return m_right->string(); + } + ASSERT_NOT_REACHED(); + } + virtual Type type() const override + { + switch (m_op) { + case BooleanOperator::And: + if (m_right_truth) + return m_left->type(); + return m_right->type(); + case BooleanOperator::Or: + if (m_left_truth) + return m_left->type(); + return m_right->type(); + } + ASSERT_NOT_REACHED(); + } + + BooleanOperator m_op { BooleanOperator::And }; + NonnullOwnPtr m_left, m_right; + bool m_left_truth { false }, m_right_truth { false }; +}; + +class ComparisonExpression : public Expression { +public: + enum class ComparisonOperation { + Less, + LessEq, + Eq, + Neq, + GreaterEq, + Greater, + }; + + static ComparisonOperation op_from(const StringView& sv) + { + if (sv == "<") + return ComparisonOperation::Less; + if (sv == "<=") + return ComparisonOperation::LessEq; + if (sv == "=") + return ComparisonOperation::Eq; + if (sv == "!=") + return ComparisonOperation::Neq; + if (sv == ">=") + return ComparisonOperation::GreaterEq; + return ComparisonOperation::Greater; + } + + ComparisonExpression(ComparisonOperation op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) + : m_op(op) + , m_left(move(left)) + , m_right(move(right)) + { + } + +private: + template + bool compare(const T& left, const T& right) const + { + switch (m_op) { + case ComparisonOperation::Less: + return left < right; + case ComparisonOperation::LessEq: + return left == right || left < right; + case ComparisonOperation::Eq: + return left == right; + case ComparisonOperation::Neq: + return left != right; + case ComparisonOperation::GreaterEq: + return !(left < right); + case ComparisonOperation::Greater: + return left != right && !(left < right); + } + ASSERT_NOT_REACHED(); + } + + virtual bool truth() const override + { + switch (m_left->type()) { + case Type::Integer: + return compare(m_left->integer(), m_right->integer()); + case Type::String: + return compare(m_left->string(), m_right->string()); + } + ASSERT_NOT_REACHED(); + } + virtual int integer() const override { return truth(); } + virtual String string() const override { return truth() ? "1" : "0"; } + virtual Type type() const override { return Type::Integer; } + + ComparisonOperation m_op { ComparisonOperation::Less }; + NonnullOwnPtr m_left, m_right; +}; + +class ArithmeticExpression : public Expression { +public: + enum class ArithmeticOperation { + Sum, + Difference, + Product, + Quotient, + Remainder, + }; + static ArithmeticOperation op_from(const StringView& sv) + { + if (sv == "+") + return ArithmeticOperation::Sum; + if (sv == "-") + return ArithmeticOperation::Difference; + if (sv == "*") + return ArithmeticOperation::Product; + if (sv == "/") + return ArithmeticOperation::Quotient; + return ArithmeticOperation::Remainder; + } + ArithmeticExpression(ArithmeticOperation op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) + : m_op(op) + , m_left(move(left)) + , m_right(move(right)) + { + } + +private: + virtual bool truth() const override + { + switch (m_op) { + case ArithmeticOperation::Sum: + return m_left->truth() || m_right->truth(); + default: + return integer() != 0; + } + } + virtual int integer() const override + { + auto right = m_right->integer(); + if (right == 0) { + if (m_op == ArithmeticOperation::Product) + return 0; + if (m_op == ArithmeticOperation::Quotient || m_op == ArithmeticOperation::Remainder) + fail("Division by zero"); + } + + auto left = m_left->integer(); + switch (m_op) { + case ArithmeticOperation::Product: + return right * left; + case ArithmeticOperation::Sum: + return right + left; + case ArithmeticOperation::Difference: + return left - right; + case ArithmeticOperation::Quotient: + return left / right; + case ArithmeticOperation::Remainder: + return left % right; + } + ASSERT_NOT_REACHED(); + } + virtual String string() const override + { + return String::formatted("{}", integer()); + } + virtual Type type() const override + { + return Type::Integer; + } + + ArithmeticOperation m_op { ArithmeticOperation::Sum }; + NonnullOwnPtr m_left, m_right; +}; + +class StringExpression : public Expression { +public: + enum class StringOperation { + Substring, + Index, + Length, + Match, + }; + + StringExpression(StringOperation op, NonnullOwnPtr string, OwnPtr pos_or_chars = {}, OwnPtr length = {}) + : m_op(op) + , m_str(move(string)) + , m_pos_or_chars(move(pos_or_chars)) + , m_length(move(length)) + { + } + +private: + virtual bool truth() const override { return integer() != 0; } + virtual int integer() const override + { + if (m_op == StringOperation::Substring || m_op == StringOperation::Match) { + auto substr = string(); + if (auto integer = substr.to_int(); integer.has_value()) + return integer.value(); + else + fail("Not an integer: '{}'", substr); + } + + if (m_op == StringOperation::Index) { + if (auto idx = m_str->string().index_of(m_pos_or_chars->string()); idx.has_value()) + return idx.value() + 1; + return 0; + } + + if (m_op == StringOperation::Length) + return m_str->string().length(); + + ASSERT_NOT_REACHED(); + } + static auto safe_substring(const String& str, int start, int length) + { + if (start < 1 || (size_t)start > str.length()) + fail("Index out of range"); + --start; + if (str.length() - start < (size_t)length) + fail("Index out of range"); + return str.substring(start, length); + } + virtual String string() const override + { + if (m_op == StringOperation::Substring) + return safe_substring(m_str->string(), m_pos_or_chars->integer(), m_length->integer()); + + if (m_op == StringOperation::Match) { + auto match = m_compiled_regex->match(m_str->string(), PosixFlags::Global); + if (m_compiled_regex->parser_result.capture_groups_count == 0) { + if (!match.success) + return "0"; + + size_t count = 0; + for (auto& m : match.matches) + count += m.view.length(); + + return String::number(count); + } else { + if (!match.success) + return ""; + + StringBuilder result; + for (auto& m : match.capture_group_matches) { + for (auto& e : m) + result.append(e.view.to_string()); + } + + return result.build(); + } + } + + return String::number(integer()); + } + virtual Type type() const override + { + if (m_op == StringOperation::Substring) + return Type::String; + if (m_op == StringOperation::Match) { + if (!m_pos_or_chars) + fail("'match' expects a string pattern"); + + ensure_regex(); + if (m_compiled_regex->parser_result.capture_groups_count == 0) + return Type::Integer; + + return Type::String; + } + return Type::Integer; + } + + void ensure_regex() const + { + if (!m_compiled_regex) + m_compiled_regex = make>(m_pos_or_chars->string()); + } + + StringOperation m_op { StringOperation::Substring }; + NonnullOwnPtr m_str; + OwnPtr m_pos_or_chars, m_length; + mutable OwnPtr> m_compiled_regex; +}; + +NonnullOwnPtr Expression::parse(Queue& args, Precedence prec) +{ + switch (prec) { + case Or: { + auto left = parse(args, And); + while (!args.is_empty() && args.head() == "|") { + args.dequeue(); + auto right = parse(args, And); + left = make(BooleanExpression::BooleanOperator::Or, move(left), move(right)); + } + return left; + } + case And: { + auto left = parse(args, Comp); + while (!args.is_empty() && args.head() == "&") { + args.dequeue(); + auto right = parse(args, Comp); + left = make(BooleanExpression::BooleanOperator::And, move(left), move(right)); + } + return left; + } + case Comp: { + auto left = parse(args, ArithS); + while (!args.is_empty() && args.head().is_one_of("<", "<=", "=", "!=", "=>", ">")) { + auto op = args.dequeue(); + auto right = parse(args, ArithM); + left = make(ComparisonExpression::op_from(op), move(left), move(right)); + } + return left; + } + case ArithS: { + auto left = parse(args, ArithM); + while (!args.is_empty() && args.head().is_one_of("+", "-")) { + auto op = args.dequeue(); + auto right = parse(args, ArithM); + left = make(ArithmeticExpression::op_from(op), move(left), move(right)); + } + return left; + } + case ArithM: { + auto left = parse(args, StringO); + while (!args.is_empty() && args.head().is_one_of("*", "/", "%")) { + auto op = args.dequeue(); + auto right = parse(args, StringO); + left = make(ArithmeticExpression::op_from(op), move(left), move(right)); + } + return left; + } + case StringO: { + if (args.is_empty()) + fail("Expected a term"); + + OwnPtr left; + + while (!args.is_empty()) { + auto& op = args.head(); + if (op == "+") { + args.dequeue(); + left = make(args.dequeue()); + } else if (op == "substr") { + args.dequeue(); + auto str = parse(args, Paren); + auto pos = parse(args, Paren); + auto len = parse(args, Paren); + left = make(StringExpression::StringOperation::Substring, move(str), move(pos), move(len)); + } else if (op == "index") { + args.dequeue(); + auto str = parse(args, Paren); + auto chars = parse(args, Paren); + left = make(StringExpression::StringOperation::Index, move(str), move(chars)); + } else if (op == "match") { + args.dequeue(); + auto str = parse(args, Paren); + auto pattern = parse(args, Paren); + left = make(StringExpression::StringOperation::Match, move(str), move(pattern)); + } else if (op == "length") { + args.dequeue(); + auto str = parse(args, Paren); + left = make(StringExpression::StringOperation::Length, move(str)); + } else if (!left) { + left = parse(args, Paren); + } + + if (!args.is_empty() && args.head() == ":") { + args.dequeue(); + auto right = parse(args, Paren); + left = make(StringExpression::StringOperation::Match, left.release_nonnull(), move(right)); + } else { + return left.release_nonnull(); + } + } + + return left.release_nonnull(); + } + case Paren: { + if (args.is_empty()) + fail("Expected a term"); + + if (args.head() == "(") { + args.dequeue(); + auto expr = parse(args); + if (args.head() != ")") + fail("Expected a close paren"); + args.dequeue(); + return expr; + } + + return make(args.dequeue()); + } + } + + fail("Invalid expression"); +} + +int main(int argc, char** argv) +{ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil(nullptr, nullptr) < 0) { + perror("unveil"); + return 1; + } + + if ((argc == 2 && StringView { "--help" } == argv[1]) || argc == 1) + print_help_and_exit(); + + Queue args; + for (int i = 1; i < argc; ++i) + args.enqueue(argv[i]); + + auto expression = Expression::parse(args); + if (!args.is_empty()) + fail("Extra tokens at the end of the expression"); + + switch (expression->type()) { + case Expression::Type::Integer: + outln("{}", expression->integer()); + break; + case Expression::Type::String: + outln("{}", expression->string()); + break; + } + return 0; +} diff --git a/Userland/Utilities/false.cpp b/Userland/Utilities/false.cpp new file mode 100644 index 0000000000..c7a39e47e8 --- /dev/null +++ b/Userland/Utilities/false.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +int main(int, char**) +{ + return 1; +} diff --git a/Userland/Utilities/fgrep.cpp b/Userland/Utilities/fgrep.cpp new file mode 100644 index 0000000000..bf71e54c77 --- /dev/null +++ b/Userland/Utilities/fgrep.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc < 2) { + printf("usage: fgrep \n"); + return 0; + } + for (;;) { + char buf[4096]; + auto* str = fgets(buf, sizeof(buf), stdin); + if (str && strstr(str, argv[1])) + write(1, buf, strlen(buf)); + if (feof(stdin)) + return 0; + ASSERT(str); + } + return 0; +} diff --git a/Userland/Utilities/find.cpp b/Userland/Utilities/find.cpp new file mode 100644 index 0000000000..872cf1717e --- /dev/null +++ b/Userland/Utilities/find.cpp @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool g_follow_symlinks = false; +bool g_there_was_an_error = false; +bool g_have_seen_action_command = false; + +[[noreturn]] static void fatal_error(const char* format, ...) +{ + fputs("\033[31m", stderr); + + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + + fputs("\033[0m\n", stderr); + exit(1); +} + +class Command { +public: + virtual ~Command() { } + virtual bool evaluate(const char* file_path) const = 0; +}; + +class StatCommand : public Command { +public: + virtual bool evaluate(const struct stat&) const = 0; + +private: + virtual bool evaluate(const char* file_path) const override + { + struct stat stat; + auto stat_func = g_follow_symlinks ? ::stat : ::lstat; + int rc = stat_func(file_path, &stat); + if (rc < 0) { + perror(file_path); + g_there_was_an_error = true; + return false; + } + return evaluate(stat); + } +}; + +class TypeCommand final : public StatCommand { +public: + TypeCommand(const char* arg) + { + StringView type = arg; + if (type.length() != 1 || !StringView("bcdlpfs").contains(type[0])) + fatal_error("Invalid mode: \033[1m%s", arg); + m_type = type[0]; + } + +private: + virtual bool evaluate(const struct stat& stat) const override + { + auto type = stat.st_mode; + switch (m_type) { + case 'b': + return S_ISBLK(type); + case 'c': + return S_ISCHR(type); + case 'd': + return S_ISDIR(type); + case 'l': + return S_ISLNK(type); + case 'p': + return S_ISFIFO(type); + case 'f': + return S_ISREG(type); + case 's': + return S_ISSOCK(type); + default: + // We've verified this is a correct character before. + ASSERT_NOT_REACHED(); + } + } + + char m_type { 0 }; +}; + +class LinksCommand final : public StatCommand { +public: + LinksCommand(const char* arg) + { + auto number = StringView(arg).to_uint(); + if (!number.has_value()) + fatal_error("Invalid number: \033[1m%s", arg); + m_links = number.value(); + } + +private: + virtual bool evaluate(const struct stat& stat) const override + { + return stat.st_nlink == m_links; + } + + nlink_t m_links { 0 }; +}; + +class UserCommand final : public StatCommand { +public: + UserCommand(const char* arg) + { + if (struct passwd* passwd = getpwnam(arg)) { + m_uid = passwd->pw_uid; + } else { + // Attempt to parse it as decimal UID. + auto number = StringView(arg).to_uint(); + if (!number.has_value()) + fatal_error("Invalid user: \033[1m%s", arg); + m_uid = number.value(); + } + } + +private: + virtual bool evaluate(const struct stat& stat) const override + { + return stat.st_uid == m_uid; + } + + uid_t m_uid { 0 }; +}; + +class GroupCommand final : public StatCommand { +public: + GroupCommand(const char* arg) + { + if (struct group* gr = getgrnam(arg)) { + m_gid = gr->gr_gid; + } else { + // Attempt to parse it as decimal GID. + auto number = StringView(arg).to_int(); + if (!number.has_value()) + fatal_error("Invalid group: \033[1m%s", arg); + m_gid = number.value(); + } + } + +private: + virtual bool evaluate(const struct stat& stat) const override + { + return stat.st_gid == m_gid; + } + + gid_t m_gid { 0 }; +}; + +class SizeCommand final : public StatCommand { +public: + SizeCommand(const char* arg) + { + StringView view = arg; + if (view.ends_with('c')) { + m_is_bytes = true; + view = view.substring_view(0, view.length() - 1); + } + auto number = view.to_uint(); + if (!number.has_value()) + fatal_error("Invalid size: \033[1m%s", arg); + m_size = number.value(); + } + +private: + virtual bool evaluate(const struct stat& stat) const override + { + if (m_is_bytes) + return stat.st_size == m_size; + + auto size_divided_by_512_rounded_up = (stat.st_size + 511) / 512; + return size_divided_by_512_rounded_up == m_size; + } + + off_t m_size { 0 }; + bool m_is_bytes { false }; +}; + +class NameCommand : public Command { +public: + NameCommand(const char* pattern, CaseSensitivity case_sensitivity) + : m_pattern(pattern) + , m_case_sensitivity(case_sensitivity) + { + } + +private: + virtual bool evaluate(const char* file_path) const override + { + LexicalPath path { file_path }; + return path.basename().matches(m_pattern, m_case_sensitivity); + } + + StringView m_pattern; + CaseSensitivity m_case_sensitivity { CaseSensitivity::CaseSensitive }; +}; + +class PrintCommand final : public Command { +public: + PrintCommand(char terminator = '\n') + : m_terminator(terminator) + { + } + +private: + virtual bool evaluate(const char* file_path) const override + { + printf("%s%c", file_path, m_terminator); + return true; + } + + char m_terminator { '\n' }; +}; + +class ExecCommand final : public Command { +public: + ExecCommand(Vector&& argv) + : m_argv(move(argv)) + { + } + +private: + virtual bool evaluate(const char* file_path) const override + { + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + g_there_was_an_error = true; + return false; + } else if (pid == 0) { + // Replace any occurrences of "{}" with the path. Since we're in the + // child and going to exec real soon, let's just const_cast away the + // constness. + auto argv = const_cast&>(m_argv); + for (auto& arg : argv) { + if (StringView(arg) == "{}") + arg = const_cast(file_path); + } + argv.append(nullptr); + execvp(m_argv[0], argv.data()); + perror("execvp"); + exit(1); + } else { + int status; + int rc = waitpid(pid, &status, 0); + if (rc < 0) { + perror("waitpid"); + g_there_was_an_error = true; + return false; + } + return WIFEXITED(status) && WEXITSTATUS(status) == 0; + } + } + + Vector m_argv; +}; + +class AndCommand final : public Command { +public: + AndCommand(NonnullOwnPtr&& lhs, NonnullOwnPtr&& rhs) + : m_lhs(move(lhs)) + , m_rhs(move(rhs)) + { + } + +private: + virtual bool evaluate(const char* file_path) const override + { + return m_lhs->evaluate(file_path) && m_rhs->evaluate(file_path); + } + + NonnullOwnPtr m_lhs; + NonnullOwnPtr m_rhs; +}; + +class OrCommand final : public Command { +public: + OrCommand(NonnullOwnPtr&& lhs, NonnullOwnPtr&& rhs) + : m_lhs(move(lhs)) + , m_rhs(move(rhs)) + { + } + +private: + virtual bool evaluate(const char* file_path) const override + { + return m_lhs->evaluate(file_path) || m_rhs->evaluate(file_path); + } + + NonnullOwnPtr m_lhs; + NonnullOwnPtr m_rhs; +}; + +static OwnPtr parse_complex_command(char* argv[]); + +// Parse a simple command starting at optind; leave optind at its the last +// argument. Return nullptr if we reach the end of arguments. +static OwnPtr parse_simple_command(char* argv[]) +{ + StringView arg = argv[optind]; + + if (arg.is_null()) { + return {}; + } else if (arg == "(") { + optind++; + auto command = parse_complex_command(argv); + if (command && argv[optind] && StringView(argv[++optind]) == ")") + return command; + fatal_error("Unmatched \033[1m("); + } else if (arg == "-type") { + return make(argv[++optind]); + } else if (arg == "-links") { + return make(argv[++optind]); + } else if (arg == "-user") { + return make(argv[++optind]); + } else if (arg == "-group") { + return make(argv[++optind]); + } else if (arg == "-size") { + return make(argv[++optind]); + } else if (arg == "-name") { + return make(argv[++optind], CaseSensitivity::CaseSensitive); + } else if (arg == "-iname") { + return make(argv[++optind], CaseSensitivity::CaseInsensitive); + } else if (arg == "-print") { + g_have_seen_action_command = true; + return make(); + } else if (arg == "-print0") { + g_have_seen_action_command = true; + return make(0); + } else if (arg == "-exec") { + g_have_seen_action_command = true; + Vector command_argv; + while (argv[++optind] && StringView(argv[optind]) != ";") + command_argv.append(argv[optind]); + return make(move(command_argv)); + } else { + fatal_error("Unsupported command \033[1m%s", argv[optind]); + } +} + +static OwnPtr parse_complex_command(char* argv[]) +{ + auto command = parse_simple_command(argv); + + while (command && argv[optind] && argv[optind + 1]) { + StringView arg = argv[++optind]; + + enum { And, + Or } binary_operation + = And; + + if (arg == "-a") { + optind++; + binary_operation = And; + } else if (arg == "-o") { + optind++; + binary_operation = Or; + } else if (arg == ")") { + // Ooops, looked too far. + optind--; + return command; + } else { + // Juxtaposition is an And too, and there's nothing to skip. + binary_operation = And; + } + + auto rhs = parse_complex_command(argv); + if (!rhs) + fatal_error("Missing right-hand side"); + + if (binary_operation == And) + command = make(command.release_nonnull(), rhs.release_nonnull()); + else + command = make(command.release_nonnull(), rhs.release_nonnull()); + } + + return command; +} + +static NonnullOwnPtr parse_all_commands(char* argv[]) +{ + auto command = parse_complex_command(argv); + + if (g_have_seen_action_command) { + ASSERT(command); + return command.release_nonnull(); + } + + if (!command) { + return make(); + } + + return make(command.release_nonnull(), make()); +} + +static const char* parse_options(int argc, char* argv[]) +{ + // Sadly, we can't use Core::ArgsParser, because find accepts arguments in + // an extremely unusual format. We're going to try to use getopt(), though. + opterr = 0; + while (true) { + int opt = getopt(argc, argv, "+L"); + switch (opt) { + case -1: { + // No more options. + StringView arg = argv[optind]; + if (!arg.is_null() && !arg.starts_with('-')) { + // It's our root path! + return argv[optind++]; + } else { + // It's a part of the script, and our root path is the current + // directory by default. + return "."; + } + } + case '?': + // Some error. Most likely, it's getopt() getting confused about + // what it thought was an option, but is actually a command. Return + // the default path, and hope the command parsing logic deals with + // this. + return "."; + case 'L': + g_follow_symlinks = true; + break; + default: + ASSERT_NOT_REACHED(); + } + } +} + +static void walk_tree(const char* root_path, Command& command) +{ + command.evaluate(root_path); + + Core::DirIterator dir_iterator(root_path, Core::DirIterator::SkipParentAndBaseDir); + if (dir_iterator.has_error() && dir_iterator.error() == ENOTDIR) + return; + + while (dir_iterator.has_next()) + walk_tree(dir_iterator.next_full_path().characters(), command); + + if (dir_iterator.has_error()) { + fprintf(stderr, "%s: %s\n", root_path, dir_iterator.error_string()); + g_there_was_an_error = true; + } +} + +int main(int argc, char* argv[]) +{ + auto root_path = parse_options(argc, argv); + auto command = parse_all_commands(argv); + walk_tree(root_path, *command); + return g_there_was_an_error ? 1 : 0; +} diff --git a/Userland/Utilities/flock.cpp b/Userland/Utilities/flock.cpp new file mode 100644 index 0000000000..dade8a2c44 --- /dev/null +++ b/Userland/Utilities/flock.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (argc < 3) { + printf("usage: flock \n"); + return 0; + } + + pid_t child_pid; + if ((errno = posix_spawnp(&child_pid, argv[2], nullptr, nullptr, &argv[2], environ))) { + perror("posix_spawn"); + return 1; + } + + int status; + if (waitpid(child_pid, &status, 0) < 0) { + perror("waitpid"); + return 1; + } + return WEXITSTATUS(status); +} diff --git a/Userland/Utilities/functrace.cpp b/Userland/Utilities/functrace.cpp new file mode 100644 index 0000000000..dcb2d046b3 --- /dev/null +++ b/Userland/Utilities/functrace.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2020, Itamar S. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static OwnPtr g_debug_session; +static bool g_should_output_color = false; + +static void handle_sigint(int) +{ + printf("Debugger: SIGINT\n"); + + // The destructor of DebugSession takes care of detaching + g_debug_session = nullptr; +} + +static void print_function_call(String function_name, size_t depth) +{ + for (size_t i = 0; i < depth; ++i) { + out(" "); + } + outln("=> {}", function_name); +} + +static void print_syscall(PtraceRegisters& regs, size_t depth) +{ + for (size_t i = 0; i < depth; ++i) { + printf(" "); + } + const char* begin_color = g_should_output_color ? "\033[34;1m" : ""; + const char* end_color = g_should_output_color ? "\033[0m" : ""; + outln("=> {}SC_{}(0x{:x}, 0x{:x}, 0x{:x}){}", + begin_color, + Syscall::to_string((Syscall::Function)regs.eax), + regs.edx, + regs.ecx, + regs.ebx, + end_color); +} + +static NonnullOwnPtr> instrument_code() +{ + auto instrumented = make>(); + g_debug_session->for_each_loaded_library([&](const Debug::DebugSession::LoadedLibrary& lib) { + lib.debug_info->elf().for_each_section_of_type(SHT_PROGBITS, [&](const ELF::Image::Section& section) { + if (section.name() != ".text") + return IterationDecision::Continue; + + X86::SimpleInstructionStream stream((const u8*)((u32)lib.file->data() + section.offset()), section.size()); + X86::Disassembler disassembler(stream); + for (;;) { + auto offset = stream.offset(); + void* instruction_address = (void*)(section.address() + offset + lib.base_address); + auto insn = disassembler.next(); + if (!insn.has_value()) + break; + if (insn.value().mnemonic() == "RET" || insn.value().mnemonic() == "CALL") { + g_debug_session->insert_breakpoint(instruction_address); + instrumented->set(instruction_address, insn.value()); + } + } + return IterationDecision::Continue; + }); + return IterationDecision::Continue; + }); + return instrumented; +} + +int main(int argc, char** argv) +{ + if (pledge("stdio proc exec rpath sigaction ptrace", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (isatty(STDOUT_FILENO)) + g_should_output_color = true; + + const char* command = nullptr; + Core::ArgsParser args_parser; + args_parser.add_positional_argument(command, + "The program to be traced, along with its arguments", + "program", Core::ArgsParser::Required::Yes); + args_parser.parse(argc, argv); + + auto result = Debug::DebugSession::exec_and_attach(command); + if (!result) { + warnln("Failed to start debugging session for: \"{}\"", command); + exit(1); + } + g_debug_session = result.release_nonnull(); + + auto instrumented = instrument_code(); + + struct sigaction sa; + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = handle_sigint; + sigaction(SIGINT, &sa, nullptr); + + size_t depth = 0; + bool new_function = true; + + g_debug_session->run(Debug::DebugSession::DesiredInitialDebugeeState::Running, [&](Debug::DebugSession::DebugBreakReason reason, Optional regs) { + if (reason == Debug::DebugSession::DebugBreakReason::Exited) { + outln("Program exited."); + return Debug::DebugSession::DebugDecision::Detach; + } + + if (reason == Debug::DebugSession::DebugBreakReason::Syscall) { + print_syscall(regs.value(), depth + 1); + return Debug::DebugSession::DebugDecision::ContinueBreakAtSyscall; + } + + if (new_function) { + auto function_name = g_debug_session->symbolicate(regs.value().eip); + print_function_call(function_name.value().symbol, depth); + new_function = false; + return Debug::DebugSession::ContinueBreakAtSyscall; + } + auto instruction = instrumented->get((void*)regs.value().eip).value(); + + if (instruction.mnemonic() == "RET") { + if (depth != 0) + --depth; + return Debug::DebugSession::ContinueBreakAtSyscall; + } + + // FIXME: we could miss some leaf functions that were called with a jump + ASSERT(instruction.mnemonic() == "CALL"); + + ++depth; + new_function = true; + + return Debug::DebugSession::DebugDecision::SingleStep; + }); +} diff --git a/Userland/Utilities/gml-format.cpp b/Userland/Utilities/gml-format.cpp new file mode 100644 index 0000000000..79ce530a32 --- /dev/null +++ b/Userland/Utilities/gml-format.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2021, Linus Groh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +bool format_file(const StringView&, bool); + +bool format_file(const StringView& path, bool inplace) +{ + auto read_from_stdin = path == "-"; + RefPtr file; + if (read_from_stdin) { + file = Core::File::standard_input(); + } else { + auto open_mode = inplace ? Core::File::ReadWrite : Core::File::ReadOnly; + auto file_or_error = Core::File::open(path, open_mode); + if (file_or_error.is_error()) { + warnln("Could not open {}: {}", path, file_or_error.error()); + return false; + } + file = file_or_error.value(); + } + auto formatted_gml = GUI::format_gml(file->read_all()); + if (formatted_gml.is_null()) { + warnln("Failed to parse GML!"); + return false; + } + if (inplace && !read_from_stdin) { + if (!file->seek(0) || !file->truncate(0)) { + warnln("Could not truncate {}: {}", path, file->error_string()); + return false; + } + if (!file->write(formatted_gml)) { + warnln("Could not write to {}: {}", path, file->error_string()); + return false; + } + } else { + out("{}", formatted_gml); + } + return true; +} + +int main(int argc, char** argv) +{ +#ifdef __serenity__ + if (pledge("stdio rpath wpath cpath", nullptr) < 0) { + perror("pledge"); + return 1; + } +#endif + + bool inplace = false; + Vector files; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Format GML files."); + args_parser.add_option(inplace, "Write formatted contents back to file rather than standard output", "inplace", 'i'); + args_parser.add_positional_argument(files, "File(s) to process", "path", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + +#ifdef __serenity__ + if (!inplace) { + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + } +#endif + + unsigned exit_code = 0; + + if (files.is_empty()) + files.append("-"); + for (auto& file : files) { + if (!format_file(file, inplace)) + exit_code = 1; + } + + return exit_code; +} diff --git a/Userland/Utilities/grep.cpp b/Userland/Utilities/grep.cpp new file mode 100644 index 0000000000..25ab2e900c --- /dev/null +++ b/Userland/Utilities/grep.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2020, Emanuel Sprung + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum class BinaryFileMode { + Binary, + Text, + Skip, +}; + +template +void fail(StringView format, Ts... args) +{ + fprintf(stderr, "\x1b[31m"); + warnln(format, forward(args)...); + fprintf(stderr, "\x1b[0m"); + abort(); +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector files; + + bool recursive { false }; + bool use_ere { true }; + const char* pattern = nullptr; + BinaryFileMode binary_mode { BinaryFileMode::Binary }; + bool case_insensitive = false; + + Core::ArgsParser args_parser; + args_parser.add_option(recursive, "Recursively scan files starting in working directory", "recursive", 'r'); + args_parser.add_option(use_ere, "Extended regular expressions (default)", "extended-regexp", 'E'); + args_parser.add_option(pattern, "Pattern", "regexp", 'e', "Pattern"); + args_parser.add_option(case_insensitive, "Make matches case-insensitive", nullptr, 'i'); + args_parser.add_option(Core::ArgsParser::Option { + .requires_argument = true, + .help_string = "Action to take for binary files ([binary], text, skip)", + .long_name = "binary-mode", + .accept_value = [&](auto* str) { + if (StringView { "text" } == str) + binary_mode = BinaryFileMode::Text; + else if (StringView { "binary" } == str) + binary_mode = BinaryFileMode::Binary; + else if (StringView { "skip" } == str) + binary_mode = BinaryFileMode::Skip; + else + return false; + return true; + }, + }); + args_parser.add_option(Core::ArgsParser::Option { + .requires_argument = false, + .help_string = "Treat binary files as text (same as --binary-mode text)", + .long_name = "text", + .short_name = 'a', + .accept_value = [&](auto) { + binary_mode = BinaryFileMode::Text; + return true; + }, + }); + args_parser.add_option(Core::ArgsParser::Option { + .requires_argument = false, + .help_string = "Ignore binary files (same as --binary-mode skip)", + .long_name = nullptr, + .short_name = 'I', + .accept_value = [&](auto) { + binary_mode = BinaryFileMode::Skip; + return true; + }, + }); + args_parser.add_positional_argument(files, "File(s) to process", "file", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (!use_ere) + return 0; + + // mock grep behaviour: if -e is omitted, use first positional argument as pattern + if (pattern == nullptr && files.size()) + pattern = files.take_first(); + + PosixOptions options {}; + if (case_insensitive) + options |= PosixFlags::Insensitive; + + Regex re(pattern, options); + if (re.parser_result.error != Error::NoError) { + return 1; + } + + auto matches = [&](StringView str, StringView filename = "", bool print_filename = false, bool is_binary = false) { + size_t last_printed_char_pos { 0 }; + if (is_binary && binary_mode == BinaryFileMode::Skip) + return false; + + auto result = re.match(str, PosixFlags::Global); + if (result.success) { + if (is_binary && binary_mode == BinaryFileMode::Binary) { + outln("binary file \x1B[34m{}\x1B[0m matches", filename); + } else { + if (result.matches.size() && print_filename) { + out("\x1B[34m{}:\x1B[0m", filename); + } + + for (auto& match : result.matches) { + + out("{}\x1B[32m{}\x1B[0m", + StringView(&str[last_printed_char_pos], match.global_offset - last_printed_char_pos), + match.view.to_string()); + last_printed_char_pos = match.global_offset + match.view.length(); + } + outln("{}", StringView(&str[last_printed_char_pos], str.length() - last_printed_char_pos)); + } + + return true; + } + + return false; + }; + + auto handle_file = [&matches, binary_mode](StringView filename, bool print_filename) -> bool { + auto file = Core::File::construct(filename); + if (!file->open(Core::IODevice::ReadOnly)) { + warnln("Failed to open {}: {}", filename, file->error_string()); + return false; + } + + while (file->can_read_line()) { + auto line = file->read_line(); + auto is_binary = memchr(line.characters(), 0, line.length()) != nullptr; + + if (matches(line, filename, print_filename, is_binary) && is_binary && binary_mode == BinaryFileMode::Binary) + return true; + } + return true; + }; + + auto add_directory = [&handle_file](String base, Optional recursive, auto handle_directory) -> void { + Core::DirIterator it(recursive.value_or(base), Core::DirIterator::Flags::SkipDots); + while (it.has_next()) { + auto path = it.next_full_path(); + if (!Core::File::is_directory(path)) { + auto key = path.substring_view(base.length() + 1, path.length() - base.length() - 1); + handle_file(key, true); + } else { + handle_directory(base, path, handle_directory); + } + } + }; + + if (!files.size() && !recursive) { + auto stdin_file = Core::File::standard_input(); + while (!stdin_file->eof()) { + auto line = stdin_file->read_line(); + bool is_binary = line.bytes().contains_slow(0); + + if (is_binary && binary_mode == BinaryFileMode::Skip) + return 1; + + if (matches(line, "stdin", false, is_binary) && is_binary && binary_mode == BinaryFileMode::Binary) + return 0; + } + } else { + if (recursive) { + add_directory(".", {}, add_directory); + + } else { + bool print_filename { files.size() > 1 }; + for (auto& filename : files) { + if (!handle_file(filename, print_filename)) + return 1; + } + } + } + + return 0; +} diff --git a/Userland/Utilities/gron.cpp b/Userland/Utilities/gron.cpp new file mode 100644 index 0000000000..0c1b50eca9 --- /dev/null +++ b/Userland/Utilities/gron.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static bool use_color = false; +static void print(const String& name, const JsonValue&, Vector& trail); + +static const char* color_name = ""; +static const char* color_index = ""; +static const char* color_brace = ""; +static const char* color_bool = ""; +static const char* color_null = ""; +static const char* color_string = ""; +static const char* color_off = ""; + +int main(int argc, char** argv) +{ + if (pledge("stdio tty rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (isatty(STDOUT_FILENO)) + use_color = true; + + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (argc != 2 || !strcmp(argv[1], "--help")) { + fprintf(stderr, "usage: gron \n"); + fprintf(stderr, "Print each value in a JSON file with its fully expanded key.\n"); + return 0; + } + auto file = Core::File::construct(argv[1]); + if (!file->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Couldn't open %s for reading: %s\n", argv[1], file->error_string()); + return 1; + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto file_contents = file->read_all(); + auto json = JsonValue::from_string(file_contents); + ASSERT(json.has_value()); + + if (use_color) { + color_name = "\033[33;1m"; + color_index = "\033[35;1m"; + color_brace = "\033[36m"; + color_bool = "\033[32;1m"; + color_string = "\033[31;1m"; + color_null = "\033[34;1m"; + color_off = "\033[0m"; + } + + Vector trail; + print("json", json.value(), trail); + return 0; +} + +static void print(const String& name, const JsonValue& value, Vector& trail) +{ + for (size_t i = 0; i < trail.size(); ++i) + printf("%s", trail[i].characters()); + + printf("%s%s%s = ", color_name, name.characters(), color_off); + + if (value.is_object()) { + printf("%s{}%s;\n", color_brace, color_off); + trail.append(String::format("%s%s%s.", color_name, name.characters(), color_off)); + value.as_object().for_each_member([&](auto& on, auto& ov) { print(on, ov, trail); }); + trail.take_last(); + return; + } + if (value.is_array()) { + printf("%s[]%s;\n", color_brace, color_off); + trail.append(String::format("%s%s%s", color_name, name.characters(), color_off)); + for (int i = 0; i < value.as_array().size(); ++i) { + auto element_name = String::format("%s%s[%s%s%d%s%s]%s", color_off, color_brace, color_off, color_index, i, color_off, color_brace, color_off); + print(element_name, value.as_array()[i], trail); + } + trail.take_last(); + return; + } + switch (value.type()) { + case JsonValue::Type::Null: + printf("%s", color_null); + break; + case JsonValue::Type::Bool: + printf("%s", color_bool); + break; + case JsonValue::Type::String: + printf("%s", color_string); + break; + default: + printf("%s", color_index); + break; + } + + printf("%s%s;\n", value.serialized().characters(), color_off); +} diff --git a/Userland/Utilities/gunzip.cpp b/Userland/Utilities/gunzip.cpp new file mode 100644 index 0000000000..58678d1b88 --- /dev/null +++ b/Userland/Utilities/gunzip.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +static void decompress_file(Buffered& input_stream, Buffered& output_stream) +{ + auto gzip_stream = Compress::GzipDecompressor { input_stream }; + + u8 buffer[4096]; + + while (!gzip_stream.unreliable_eof()) { + const auto nread = gzip_stream.read({ buffer, sizeof(buffer) }); + output_stream.write_or_error({ buffer, nread }); + } +} + +int main(int argc, char** argv) +{ + Vector filenames; + bool keep_input_files { false }; + bool write_to_stdout { false }; + + Core::ArgsParser args_parser; + args_parser.add_option(keep_input_files, "Keep (don't delete) input files", "keep", 'k'); + args_parser.add_option(write_to_stdout, "Write to stdout, keep original files unchanged", "stdout", 'c'); + args_parser.add_positional_argument(filenames, "File to decompress", "FILE"); + args_parser.parse(argc, argv); + + if (write_to_stdout) + keep_input_files = true; + + for (String filename : filenames) { + if (!filename.ends_with(".gz")) + filename = String::format("%s.gz", filename.characters()); + + const auto input_filename = filename; + const auto output_filename = filename.substring_view(0, filename.length() - 3); + + auto input_stream_result = Core::InputFileStream::open_buffered(input_filename); + + if (write_to_stdout) { + auto stdout = Core::OutputFileStream::stdout_buffered(); + decompress_file(input_stream_result.value(), stdout); + } else { + auto output_stream_result = Core::OutputFileStream::open_buffered(output_filename); + decompress_file(input_stream_result.value(), output_stream_result.value()); + } + + if (!keep_input_files) { + const auto retval = unlink(String { input_filename }.characters()); + ASSERT(retval == 0); + } + } +} diff --git a/Userland/Utilities/head.cpp b/Userland/Utilities/head.cpp new file mode 100644 index 0000000000..132df61ee6 --- /dev/null +++ b/Userland/Utilities/head.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int head(const String& filename, bool print_filename, int line_count, int char_count); + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + int line_count = 0; + int char_count = 0; + bool never_print_filenames = false; + bool always_print_filenames = false; + Vector files; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Print the beginning ('head') of a file."); + args_parser.add_option(line_count, "Number of lines to print (default 10)", "lines", 'n', "number"); + args_parser.add_option(char_count, "Number of characters to print", "characters", 'c', "number"); + args_parser.add_option(never_print_filenames, "Never print file names", "quiet", 'q'); + args_parser.add_option(always_print_filenames, "Always print file names", "verbose", 'v'); + args_parser.add_positional_argument(files, "File to process", "file", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (line_count == 0 && char_count == 0) { + line_count = 10; + } + + bool print_filenames = files.size() > 1; + if (always_print_filenames) + print_filenames = true; + else if (never_print_filenames) + print_filenames = false; + + if (files.is_empty()) { + return head("", print_filenames, line_count, char_count); + } + + int rc = 0; + + for (auto& file : files) { + if (head(file, print_filenames, line_count, char_count) != 0) { + rc = 1; + } + } + + return rc; +} + +int head(const String& filename, bool print_filename, int line_count, int char_count) +{ + bool is_stdin = false; + FILE* fp = nullptr; + + if (filename == "" || filename == "-") { + fp = stdin; + is_stdin = true; + } else { + fp = fopen(filename.characters(), "r"); + if (!fp) { + fprintf(stderr, "can't open %s for reading: %s\n", filename.characters(), strerror(errno)); + return 1; + } + } + + if (print_filename) { + if (is_stdin) { + puts("==> standard input <=="); + } else { + printf("==> %s <==\n", filename.characters()); + } + } + + if (line_count) { + for (int line = 0; line < line_count; ++line) { + char buffer[BUFSIZ]; + auto* str = fgets(buffer, sizeof(buffer), fp); + if (!str) + break; + + // specifically use fputs rather than puts, because fputs doesn't add + // its own newline. + fputs(str, stdout); + } + } else if (char_count) { + char buffer[BUFSIZ]; + + while (char_count) { + int nread = fread(buffer, 1, min(BUFSIZ, char_count), fp); + if (nread > 0) { + int ncomplete = 0; + + while (ncomplete < nread) { + int nwrote = fwrite(&buffer[ncomplete], 1, nread - ncomplete, stdout); + if (nwrote > 0) + ncomplete += nwrote; + + if (feof(stdout)) { + fprintf(stderr, "unexpected eof writing to stdout\n"); + return 1; + } + + if (ferror(stdout)) { + fprintf(stderr, "error writing to stdout\n"); + return 1; + } + } + } + + char_count -= nread; + + if (feof(fp)) + break; + + if (ferror(fp)) { + fprintf(stderr, "error reading input\n"); + break; + } + } + } + + fclose(fp); + + if (print_filename) { + puts(""); + } + + return 0; +} diff --git a/Userland/Utilities/hexdump.cpp b/Userland/Utilities/hexdump.cpp new file mode 100644 index 0000000000..a4c4b04cb8 --- /dev/null +++ b/Userland/Utilities/hexdump.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + Core::ArgsParser args_parser; + const char* path = nullptr; + args_parser.add_positional_argument(path, "Input", "input", Core::ArgsParser::Required::No); + + args_parser.parse(argc, argv); + + RefPtr file; + + if (!path) { + file = Core::File::standard_input(); + } else { + auto file_or_error = Core::File::open(path, Core::File::ReadOnly); + if (file_or_error.is_error()) { + warnln("Failed to open {}: {}", path, file_or_error.error()); + return 1; + } + file = file_or_error.value(); + } + + auto contents = file->read_all(); + + Vector line; + + auto print_line = [&] { + for (size_t i = 0; i < 16; ++i) { + if (i < line.size()) + printf("%02x ", line[i]); + else + printf(" "); + + if (i == 7) + printf(" "); + } + + printf(" "); + + for (size_t i = 0; i < 16; ++i) { + if (i < line.size() && isprint(line[i])) + putchar(line[i]); + else + putchar(' '); + } + + putchar('\n'); + }; + + for (size_t i = 0; i < contents.size(); ++i) { + line.append(contents[i]); + + if (line.size() == 16) { + print_line(); + line.clear(); + } + } + + if (!line.is_empty()) + print_line(); + + return 0; +} diff --git a/Userland/Utilities/host.cpp b/Userland/Utilities/host.cpp new file mode 100644 index 0000000000..d77054b868 --- /dev/null +++ b/Userland/Utilities/host.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio dns", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* name_or_ip = nullptr; + Core::ArgsParser args_parser; + args_parser.set_general_help("Convert between domain name and IPv4 address."); + args_parser.add_positional_argument(name_or_ip, "Domain name or IPv4 address", "name"); + args_parser.parse(argc, argv); + + // If input looks like an IPv4 address, we should do a reverse lookup. + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(53); + int rc = inet_pton(AF_INET, name_or_ip, &addr.sin_addr); + if (rc == 1) { + // Okay, let's do a reverse lookup. + auto* hostent = gethostbyaddr(&addr.sin_addr, sizeof(in_addr), AF_INET); + if (!hostent) { + fprintf(stderr, "Reverse lookup failed for '%s'\n", name_or_ip); + return 1; + } + printf("%s is %s\n", name_or_ip, hostent->h_name); + return 0; + } + + auto* hostent = gethostbyname(name_or_ip); + if (!hostent) { + fprintf(stderr, "Lookup failed for '%s'\n", name_or_ip); + return 1; + } + + char buffer[32]; + const char* ip_str = inet_ntop(AF_INET, hostent->h_addr_list[0], buffer, sizeof(buffer)); + + printf("%s is %s\n", name_or_ip, ip_str); + return 0; +} diff --git a/Userland/Utilities/hostname.cpp b/Userland/Utilities/hostname.cpp new file mode 100644 index 0000000000..68c6dfe030 --- /dev/null +++ b/Userland/Utilities/hostname.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + const char* hostname = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(hostname, "Hostname to set", "hostname", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (!hostname) { + char buffer[HOST_NAME_MAX]; + int rc = gethostname(buffer, sizeof(buffer)); + if (rc < 0) { + perror("gethostname"); + return 1; + } + printf("%s\n", buffer); + } else { + if (strlen(hostname) >= HOST_NAME_MAX) { + fprintf(stderr, "Hostname must be less than %i characters\n", HOST_NAME_MAX); + return 1; + } + sethostname(hostname, strlen(hostname)); + } + return 0; +} diff --git a/Userland/Utilities/html.cpp b/Userland/Utilities/html.cpp new file mode 100644 index 0000000000..b6e2524474 --- /dev/null +++ b/Userland/Utilities/html.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + auto app = GUI::Application::construct(argc, argv); + + auto f = Core::File::construct(); + URL url; + bool success; + if (argc < 2) { + success = f->open(STDIN_FILENO, Core::IODevice::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No); + } else { + url = URL::create_with_file_protocol(argv[1]); + f->set_filename(argv[1]); + success = f->open(Core::IODevice::OpenMode::ReadOnly); + } + if (!success) { + fprintf(stderr, "Error: %s\n", f->error_string()); + return 1; + } + + auto html = f->read_all(); + + auto window = GUI::Window::construct(); + window->set_title("HTML"); + auto& widget = window->set_main_widget(); + widget.on_title_change = [&](auto& title) { + window->set_title(String::formatted("{} - HTML", title)); + }; + widget.load_html(html, url); + window->show(); + + auto menubar = GUI::MenuBar::construct(); + + auto& app_menu = menubar->add_menu("HTML"); + app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { + app->quit(); + })); + + auto& help_menu = menubar->add_menu("Help"); + help_menu.add_action(GUI::CommonActions::make_about_action("HTML", GUI::Icon::default_icon("filetype-html"), window)); + + app->set_menubar(move(menubar)); + + window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png")); + + return app->exec(); +} diff --git a/Userland/Utilities/id.cpp b/Userland/Utilities/id.cpp new file mode 100644 index 0000000000..f0caa31508 --- /dev/null +++ b/Userland/Utilities/id.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +static int print_id_objects(); + +static bool flag_print_uid = false; +static bool flag_print_gid = false; +static bool flag_print_name = false; +static bool flag_print_gid_all = false; + +int main(int argc, char** argv) +{ + if (unveil("/etc/passwd", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/group", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil(nullptr, nullptr) < 0) { + perror("unveil"); + return 1; + } + + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Core::ArgsParser args_parser; + args_parser.add_option(flag_print_uid, "Print UID", nullptr, 'u'); + args_parser.add_option(flag_print_gid, "Print GID", nullptr, 'g'); + args_parser.add_option(flag_print_gid_all, "Print all GIDs", nullptr, 'G'); + args_parser.add_option(flag_print_name, "Print name", nullptr, 'n'); + args_parser.parse(argc, argv); + + if (flag_print_name && !(flag_print_uid || flag_print_gid || flag_print_gid_all)) { + fprintf(stderr, "cannot print only names or real IDs in default format\n"); + return 1; + } + + if (flag_print_uid + flag_print_gid + flag_print_gid_all > 1) { + fprintf(stderr, "cannot print \"only\" of more than one choice\n"); + return 1; + } + + int status = print_id_objects(); + return status; +} + +static bool print_uid_object(uid_t uid) +{ + if (flag_print_name) { + struct passwd* pw = getpwuid(uid); + printf("%s", pw ? pw->pw_name : "n/a"); + } else + printf("%u", uid); + + return true; +} + +static bool print_gid_object(gid_t gid) +{ + if (flag_print_name) { + struct group* gr = getgrgid(gid); + printf("%s", gr ? gr->gr_name : "n/a"); + } else + printf("%u", gid); + return true; +} + +static bool print_gid_list() +{ + int extra_gid_count = getgroups(0, nullptr); + if (extra_gid_count) { + auto* extra_gids = (gid_t*)alloca(extra_gid_count * sizeof(gid_t)); + int rc = getgroups(extra_gid_count, extra_gids); + + if (rc < 0) { + perror("\ngetgroups"); + return false; + } + + for (int g = 0; g < extra_gid_count; ++g) { + auto* gr = getgrgid(extra_gids[g]); + if (flag_print_name && gr) + printf("%s", gr->gr_name); + else + printf("%u", extra_gids[g]); + if (g != extra_gid_count - 1) + printf(" "); + } + } + return true; +} + +static bool print_full_id_list() +{ + + uid_t uid = getuid(); + gid_t gid = getgid(); + struct passwd* pw = getpwuid(uid); + struct group* gr = getgrgid(gid); + + printf("uid=%u(%s) gid=%u(%s)", uid, pw ? pw->pw_name : "n/a", gid, gr ? gr->gr_name : "n/a"); + + int extra_gid_count = getgroups(0, nullptr); + if (extra_gid_count) { + auto* extra_gids = (gid_t*)alloca(extra_gid_count * sizeof(gid_t)); + int rc = getgroups(extra_gid_count, extra_gids); + if (rc < 0) { + perror("\ngetgroups"); + return false; + } + printf(" groups="); + for (int g = 0; g < extra_gid_count; ++g) { + auto* gr = getgrgid(extra_gids[g]); + if (gr) + printf("%u(%s)", extra_gids[g], gr->gr_name); + else + printf("%u", extra_gids[g]); + if (g != extra_gid_count - 1) + printf(","); + } + } + return true; +} + +static int print_id_objects() +{ + if (flag_print_uid) { + if (!print_uid_object(getuid())) + return 1; + } else if (flag_print_gid) { + if (!print_gid_object(getgid())) + return 1; + } else if (flag_print_gid_all) { + if (!print_gid_list()) + return 1; + } else { + if (!print_full_id_list()) + return 1; + } + + printf("\n"); + return 0; +} diff --git a/Userland/Utilities/ifconfig.cpp b/Userland/Utilities/ifconfig.cpp new file mode 100644 index 0000000000..2a1b000d9b --- /dev/null +++ b/Userland/Utilities/ifconfig.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + const char* value_ipv4 = nullptr; + const char* value_adapter = nullptr; + const char* value_gateway = nullptr; + const char* value_mask = nullptr; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Display or modify the configuration of each network interface."); + args_parser.add_option(value_ipv4, "Set the IP address of the selected network", "ipv4", 'i', "The new IP of the network"); + args_parser.add_option(value_adapter, "Select a specific network adapter to configure", "adapter", 'a', "The name of a network adapter"); + args_parser.add_option(value_gateway, "Set the default gateway of the selected network", "gateway", 'g', "The new IP of the gateway"); + args_parser.add_option(value_mask, "Set the network mask of the selected network", "mask", 'm', "The new network mask"); + args_parser.parse(argc, argv); + + if (!value_ipv4 && !value_adapter && !value_gateway && !value_mask) { + + auto file = Core::File::construct("/proc/net/adapters"); + if (!file->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Error: %s\n", file->error_string()); + return 1; + } + + auto file_contents = file->read_all(); + auto json = JsonValue::from_string(file_contents); + ASSERT(json.has_value()); + json.value().as_array().for_each([](auto& value) { + auto if_object = value.as_object(); + + auto name = if_object.get("name").to_string(); + auto class_name = if_object.get("class_name").to_string(); + auto mac_address = if_object.get("mac_address").to_string(); + auto ipv4_address = if_object.get("ipv4_address").to_string(); + auto gateway = if_object.get("ipv4_gateway").to_string(); + auto netmask = if_object.get("ipv4_netmask").to_string(); + auto packets_in = if_object.get("packets_in").to_u32(); + auto bytes_in = if_object.get("bytes_in").to_u32(); + auto packets_out = if_object.get("packets_out").to_u32(); + auto bytes_out = if_object.get("bytes_out").to_u32(); + auto mtu = if_object.get("mtu").to_u32(); + + printf("%s:\n", name.characters()); + printf("\tmac: %s\n", mac_address.characters()); + printf("\tipv4: %s\n", ipv4_address.characters()); + printf("\tnetmask: %s\n", netmask.characters()); + printf("\tgateway: %s\n", gateway.characters()); + printf("\tclass: %s\n", class_name.characters()); + printf("\tRX: %u packets %u bytes (%s)\n", packets_in, bytes_in, human_readable_size(bytes_in).characters()); + printf("\tTX: %u packets %u bytes (%s)\n", packets_out, bytes_out, human_readable_size(bytes_out).characters()); + printf("\tMTU: %u\n", mtu); + printf("\n"); + }); + } else { + + if (!value_adapter) { + fprintf(stderr, "No network adapter was specified.\n"); + return 1; + } + + String ifname = value_adapter; + + if (value_ipv4) { + auto address = IPv4Address::from_string(value_ipv4); + + if (!address.has_value()) { + fprintf(stderr, "Invalid IPv4 address: '%s'\n", value_ipv4); + return 1; + } + + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) { + perror("socket"); + return 1; + } + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + + bool fits = ifname.copy_characters_to_buffer(ifr.ifr_name, IFNAMSIZ); + if (!fits) { + fprintf(stderr, "Interface name '%s' is too long\n", ifname.characters()); + return 1; + } + ifr.ifr_addr.sa_family = AF_INET; + ((sockaddr_in&)ifr.ifr_addr).sin_addr.s_addr = address.value().to_in_addr_t(); + + int rc = ioctl(fd, SIOCSIFADDR, &ifr); + if (rc < 0) { + perror("ioctl(SIOCSIFADDR)"); + return 1; + } + } + + if (value_mask) { + auto address = IPv4Address::from_string(value_mask); + + if (!address.has_value()) { + fprintf(stderr, "Invalid IPv4 mask: '%s'\n", value_mask); + return 1; + } + + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) { + perror("socket"); + return 1; + } + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + + bool fits = ifname.copy_characters_to_buffer(ifr.ifr_name, IFNAMSIZ); + if (!fits) { + fprintf(stderr, "Interface name '%s' is too long\n", ifname.characters()); + return 1; + } + ifr.ifr_netmask.sa_family = AF_INET; + ((sockaddr_in&)ifr.ifr_netmask).sin_addr.s_addr = address.value().to_in_addr_t(); + + int rc = ioctl(fd, SIOCSIFNETMASK, &ifr); + if (rc < 0) { + perror("ioctl(SIOCSIFNETMASK)"); + return 1; + } + } + + if (value_gateway) { + auto address = IPv4Address::from_string(value_gateway); + + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) { + perror("socket"); + return 1; + } + + struct rtentry rt; + memset(&rt, 0, sizeof(rt)); + + rt.rt_dev = const_cast(ifname.characters()); + rt.rt_gateway.sa_family = AF_INET; + ((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = address.value().to_in_addr_t(); + rt.rt_flags = RTF_UP | RTF_GATEWAY; + + int rc = ioctl(fd, SIOCADDRT, &rt); + if (rc < 0) { + perror("ioctl(SIOCADDRT)"); + return 1; + } + } + } + return 0; +} diff --git a/Userland/Utilities/ini.cpp b/Userland/Utilities/ini.cpp new file mode 100644 index 0000000000..5ac2c445a3 --- /dev/null +++ b/Userland/Utilities/ini.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2020, Linus Groh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath wpath cpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* path = nullptr; + const char* group = nullptr; + const char* key = nullptr; + const char* value_to_write = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(path, "Path to INI file", "path"); + args_parser.add_positional_argument(group, "Group name", "group"); + args_parser.add_positional_argument(key, "Key name", "key"); + args_parser.add_positional_argument(value_to_write, "Value to write", "value", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (!Core::File::exists(path)) { + fprintf(stderr, "File does not exist: '%s'\n", path); + return 1; + } + + auto config = Core::ConfigFile::open(path); + + if (value_to_write) { + config->write_entry(group, key, value_to_write); + config->sync(); + return 0; + } + + auto value = config->read_entry(group, key); + if (!value.is_empty()) + printf("%s\n", value.characters()); + + return 0; +} diff --git a/Userland/Utilities/jp.cpp b/Userland/Utilities/jp.cpp new file mode 100644 index 0000000000..cb7bef1f95 --- /dev/null +++ b/Userland/Utilities/jp.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void print(const JsonValue& value, int indent = 0, bool use_color = true); +static void print_indent(int indent) +{ + for (int i = 0; i < indent; ++i) + out(" "); +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* path = nullptr; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Pretty-print a JSON file with syntax-coloring and indentation."); + args_parser.add_positional_argument(path, "Path to JSON file", "path"); + args_parser.parse(argc, argv); + + auto file = Core::File::construct(path); + if (!file->open(Core::IODevice::ReadOnly)) { + warnln("Couldn't open {} for reading: {}", path, file->error_string()); + return 1; + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto file_contents = file->read_all(); + auto json = JsonValue::from_string(file_contents); + if (!json.has_value()) { + warnln("Couldn't parse {} as JSON", path); + return 1; + } + + print(json.value(), 0, isatty(STDOUT_FILENO)); + outln(); + + return 0; +} + +void print(const JsonValue& value, int indent, bool use_color) +{ + if (value.is_object()) { + size_t printed_members = 0; + auto object = value.as_object(); + outln("{{"); + object.for_each_member([&](auto& member_name, auto& member_value) { + ++printed_members; + print_indent(indent + 1); + if (use_color) + out("\"\033[33;1m{}\033[0m\": ", member_name); + else + out("\"{}\": ", member_name); + print(member_value, indent + 1, use_color); + if (printed_members < static_cast(object.size())) + out(","); + outln(); + }); + print_indent(indent); + out("}}"); + return; + } + if (value.is_array()) { + size_t printed_entries = 0; + auto array = value.as_array(); + outln("["); + array.for_each([&](auto& entry_value) { + ++printed_entries; + print_indent(indent + 1); + print(entry_value, indent + 1, use_color); + if (printed_entries < static_cast(array.size())) + out(","); + outln(); + }); + print_indent(indent); + out("]"); + return; + } + if (use_color) { + if (value.is_string()) + out("\033[31;1m"); + else if (value.is_number()) + out("\033[35;1m"); + else if (value.is_bool()) + out("\033[32;1m"); + else if (value.is_null()) + out("\033[34;1m"); + } + if (value.is_string()) + out("\""); + out("{}", value.to_string()); + if (value.is_string()) + out("\""); + if (use_color) + out("\033[0m"); +} diff --git a/Userland/Utilities/js.cpp b/Userland/Utilities/js.cpp new file mode 100644 index 0000000000..036c097d7a --- /dev/null +++ b/Userland/Utilities/js.cpp @@ -0,0 +1,918 @@ +/* + * Copyright (c) 2020, Andreas Kling + * Copyright (c) 2020, Linus Groh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#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 +#include +#include +#include + +RefPtr vm; +Vector repl_statements; + +class ReplObject : public JS::GlobalObject { +public: + ReplObject(); + virtual void initialize() override; + virtual ~ReplObject() override; + +private: + virtual const char* class_name() const override { return "ReplObject"; } + + JS_DECLARE_NATIVE_FUNCTION(exit_interpreter); + JS_DECLARE_NATIVE_FUNCTION(repl_help); + JS_DECLARE_NATIVE_FUNCTION(load_file); + JS_DECLARE_NATIVE_FUNCTION(save_to_file); +}; + +static bool s_dump_ast = false; +static bool s_print_last_result = false; +static RefPtr s_editor; +static String s_history_path = String::formatted("{}/.js-history", Core::StandardPaths::home_directory()); +static int s_repl_line_level = 0; +static bool s_fail_repl = false; + +static String prompt_for_level(int level) +{ + static StringBuilder prompt_builder; + prompt_builder.clear(); + prompt_builder.append("> "); + + for (auto i = 0; i < level; ++i) + prompt_builder.append(" "); + + return prompt_builder.build(); +} + +static String read_next_piece() +{ + StringBuilder piece; + + auto line_level_delta_for_next_line { 0 }; + + do { + auto line_result = s_editor->get_line(prompt_for_level(s_repl_line_level)); + + line_level_delta_for_next_line = 0; + + if (line_result.is_error()) { + s_fail_repl = true; + return ""; + } + + auto& line = line_result.value(); + s_editor->add_to_history(line); + + piece.append(line); + auto lexer = JS::Lexer(line); + + enum { + NotInLabelOrObjectKey, + InLabelOrObjectKeyIdentifier, + InLabelOrObjectKey + } label_state { NotInLabelOrObjectKey }; + + for (JS::Token token = lexer.next(); token.type() != JS::TokenType::Eof; token = lexer.next()) { + switch (token.type()) { + case JS::TokenType::BracketOpen: + case JS::TokenType::CurlyOpen: + case JS::TokenType::ParenOpen: + label_state = NotInLabelOrObjectKey; + s_repl_line_level++; + break; + case JS::TokenType::BracketClose: + case JS::TokenType::CurlyClose: + case JS::TokenType::ParenClose: + label_state = NotInLabelOrObjectKey; + s_repl_line_level--; + break; + + case JS::TokenType::Identifier: + case JS::TokenType::StringLiteral: + if (label_state == NotInLabelOrObjectKey) + label_state = InLabelOrObjectKeyIdentifier; + else + label_state = NotInLabelOrObjectKey; + break; + case JS::TokenType::Colon: + if (label_state == InLabelOrObjectKeyIdentifier) + label_state = InLabelOrObjectKey; + else + label_state = NotInLabelOrObjectKey; + break; + default: + break; + } + } + + if (label_state == InLabelOrObjectKey) { + // If there's a label or object literal key at the end of this line, + // prompt for more lines but do not change the line level. + line_level_delta_for_next_line += 1; + } + } while (s_repl_line_level + line_level_delta_for_next_line > 0); + + return piece.to_string(); +} + +static void print_value(JS::Value value, HashTable& seen_objects); + +static void print_type(const FlyString& name) +{ + out("[\033[36;1m{}\033[0m]", name); +} + +static void print_separator(bool& first) +{ + out(first ? " " : ", "); + first = false; +} + +static void print_array(JS::Array& array, HashTable& seen_objects) +{ + out("["); + bool first = true; + for (auto it = array.indexed_properties().begin(false); it != array.indexed_properties().end(); ++it) { + print_separator(first); + auto value = it.value_and_attributes(&array).value; + // The V8 repl doesn't throw an exception here, and instead just + // prints 'undefined'. We may choose to replicate that behavior in + // the future, but for now lets just catch the error + if (vm->exception()) + return; + print_value(value, seen_objects); + } + if (!first) + out(" "); + out("]"); +} + +static void print_object(JS::Object& object, HashTable& seen_objects) +{ + out("{{"); + bool first = true; + for (auto& entry : object.indexed_properties()) { + print_separator(first); + out("\"\033[33;1m{}\033[0m\": ", entry.index()); + auto value = entry.value_and_attributes(&object).value; + // The V8 repl doesn't throw an exception here, and instead just + // prints 'undefined'. We may choose to replicate that behavior in + // the future, but for now lets just catch the error + if (vm->exception()) + return; + print_value(value, seen_objects); + } + for (auto& it : object.shape().property_table_ordered()) { + print_separator(first); + if (it.key.is_string()) { + out("\"\033[33;1m{}\033[0m\": ", it.key.to_display_string()); + } else { + out("[\033[33;1m{}\033[0m]: ", it.key.to_display_string()); + } + print_value(object.get_direct(it.value.offset), seen_objects); + } + if (!first) + out(" "); + out("}}"); +} + +static void print_function(const JS::Object& object, HashTable&) +{ + print_type(object.class_name()); + if (is(object)) + out(" {}", static_cast(object).name()); + else if (is(object)) + out(" {}", static_cast(object).name()); +} + +static void print_date(const JS::Object& date, HashTable&) +{ + print_type("Date"); + out(" \033[34;1m{}\033[0m", static_cast(date).string()); +} + +static void print_error(const JS::Object& object, HashTable&) +{ + auto& error = static_cast(object); + print_type(error.name()); + if (!error.message().is_empty()) + out(" \033[31;1m{}\033[0m", error.message()); +} + +static void print_regexp_object(const JS::Object& object, HashTable&) +{ + auto& regexp_object = static_cast(object); + // Use RegExp.prototype.source rather than RegExpObject::pattern() so we get proper escaping + auto source = regexp_object.get("source").to_primitive_string(object.global_object())->string(); + print_type("RegExp"); + out(" \033[34;1m/{}/{}\033[0m", source, regexp_object.flags()); +} + +static void print_proxy_object(const JS::Object& object, HashTable& seen_objects) +{ + auto& proxy_object = static_cast(object); + print_type("Proxy"); + out("\n target: "); + print_value(&proxy_object.target(), seen_objects); + out("\n handler: "); + print_value(&proxy_object.handler(), seen_objects); +} + +static void print_array_buffer(const JS::Object& object, HashTable& seen_objects) +{ + auto& array_buffer = static_cast(object); + auto& buffer = array_buffer.buffer(); + auto byte_length = array_buffer.byte_length(); + print_type("ArrayBuffer"); + out("\n byteLength: "); + print_value(JS::Value((double)byte_length), seen_objects); + outln(); + for (size_t i = 0; i < byte_length; ++i) { + out("{:02x}", buffer[i]); + if (i + 1 < byte_length) { + if ((i + 1) % 32 == 0) + outln(); + else if ((i + 1) % 16 == 0) + out(" "); + else + out(" "); + } + } +} + +static void print_typed_array(const JS::Object& object, HashTable& seen_objects) +{ + auto& typed_array_base = static_cast(object); + auto length = typed_array_base.array_length(); + print_type(object.class_name()); + out("\n length: "); + print_value(JS::Value(length), seen_objects); + out("\n byteLength: "); + print_value(JS::Value(typed_array_base.byte_length()), seen_objects); + out("\n buffer: "); + print_type("ArrayBuffer"); + out(" @ {:p}", typed_array_base.viewed_array_buffer()); + if (!length) + return; + outln(); + // FIXME: This kinda sucks. +#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ + if (StringView(object.class_name()) == StringView(#ClassName)) { \ + out("[ "); \ + auto& typed_array = static_cast(typed_array_base); \ + auto data = typed_array.data(); \ + for (size_t i = 0; i < length; ++i) { \ + if (i > 0) \ + out(", "); \ + print_value(JS::Value(data[i]), seen_objects); \ + } \ + out(" ]"); \ + return; \ + } + JS_ENUMERATE_TYPED_ARRAYS +#undef __JS_ENUMERATE + ASSERT_NOT_REACHED(); +} + +static void print_primitive_wrapper_object(const FlyString& name, const JS::Object& object, HashTable& seen_objects) +{ + // BooleanObject, NumberObject, StringObject + print_type(name); + out(" "); + print_value(object.value_of(), seen_objects); +} + +static void print_value(JS::Value value, HashTable& seen_objects) +{ + if (value.is_empty()) { + out("\033[34;1m\033[0m"); + return; + } + + if (value.is_object()) { + if (seen_objects.contains(&value.as_object())) { + // FIXME: Maybe we should only do this for circular references, + // not for all reoccurring objects. + out("", &value.as_object()); + return; + } + seen_objects.set(&value.as_object()); + } + + if (value.is_array()) + return print_array(static_cast(value.as_object()), seen_objects); + + if (value.is_object()) { + auto& object = value.as_object(); + if (object.is_function()) + return print_function(object, seen_objects); + if (is(object)) + return print_date(object, seen_objects); + if (is(object)) + return print_error(object, seen_objects); + if (is(object)) + return print_regexp_object(object, seen_objects); + if (is(object)) + return print_proxy_object(object, seen_objects); + if (is(object)) + return print_array_buffer(object, seen_objects); + if (object.is_typed_array()) + return print_typed_array(object, seen_objects); + if (is(object)) + return print_primitive_wrapper_object("String", object, seen_objects); + if (is(object)) + return print_primitive_wrapper_object("Number", object, seen_objects); + if (is(object)) + return print_primitive_wrapper_object("Boolean", object, seen_objects); + return print_object(object, seen_objects); + } + + if (value.is_string()) + out("\033[32;1m"); + else if (value.is_number() || value.is_bigint()) + out("\033[35;1m"); + else if (value.is_boolean()) + out("\033[33;1m"); + else if (value.is_null()) + out("\033[33;1m"); + else if (value.is_undefined()) + out("\033[34;1m"); + if (value.is_string()) + out("\""); + else if (value.is_negative_zero()) + out("-"); + out("{}", value.to_string_without_side_effects()); + if (value.is_string()) + out("\""); + out("\033[0m"); +} + +static void print(JS::Value value) +{ + HashTable seen_objects; + print_value(value, seen_objects); + outln(); +} + +static bool file_has_shebang(AK::ByteBuffer file_contents) +{ + if (file_contents.size() >= 2 && file_contents[0] == '#' && file_contents[1] == '!') + return true; + return false; +} + +static StringView strip_shebang(AK::ByteBuffer file_contents) +{ + size_t i = 0; + for (i = 2; i < file_contents.size(); ++i) { + if (file_contents[i] == '\n') + break; + } + return StringView((const char*)file_contents.data() + i, file_contents.size() - i); +} + +static bool write_to_file(const StringView& path) +{ + int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666); + for (size_t i = 0; i < repl_statements.size(); i++) { + auto line = repl_statements[i]; + if (line.length() && i != repl_statements.size() - 1) { + ssize_t nwritten = write(fd, line.characters(), line.length()); + if (nwritten < 0) { + close(fd); + return false; + } + } + if (i != repl_statements.size() - 1) { + char ch = '\n'; + ssize_t nwritten = write(fd, &ch, 1); + if (nwritten != 1) { + perror("write"); + close(fd); + return false; + } + } + } + close(fd); + return true; +} + +static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source) +{ + auto parser = JS::Parser(JS::Lexer(source)); + auto program = parser.parse_program(); + + if (s_dump_ast) + program->dump(0); + + if (parser.has_errors()) { + auto error = parser.errors()[0]; + auto hint = error.source_location_hint(source); + if (!hint.is_empty()) + outln("{}", hint); + vm->throw_exception(interpreter.global_object(), error.to_string()); + } else { + interpreter.run(interpreter.global_object(), *program); + } + + if (vm->exception()) { + out("Uncaught exception: "); + print(vm->exception()->value()); + auto trace = vm->exception()->trace(); + if (trace.size() > 1) { + unsigned repetitions = 0; + for (size_t i = 0; i < trace.size(); ++i) { + auto& function_name = trace[i]; + if (i + 1 < trace.size() && trace[i + 1] == function_name) { + repetitions++; + continue; + } + if (repetitions > 4) { + // If more than 5 (1 + >4) consecutive function calls with the same name, print + // the name only once and show the number of repetitions instead. This prevents + // printing ridiculously large call stacks of recursive functions. + outln(" -> {}", function_name); + outln(" {} more calls", repetitions); + } else { + for (size_t j = 0; j < repetitions + 1; ++j) + outln(" -> {}", function_name); + } + repetitions = 0; + } + } + vm->clear_exception(); + return false; + } + if (s_print_last_result) + print(vm->last_value()); + return true; +} + +ReplObject::ReplObject() +{ +} + +void ReplObject::initialize() +{ + GlobalObject::initialize(); + define_property("global", this, JS::Attribute::Enumerable); + define_native_function("exit", exit_interpreter); + define_native_function("help", repl_help); + define_native_function("load", load_file, 1); + define_native_function("save", save_to_file, 1); +} + +ReplObject::~ReplObject() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(ReplObject::save_to_file) +{ + if (!vm.argument_count()) + return JS::Value(false); + String save_path = vm.argument(0).to_string_without_side_effects(); + StringView path = StringView(save_path.characters()); + if (write_to_file(path)) { + return JS::Value(true); + } + return JS::Value(false); +} + +JS_DEFINE_NATIVE_FUNCTION(ReplObject::exit_interpreter) +{ + if (!vm.argument_count()) + exit(0); + auto exit_code = vm.argument(0).to_number(global_object); + if (::vm->exception()) + return {}; + exit(exit_code.as_double()); +} + +JS_DEFINE_NATIVE_FUNCTION(ReplObject::repl_help) +{ + outln("REPL commands:"); + outln(" exit(code): exit the REPL with specified code. Defaults to 0."); + outln(" help(): display this menu"); + outln(" load(files): accepts file names as params to load into running session. For example load(\"js/1.js\", \"js/2.js\", \"js/3.js\")"); + outln(" save(file): accepts a file name, writes REPL input history to a file. For example: save(\"foo.txt\")"); + return JS::js_undefined(); +} + +JS_DEFINE_NATIVE_FUNCTION(ReplObject::load_file) +{ + if (!vm.argument_count()) + return JS::Value(false); + + for (auto& file : vm.call_frame().arguments) { + String file_name = file.as_string().string(); + auto js_file = Core::File::construct(file_name); + if (!js_file->open(Core::IODevice::ReadOnly)) { + warnln("Failed to open {}: {}", file_name, js_file->error_string()); + continue; + } + auto file_contents = js_file->read_all(); + + StringView source; + if (file_has_shebang(file_contents)) { + source = strip_shebang(file_contents); + } else { + source = file_contents; + } + parse_and_run(vm.interpreter(), source); + } + return JS::Value(true); +} + +static void repl(JS::Interpreter& interpreter) +{ + while (!s_fail_repl) { + String piece = read_next_piece(); + if (piece.is_empty()) + continue; + repl_statements.append(piece); + parse_and_run(interpreter, piece); + } +} + +static Function interrupt_interpreter; +static void sigint_handler() +{ + interrupt_interpreter(); +} + +class ReplConsoleClient final : public JS::ConsoleClient { +public: + ReplConsoleClient(JS::Console& console) + : ConsoleClient(console) + { + } + + virtual JS::Value log() override + { + outln("{}", vm().join_arguments()); + return JS::js_undefined(); + } + virtual JS::Value info() override + { + outln("(i) {}", vm().join_arguments()); + return JS::js_undefined(); + } + virtual JS::Value debug() override + { + outln("\033[36;1m{}\033[0m", vm().join_arguments()); + return JS::js_undefined(); + } + virtual JS::Value warn() override + { + outln("\033[33;1m{}\033[0m", vm().join_arguments()); + return JS::js_undefined(); + } + virtual JS::Value error() override + { + outln("\033[31;1m{}\033[0m", vm().join_arguments()); + return JS::js_undefined(); + } + virtual JS::Value clear() override + { + out("\033[3J\033[H\033[2J"); + fflush(stdout); + return JS::js_undefined(); + } + virtual JS::Value trace() override + { + outln("{}", vm().join_arguments()); + auto trace = get_trace(); + for (auto& function_name : trace) { + if (function_name.is_empty()) + function_name = ""; + outln(" -> {}", function_name); + } + return JS::js_undefined(); + } + virtual JS::Value count() override + { + auto label = vm().argument_count() ? vm().argument(0).to_string_without_side_effects() : "default"; + auto counter_value = m_console.counter_increment(label); + outln("{}: {}", label, counter_value); + return JS::js_undefined(); + } + virtual JS::Value count_reset() override + { + auto label = vm().argument_count() ? vm().argument(0).to_string_without_side_effects() : "default"; + if (m_console.counter_reset(label)) + outln("{}: 0", label); + else + outln("\033[33;1m\"{}\" doesn't have a count\033[0m", label); + return JS::js_undefined(); + } +}; + +int main(int argc, char** argv) +{ + bool gc_on_every_allocation = false; + bool disable_syntax_highlight = false; + const char* script_path = nullptr; + + Core::ArgsParser args_parser; + args_parser.set_general_help("This is a JavaScript interpreter."); + args_parser.add_option(s_dump_ast, "Dump the AST", "dump-ast", 'A'); + args_parser.add_option(s_print_last_result, "Print last result", "print-last-result", 'l'); + args_parser.add_option(gc_on_every_allocation, "GC on every allocation", "gc-on-every-allocation", 'g'); + args_parser.add_option(disable_syntax_highlight, "Disable live syntax highlighting", "no-syntax-highlight", 's'); + args_parser.add_positional_argument(script_path, "Path to script file", "script", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + bool syntax_highlight = !disable_syntax_highlight; + + vm = JS::VM::create(); + OwnPtr interpreter; + + interrupt_interpreter = [&] { + auto error = JS::Error::create(interpreter->global_object(), "Error", "Received SIGINT"); + vm->throw_exception(interpreter->global_object(), error); + }; + + if (script_path == nullptr) { + s_print_last_result = true; + interpreter = JS::Interpreter::create(*vm); + ReplConsoleClient console_client(interpreter->global_object().console()); + interpreter->global_object().console().set_client(console_client); + interpreter->heap().set_should_collect_on_every_allocation(gc_on_every_allocation); + interpreter->vm().set_underscore_is_last_value(true); + + s_editor = Line::Editor::construct(); + s_editor->load_history(s_history_path); + + signal(SIGINT, [](int) { + if (!s_editor->is_editing()) + sigint_handler(); + s_editor->save_history(s_history_path); + }); + + s_editor->on_display_refresh = [syntax_highlight](Line::Editor& editor) { + auto stylize = [&](Line::Span span, Line::Style styles) { + if (syntax_highlight) + editor.stylize(span, styles); + }; + editor.strip_styles(); + + size_t open_indents = s_repl_line_level; + + auto line = editor.line(); + JS::Lexer lexer(line); + bool indenters_starting_line = true; + for (JS::Token token = lexer.next(); token.type() != JS::TokenType::Eof; token = lexer.next()) { + auto length = token.value().length(); + auto start = token.line_column() - 1; + auto end = start + length; + if (indenters_starting_line) { + if (token.type() != JS::TokenType::ParenClose && token.type() != JS::TokenType::BracketClose && token.type() != JS::TokenType::CurlyClose) { + indenters_starting_line = false; + } else { + --open_indents; + } + } + + switch (token.category()) { + case JS::TokenCategory::Invalid: + stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Red), Line::Style::Underline }); + break; + case JS::TokenCategory::Number: + stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Magenta) }); + break; + case JS::TokenCategory::String: + stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Green), Line::Style::Bold }); + break; + case JS::TokenCategory::Punctuation: + break; + case JS::TokenCategory::Operator: + break; + case JS::TokenCategory::Keyword: + switch (token.type()) { + case JS::TokenType::BoolLiteral: + case JS::TokenType::NullLiteral: + stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow), Line::Style::Bold }); + break; + default: + stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Bold }); + break; + } + break; + case JS::TokenCategory::ControlKeyword: + stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan), Line::Style::Italic }); + break; + case JS::TokenCategory::Identifier: + stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::White), Line::Style::Bold }); + default: + break; + } + } + + editor.set_prompt(prompt_for_level(open_indents)); + }; + + auto complete = [&interpreter](const Line::Editor& editor) -> Vector { + auto line = editor.line(editor.cursor()); + + JS::Lexer lexer { line }; + enum { + Initial, + CompleteVariable, + CompleteNullProperty, + CompleteProperty, + } mode { Initial }; + + StringView variable_name; + StringView property_name; + + // we're only going to complete either + // - + // where N is part of the name of a variable + // - .

+ // where N is the complete name of a variable and + // P is part of the name of one of its properties + auto js_token = lexer.next(); + for (; js_token.type() != JS::TokenType::Eof; js_token = lexer.next()) { + switch (mode) { + case CompleteVariable: + switch (js_token.type()) { + case JS::TokenType::Period: + // ... + mode = CompleteNullProperty; + break; + default: + // not a dot, reset back to initial + mode = Initial; + break; + } + break; + case CompleteNullProperty: + if (js_token.is_identifier_name()) { + // ... + mode = CompleteProperty; + property_name = js_token.value(); + } else { + mode = Initial; + } + break; + case CompleteProperty: + // something came after the property access, reset to initial + case Initial: + if (js_token.is_identifier_name()) { + // ...... + mode = CompleteVariable; + variable_name = js_token.value(); + } else { + mode = Initial; + } + break; + } + } + + bool last_token_has_trivia = js_token.trivia().length() > 0; + + if (mode == CompleteNullProperty) { + mode = CompleteProperty; + property_name = ""; + last_token_has_trivia = false; // [tab] is sensible to complete. + } + + if (mode == Initial || last_token_has_trivia) + return {}; // we do not know how to complete this + + Vector results; + + Function list_all_properties = [&results, &list_all_properties](const JS::Shape& shape, auto& property_pattern) { + for (const auto& descriptor : shape.property_table()) { + if (!descriptor.key.is_string()) + continue; + auto key = descriptor.key.as_string(); + if (key.view().starts_with(property_pattern)) { + Line::CompletionSuggestion completion { key, Line::CompletionSuggestion::ForSearch }; + if (!results.contains_slow(completion)) { // hide duplicates + results.append(key); + } + } + } + if (const auto* prototype = shape.prototype()) { + list_all_properties(prototype->shape(), property_pattern); + } + }; + + switch (mode) { + case CompleteProperty: { + auto maybe_variable = vm->get_variable(variable_name, interpreter->global_object()); + if (maybe_variable.is_empty()) { + maybe_variable = interpreter->global_object().get(FlyString(variable_name)); + if (maybe_variable.is_empty()) + break; + } + + auto variable = maybe_variable; + if (!variable.is_object()) + break; + + const auto* object = variable.to_object(interpreter->global_object()); + const auto& shape = object->shape(); + list_all_properties(shape, property_name); + if (results.size()) + editor.suggest(property_name.length()); + break; + } + case CompleteVariable: { + const auto& variable = interpreter->global_object(); + list_all_properties(variable.shape(), variable_name); + if (results.size()) + editor.suggest(variable_name.length()); + break; + } + default: + ASSERT_NOT_REACHED(); + } + + return results; + }; + s_editor->on_tab_complete = move(complete); + repl(*interpreter); + s_editor->save_history(s_history_path); + } else { + interpreter = JS::Interpreter::create(*vm); + ReplConsoleClient console_client(interpreter->global_object().console()); + interpreter->global_object().console().set_client(console_client); + interpreter->heap().set_should_collect_on_every_allocation(gc_on_every_allocation); + + signal(SIGINT, [](int) { + sigint_handler(); + }); + + auto file = Core::File::construct(script_path); + if (!file->open(Core::IODevice::ReadOnly)) { + warnln("Failed to open {}: {}", script_path, file->error_string()); + return 1; + } + auto file_contents = file->read_all(); + + StringView source; + if (file_has_shebang(file_contents)) { + source = strip_shebang(file_contents); + } else { + source = file_contents; + } + + if (!parse_and_run(*interpreter, source)) + return 1; + } + + return 0; +} diff --git a/Userland/Utilities/keymap.cpp b/Userland/Utilities/keymap.cpp new file mode 100644 index 0000000000..c057187015 --- /dev/null +++ b/Userland/Utilities/keymap.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio setkeymap rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/res/keymaps", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil(nullptr, nullptr) < 0) { + perror("unveil"); + return 1; + } + + const char* path = nullptr; + Core::ArgsParser args_parser; + args_parser.add_positional_argument(path, "The mapping file to be used", "file"); + args_parser.parse(argc, argv); + + Keyboard::CharacterMap character_map(path); + int rc = character_map.set_system_map(); + if (rc != 0) + fprintf(stderr, "%s\n", strerror(-rc)); + + return rc; +} diff --git a/Userland/Utilities/kill.cpp b/Userland/Utilities/kill.cpp new file mode 100644 index 0000000000..e29a6fd54e --- /dev/null +++ b/Userland/Utilities/kill.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static void print_usage_and_exit() +{ + printf("usage: kill [-signal] \n"); + exit(1); +} + +int main(int argc, char** argv) +{ + if (pledge("stdio proc", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (argc == 2 && !strcmp(argv[1], "-l")) { + for (size_t i = 0; i < NSIG; ++i) { + if (i && !(i % 5)) + outln(""); + out("{:2}) {:10}", i, getsignalname(i)); + } + outln(""); + return 0; + } + + if (argc != 2 && argc != 3) + print_usage_and_exit(); + unsigned signum = SIGTERM; + int pid_argi = 1; + if (argc == 3) { + pid_argi = 2; + if (argv[1][0] != '-') + print_usage_and_exit(); + + Optional number; + + if (isalpha(argv[1][1])) { + int value = getsignalbyname(&argv[1][1]); + if (value >= 0 && value < NSIG) + number = value; + } + + if (!number.has_value()) + number = StringView(&argv[1][1]).to_uint(); + + if (!number.has_value()) { + printf("'%s' is not a valid signal name or number\n", &argv[1][1]); + return 2; + } + signum = number.value(); + } + auto pid_opt = String(argv[pid_argi]).to_int(); + if (!pid_opt.has_value()) { + printf("'%s' is not a valid PID\n", argv[pid_argi]); + return 3; + } + pid_t pid = pid_opt.value(); + + int rc = kill(pid, signum); + if (rc < 0) + perror("kill"); + return 0; +} diff --git a/Userland/Utilities/killall.cpp b/Userland/Utilities/killall.cpp new file mode 100644 index 0000000000..1caae0a112 --- /dev/null +++ b/Userland/Utilities/killall.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +static void print_usage_and_exit() +{ + printf("usage: killall [-signal] process_name\n"); + exit(1); +} + +static int kill_all(const String& process_name, const unsigned signum) +{ + auto processes = Core::ProcessStatisticsReader().get_all(); + if (!processes.has_value()) + return 1; + + for (auto& it : processes.value()) { + if (it.value.name == process_name) { + int ret = kill(it.value.pid, signum); + if (ret < 0) + perror("kill"); + } + } + + return 0; +} + +int main(int argc, char** argv) +{ + unsigned signum = SIGTERM; + int name_argi = 1; + + if (argc != 2 && argc != 3) + print_usage_and_exit(); + + if (argc == 3) { + name_argi = 2; + + if (argv[1][0] != '-') + print_usage_and_exit(); + + Optional number; + + if (isalpha(argv[1][1])) { + int value = getsignalbyname(&argv[1][1]); + if (value >= 0 && value < NSIG) + number = value; + } + + if (!number.has_value()) + number = String(&argv[1][1]).to_uint(); + + if (!number.has_value()) { + printf("'%s' is not a valid signal name or number\n", &argv[1][1]); + return 2; + } + signum = number.value(); + } + + return kill_all(argv[name_argi], signum); +} diff --git a/Userland/Utilities/ln.cpp b/Userland/Utilities/ln.cpp new file mode 100644 index 0000000000..ac9077ec0b --- /dev/null +++ b/Userland/Utilities/ln.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio cpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool symbolic = false; + const char* target = nullptr; + const char* path = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(symbolic, "Create a symlink", "symbolic", 's'); + args_parser.add_positional_argument(target, "Link target", "target"); + args_parser.add_positional_argument(path, "Link path", "path", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + String path_buffer; + if (!path) { + path_buffer = LexicalPath(target).basename(); + path = path_buffer.characters(); + } + + if (symbolic) { + int rc = symlink(target, path); + if (rc < 0) { + perror("symlink"); + return 1; + } + return 0; + } + + int rc = link(target, path); + if (rc < 0) { + perror("link"); + return 1; + } + return 0; +} diff --git a/Userland/Utilities/ls.cpp b/Userland/Utilities/ls.cpp new file mode 100644 index 0000000000..bdeba14361 --- /dev/null +++ b/Userland/Utilities/ls.cpp @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int do_file_system_object_long(const char* path); +static int do_file_system_object_short(const char* path); + +static bool flag_classify = false; +static bool flag_colorize = false; +static bool flag_long = false; +static bool flag_show_dotfiles = false; +static bool flag_show_almost_all_dotfiles = false; +static bool flag_ignore_backups = false; +static bool flag_list_directories_only = false; +static bool flag_show_inode = false; +static bool flag_print_numeric = false; +static bool flag_hide_group = false; +static bool flag_human_readable = false; +static bool flag_sort_by_timestamp = false; +static bool flag_reverse_sort = false; +static bool flag_disable_hyperlinks = false; + +static size_t terminal_rows = 0; +static size_t terminal_columns = 0; +static bool output_is_terminal = false; + +static HashMap users; +static HashMap groups; + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath tty", nullptr) < 0) { + perror("pledge"); + return 1; + } + + struct winsize ws; + int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); + if (rc == 0) { + terminal_rows = ws.ws_row; + terminal_columns = ws.ws_col; + output_is_terminal = true; + } + if (!isatty(STDOUT_FILENO)) { + flag_disable_hyperlinks = true; + } else { + flag_colorize = true; + } + + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector paths; + + Core::ArgsParser args_parser; + args_parser.set_general_help("List files in a directory."); + args_parser.add_option(flag_show_dotfiles, "Show dotfiles", "all", 'a'); + args_parser.add_option(flag_show_almost_all_dotfiles, "Do not list implied . and .. directories", nullptr, 'A'); + args_parser.add_option(flag_ignore_backups, "Do not list implied entries ending with ~", "--ignore-backups", 'B'); + args_parser.add_option(flag_list_directories_only, "List directories themselves, not their contents", "directory", 'd'); + args_parser.add_option(flag_long, "Display long info", "long", 'l'); + args_parser.add_option(flag_sort_by_timestamp, "Sort files by timestamp", nullptr, 't'); + args_parser.add_option(flag_reverse_sort, "Reverse sort order", "reverse", 'r'); + args_parser.add_option(flag_classify, "Append a file type indicator to entries", "classify", 'F'); + args_parser.add_option(flag_colorize, "Use pretty colors", nullptr, 'G'); + args_parser.add_option(flag_show_inode, "Show inode ids", "inode", 'i'); + args_parser.add_option(flag_print_numeric, "In long format, display numeric UID/GID", "numeric-uid-gid", 'n'); + args_parser.add_option(flag_hide_group, "In long format, do not show group information", nullptr, 'o'); + args_parser.add_option(flag_human_readable, "Print human-readable sizes", "human-readable", 'h'); + args_parser.add_option(flag_disable_hyperlinks, "Disable hyperlinks", "no-hyperlinks", 'K'); + args_parser.add_positional_argument(paths, "Directory to list", "path", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (flag_show_almost_all_dotfiles) + flag_show_dotfiles = true; + + if (flag_long) { + setpwent(); + for (auto* pwd = getpwent(); pwd; pwd = getpwent()) + users.set(pwd->pw_uid, pwd->pw_name); + endpwent(); + setgrent(); + for (auto* grp = getgrent(); grp; grp = getgrent()) + groups.set(grp->gr_gid, grp->gr_name); + endgrent(); + } + + auto do_file_system_object = [&](const char* path) { + if (flag_long) + return do_file_system_object_long(path); + return do_file_system_object_short(path); + }; + + int status = 0; + if (paths.is_empty()) { + status = do_file_system_object("."); + } else if (paths.size() == 1) { + status = do_file_system_object(paths[0]); + } else { + for (auto& path : paths) { + status = do_file_system_object(path); + } + } + return status; +} + +static int print_escaped(const char* name) +{ + int printed = 0; + + Utf8View utf8_name(name); + if (utf8_name.validate()) { + printf("%s", name); + return utf8_name.length(); + } + + for (int i = 0; name[i] != '\0'; i++) { + if (isprint(name[i])) { + putchar(name[i]); + printed++; + } else { + printed += printf("\\%03d", name[i]); + } + } + + return printed; +} + +static String& hostname() +{ + static String s_hostname; + if (s_hostname.is_null()) { + char buffer[HOST_NAME_MAX]; + if (gethostname(buffer, sizeof(buffer)) == 0) + s_hostname = buffer; + else + s_hostname = "localhost"; + } + return s_hostname; +} + +static size_t print_name(const struct stat& st, const String& name, const char* path_for_link_resolution, const char* path_for_hyperlink) +{ + if (!flag_disable_hyperlinks) { + auto full_path = Core::File::real_path_for(path_for_hyperlink); + if (!full_path.is_null()) { + out("\033]8;;file://{}{}\033\\", hostname(), full_path); + } + } + + size_t nprinted = 0; + + if (!flag_colorize || !output_is_terminal) { + nprinted = printf("%s", name.characters()); + } else { + const char* begin_color = ""; + const char* end_color = "\033[0m"; + + if (st.st_mode & S_ISVTX) + begin_color = "\033[42;30;1m"; + else if (st.st_mode & S_ISUID) + begin_color = "\033[41;1m"; + else if (st.st_mode & S_ISGID) + begin_color = "\033[43;1m"; + else if (S_ISLNK(st.st_mode)) + begin_color = "\033[36;1m"; + else if (S_ISDIR(st.st_mode)) + begin_color = "\033[34;1m"; + else if (st.st_mode & 0111) + begin_color = "\033[32;1m"; + else if (S_ISSOCK(st.st_mode)) + begin_color = "\033[35;1m"; + else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) + begin_color = "\033[33;1m"; + printf("%s", begin_color); + nprinted = print_escaped(name.characters()); + printf("%s", end_color); + } + if (S_ISLNK(st.st_mode)) { + if (path_for_link_resolution) { + auto link_destination = Core::File::read_link(path_for_link_resolution); + if (link_destination.is_null()) { + perror("readlink"); + } else { + nprinted += printf(" -> ") + print_escaped(link_destination.characters()); + } + } else { + if (flag_classify) + nprinted += printf("@"); + } + } else if (S_ISDIR(st.st_mode)) { + if (flag_classify) + nprinted += printf("/"); + } else if (st.st_mode & 0111) { + if (flag_classify) + nprinted += printf("*"); + } + + if (!flag_disable_hyperlinks) { + printf("\033]8;;\033\\"); + } + + return nprinted; +} + +static bool print_filesystem_object(const String& path, const String& name, const struct stat& st) +{ + if (flag_show_inode) + printf("%08u ", st.st_ino); + + if (S_ISDIR(st.st_mode)) + printf("d"); + else if (S_ISLNK(st.st_mode)) + printf("l"); + else if (S_ISBLK(st.st_mode)) + printf("b"); + else if (S_ISCHR(st.st_mode)) + printf("c"); + else if (S_ISFIFO(st.st_mode)) + printf("f"); + else if (S_ISSOCK(st.st_mode)) + printf("s"); + else if (S_ISREG(st.st_mode)) + printf("-"); + else + printf("?"); + + printf("%c%c%c%c%c%c%c%c", + st.st_mode & S_IRUSR ? 'r' : '-', + st.st_mode & S_IWUSR ? 'w' : '-', + st.st_mode & S_ISUID ? 's' : (st.st_mode & S_IXUSR ? 'x' : '-'), + st.st_mode & S_IRGRP ? 'r' : '-', + st.st_mode & S_IWGRP ? 'w' : '-', + st.st_mode & S_ISGID ? 's' : (st.st_mode & S_IXGRP ? 'x' : '-'), + st.st_mode & S_IROTH ? 'r' : '-', + st.st_mode & S_IWOTH ? 'w' : '-'); + + if (st.st_mode & S_ISVTX) + printf("t"); + else + printf("%c", st.st_mode & S_IXOTH ? 'x' : '-'); + + printf(" %u", st.st_nlink); + + auto username = users.get(st.st_uid); + if (!flag_print_numeric && username.has_value()) { + printf(" %7s", username.value().characters()); + } else { + printf(" %7u", st.st_uid); + } + + if (!flag_hide_group) { + auto groupname = groups.get(st.st_gid); + if (!flag_print_numeric && groupname.has_value()) { + printf(" %7s", groupname.value().characters()); + } else { + printf(" %7u", st.st_gid); + } + } + + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { + printf(" %4u,%4u ", major(st.st_rdev), minor(st.st_rdev)); + } else { + if (flag_human_readable) { + printf(" %10s ", human_readable_size((size_t)st.st_size).characters()); + } else { + printf(" %10zd ", st.st_size); + } + } + + printf(" %s ", Core::DateTime::from_timestamp(st.st_mtime).to_string().characters()); + + print_name(st, name, path.characters(), path.characters()); + + printf("\n"); + return true; +} + +static int do_file_system_object_long(const char* path) +{ + if (flag_list_directories_only) { + struct stat stat; + int rc = lstat(path, &stat); + if (rc < 0) { + perror("lstat"); + memset(&stat, 0, sizeof(stat)); + } + if (print_filesystem_object(path, path, stat)) + return 0; + return 2; + } + + auto flags = Core::DirIterator::SkipDots; + if (flag_show_dotfiles) + flags = Core::DirIterator::Flags::NoFlags; + if (flag_show_almost_all_dotfiles) + flags = Core::DirIterator::SkipParentAndBaseDir; + + Core::DirIterator di(path, flags); + + if (di.has_error()) { + if (di.error() == ENOTDIR) { + struct stat stat; + int rc = lstat(path, &stat); + if (rc < 0) { + perror("lstat"); + memset(&stat, 0, sizeof(stat)); + } + if (print_filesystem_object(path, path, stat)) + return 0; + return 2; + } + fprintf(stderr, "%s: %s\n", path, di.error_string()); + return 1; + } + + struct FileMetadata { + String name; + String path; + struct stat stat; + }; + + Vector files; + while (di.has_next()) { + FileMetadata metadata; + metadata.name = di.next_path(); + ASSERT(!metadata.name.is_empty()); + + if (metadata.name.ends_with('~') && flag_ignore_backups && metadata.name != path) + continue; + + StringBuilder builder; + builder.append(path); + builder.append('/'); + builder.append(metadata.name); + metadata.path = builder.to_string(); + ASSERT(!metadata.path.is_null()); + int rc = lstat(metadata.path.characters(), &metadata.stat); + if (rc < 0) { + perror("lstat"); + memset(&metadata.stat, 0, sizeof(metadata.stat)); + } + files.append(move(metadata)); + } + + quick_sort(files, [](auto& a, auto& b) { + if (flag_sort_by_timestamp) { + if (flag_reverse_sort) + return a.stat.st_mtime > b.stat.st_mtime; + return a.stat.st_mtime < b.stat.st_mtime; + } + // Fine, sort by name then! + if (flag_reverse_sort) + return a.name > b.name; + return a.name < b.name; + }); + + for (auto& file : files) { + if (!print_filesystem_object(file.path, file.name, file.stat)) + return 2; + } + return 0; +} + +static bool print_filesystem_object_short(const char* path, const char* name, size_t* nprinted) +{ + struct stat st; + int rc = lstat(path, &st); + if (rc == -1) { + printf("lstat(%s) failed: %s\n", path, strerror(errno)); + return false; + } + + if (flag_show_inode) + printf("%08u ", st.st_ino); + + *nprinted = print_name(st, name, nullptr, path); + return true; +} + +int do_file_system_object_short(const char* path) +{ + if (flag_list_directories_only) { + size_t nprinted = 0; + bool status = print_filesystem_object_short(path, path, &nprinted); + printf("\n"); + if (status) + return 0; + return 2; + } + + auto flags = Core::DirIterator::SkipDots; + if (flag_show_dotfiles) + flags = Core::DirIterator::Flags::NoFlags; + if (flag_show_almost_all_dotfiles) + flags = Core::DirIterator::SkipParentAndBaseDir; + + Core::DirIterator di(path, flags); + if (di.has_error()) { + if (di.error() == ENOTDIR) { + size_t nprinted = 0; + bool status = print_filesystem_object_short(path, path, &nprinted); + printf("\n"); + if (status) + return 0; + return 2; + } + fprintf(stderr, "%s: %s\n", path, di.error_string()); + return 1; + } + + Vector names; + size_t longest_name = 0; + while (di.has_next()) { + String name = di.next_path(); + + if (name.ends_with('~') && flag_ignore_backups && name != path) + continue; + + names.append(name); + if (names.last().length() > longest_name) + longest_name = name.length(); + } + quick_sort(names); + + size_t printed_on_row = 0; + size_t nprinted = 0; + for (size_t i = 0; i < names.size(); ++i) { + auto& name = names[i]; + StringBuilder builder; + builder.append(path); + builder.append('/'); + builder.append(name); + if (!print_filesystem_object_short(builder.to_string().characters(), name.characters(), &nprinted)) + return 2; + int offset = 0; + if (terminal_columns > longest_name) + offset = terminal_columns % longest_name / (terminal_columns / longest_name); + + // The offset must be at least 2 because: + // - With each file an additional char is printed e.g. '@','*'. + // - Each filename must be separated by a space. + size_t column_width = longest_name + max(offset, 2); + printed_on_row += column_width; + + for (size_t j = nprinted; i != (names.size() - 1) && j < column_width; ++j) + printf(" "); + if ((printed_on_row + column_width) >= terminal_columns) { + printf("\n"); + printed_on_row = 0; + } + } + if (printed_on_row) + printf("\n"); + return 0; +} diff --git a/Userland/Utilities/lsirq.cpp b/Userland/Utilities/lsirq.cpp new file mode 100644 index 0000000000..8a24308c20 --- /dev/null +++ b/Userland/Utilities/lsirq.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020, Liav A. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/proc/interrupts", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + auto proc_interrupts = Core::File::construct("/proc/interrupts"); + if (!proc_interrupts->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Error: %s\n", proc_interrupts->error_string()); + return 1; + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + printf("%4s %-10s\n", " ", "CPU0"); + auto file_contents = proc_interrupts->read_all(); + auto json = JsonValue::from_string(file_contents); + ASSERT(json.has_value()); + json.value().as_array().for_each([](auto& value) { + auto handler = value.as_object(); + auto purpose = handler.get("purpose").to_string(); + auto interrupt = handler.get("interrupt_line").to_string(); + auto controller = handler.get("controller").to_string(); + auto call_count = handler.get("call_count").to_string(); + + printf("%4s: %-10s %-10s %-30s\n", + interrupt.characters(), call_count.characters(), controller.characters(), purpose.characters()); + }); + + return 0; +} diff --git a/Userland/Utilities/lsof.cpp b/Userland/Utilities/lsof.cpp new file mode 100644 index 0000000000..029565cedc --- /dev/null +++ b/Userland/Utilities/lsof.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2020, Maciej Zygmanowski + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct OpenFile { + int fd; + int pid; + String type; + String name; + String state; + String full_name; +}; + +static bool parse_name(StringView name, OpenFile& file) +{ + GenericLexer lexer(name); + auto component1 = lexer.consume_until(':'); + + if (lexer.tell_remaining() == 0) { + file.name = component1; + return true; + } else { + file.type = component1; + auto component2 = lexer.consume_while([](char c) { return isprint(c) && !isspace(c) && c != '('; }); + lexer.ignore_while(isspace); + file.name = component2; + + if (lexer.tell_remaining() == 0) { + return true; + } else { + if (!lexer.consume_specific('(')) { + dbgln("parse_name: expected ("); + return false; + } + + auto component3 = lexer.consume_until(')'); + if (lexer.tell_remaining() != 0) { + dbgln("parse_name: expected EOF"); + return false; + } + + file.state = component3; + return true; + } + } +} + +static Vector get_open_files_by_pid(pid_t pid) +{ + auto file = Core::File::open(String::format("/proc/%d/fds", pid), Core::IODevice::OpenMode::ReadOnly); + if (file.is_error()) { + printf("lsof: PID %d: %s\n", pid, file.error().characters()); + return Vector(); + } + auto data = file.value()->read_all(); + + JsonParser parser(data); + auto result = parser.parse(); + + if (!result.has_value()) { + ASSERT_NOT_REACHED(); + } + + Vector files; + result.value().as_array().for_each([pid, &files](const JsonValue& object) { + OpenFile open_file; + open_file.pid = pid; + open_file.fd = object.as_object().get("fd").to_int(); + + String name = object.as_object().get("absolute_path").to_string(); + ASSERT(parse_name(name, open_file)); + open_file.full_name = name; + + files.append(open_file); + }); + return files; +} + +static void display_entry(const OpenFile& file, const Core::ProcessStatistics& statistics) +{ + printf("%-28s %4d %4d %-10s %4d %s\n", statistics.name.characters(), file.pid, statistics.pgid, statistics.username.characters(), file.fd, file.full_name.characters()); +} + +int main(int argc, char* argv[]) +{ + if (pledge("stdio rpath proc", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/proc", "r") < 0) { + perror("unveil /proc"); + return 1; + } + + // needed by ProcessStatisticsReader::get_all() + if (unveil("/etc/passwd", "r") < 0) { + perror("unveil /etc/passwd"); + return 1; + } + + unveil(nullptr, nullptr); + + bool arg_all_processes { false }; + int arg_fd { -1 }; + const char* arg_uid { nullptr }; + int arg_uid_int = -1; + int arg_pgid { -1 }; + pid_t arg_pid { -1 }; + const char* arg_file_name { nullptr }; + + if (argc == 1) + arg_all_processes = true; + else { + Core::ArgsParser parser; + parser.set_general_help("List open files of a processes. This can mean actual files in the file system, sockets, pipes, etc."); + parser.add_option(arg_pid, "Select by PID", nullptr, 'p', "pid"); + parser.add_option(arg_fd, "Select by file descriptor", nullptr, 'd', "fd"); + parser.add_option(arg_uid, "Select by login/UID", nullptr, 'u', "login/UID"); + parser.add_option(arg_pgid, "Select by process group ID", nullptr, 'g', "PGID"); + parser.add_positional_argument(arg_file_name, "File name", "file name", Core::ArgsParser::Required::No); + parser.parse(argc, argv); + } + { + // try convert UID to int + auto arg = String(arg_uid).to_int(); + if (arg.has_value()) + arg_uid_int = arg.value(); + } + + printf("%-28s %4s %4s %-10s %4s %s\n", "COMMAND", "PID", "PGID", "USER", "FD", "NAME"); + auto processes = Core::ProcessStatisticsReader::get_all(); + if (!processes.has_value()) + return 1; + if (arg_pid == -1) { + for (auto process : processes.value()) { + if (process.key == 0) + continue; + auto open_files = get_open_files_by_pid(process.key); + + if (open_files.is_empty()) + continue; + + for (auto file : open_files) { + if ((arg_all_processes) + || (arg_fd != -1 && file.fd == arg_fd) + || (arg_uid_int != -1 && (int)process.value.uid == arg_uid_int) + || (arg_uid != nullptr && process.value.username == arg_uid) + || (arg_pgid != -1 && (int)process.value.pgid == arg_pgid) + || (arg_file_name != nullptr && file.name == arg_file_name)) + display_entry(file, process.value); + } + } + } else { + auto open_files = get_open_files_by_pid(arg_pid); + + if (open_files.is_empty()) + return 0; + + for (auto file : open_files) { + display_entry(file, processes.value().get(arg_pid).value()); + } + } + + return 0; +} diff --git a/Userland/Utilities/lspci.cpp b/Userland/Utilities/lspci.cpp new file mode 100644 index 0000000000..4f42e26018 --- /dev/null +++ b/Userland/Utilities/lspci.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/res/pci.ids", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/proc/pci", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + auto db = PCIDB::Database::open(); + if (!db) + warnln("Couldn't open PCI ID database"); + + auto proc_pci = Core::File::construct("/proc/pci"); + if (!proc_pci->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Error: %s\n", proc_pci->error_string()); + return 1; + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto file_contents = proc_pci->read_all(); + auto json = JsonValue::from_string(file_contents); + ASSERT(json.has_value()); + json.value().as_array().for_each([db](auto& value) { + auto dev = value.as_object(); + auto seg = dev.get("seg").to_u32(); + auto bus = dev.get("bus").to_u32(); + auto slot = dev.get("slot").to_u32(); + auto function = dev.get("function").to_u32(); + auto vendor_id = dev.get("vendor_id").to_u32(); + auto device_id = dev.get("device_id").to_u32(); + auto revision_id = dev.get("revision_id").to_u32(); + auto class_id = dev.get("class").to_u32(); + + String vendor_name; + String device_name; + String class_name; + + if (db) { + vendor_name = db->get_vendor(vendor_id); + device_name = db->get_device(vendor_id, device_id); + class_name = db->get_class(class_id); + } + + if (vendor_name.is_empty()) + vendor_name = String::format("%02x", vendor_id); + if (device_name.is_empty()) + device_name = String::format("%02x", device_id); + if (class_name.is_empty()) + class_name = String::format("%04x", class_id); + + outln("{:04x}:{:02x}:{:02x}.{} {}: {} {} (rev {:02x})", seg, bus, slot, function, class_name, vendor_name, device_name, revision_id); + }); + + return 0; +} diff --git a/Userland/Utilities/man.cpp b/Userland/Utilities/man.cpp new file mode 100644 index 0000000000..079660494c --- /dev/null +++ b/Userland/Utilities/man.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + int view_width = 0; + if (isatty(STDOUT_FILENO)) { + struct winsize ws; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) + view_width = ws.ws_col; + } + + if (view_width == 0) + view_width = 80; + + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/usr/share/man", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + const char* section = nullptr; + const char* name = nullptr; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Read manual pages. Try 'man man' to get started."); + args_parser.add_positional_argument(section, "Section of the man page", "section", Core::ArgsParser::Required::No); + args_parser.add_positional_argument(name, "Name of the man page", "name"); + + args_parser.parse(argc, argv); + + auto make_path = [name](const char* section) { + return String::format("/usr/share/man/man%s/%s.md", section, name); + }; + if (!section) { + const char* sections[] = { + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8" + }; + for (auto s : sections) { + String path = make_path(s); + if (access(path.characters(), R_OK) == 0) { + section = s; + break; + } + } + if (!section) { + fprintf(stderr, "No man page for %s\n", name); + exit(1); + } + } + + auto file = Core::File::construct(); + file->set_filename(make_path(section)); + + if (!file->open(Core::IODevice::OpenMode::ReadOnly)) { + perror("Failed to open man page file"); + exit(1); + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + dbgln("Loading man page from {}", file->filename()); + auto buffer = file->read_all(); + auto source = String::copy(buffer); + + printf("%s(%s)\t\tSerenityOS manual\n", name, section); + + auto document = Markdown::Document::parse(source); + ASSERT(document); + + String rendered = document->render_for_terminal(view_width); + printf("%s", rendered.characters()); +} diff --git a/Userland/Utilities/md.cpp b/Userland/Utilities/md.cpp new file mode 100644 index 0000000000..04e77d28cd --- /dev/null +++ b/Userland/Utilities/md.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + if (pledge("stdio rpath tty", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* file_name = nullptr; + bool html = false; + int view_width = 0; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Render Markdown to some other format."); + args_parser.add_option(html, "Render to HTML rather than for the terminal", "html", 'H'); + args_parser.add_option(view_width, "Viewport width for the terminal (defaults to current terminal width)", "view-width", 0, "width"); + args_parser.add_positional_argument(file_name, "Path to Markdown file", "path", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (!html && view_width == 0) { + if (isatty(STDOUT_FILENO)) { + struct winsize ws; + if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) < 0) + view_width = 80; + else + view_width = ws.ws_col; + + } else { + view_width = 80; + } + } + auto file = Core::File::construct(); + bool success; + if (file_name == nullptr) { + success = file->open(STDIN_FILENO, Core::IODevice::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No); + } else { + file->set_filename(file_name); + success = file->open(Core::IODevice::OpenMode::ReadOnly); + } + if (!success) { + fprintf(stderr, "Error: %s\n", file->error_string()); + return 1; + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto buffer = file->read_all(); + dbgln("Read size {}", buffer.size()); + + auto input = String::copy(buffer); + auto document = Markdown::Document::parse(input); + + if (!document) { + fprintf(stderr, "Error parsing\n"); + return 1; + } + + String res = html ? document->render_to_html() : document->render_for_terminal(view_width); + printf("%s", res.characters()); +} diff --git a/Userland/Utilities/misbehaving-application.cpp b/Userland/Utilities/misbehaving-application.cpp new file mode 100644 index 0000000000..cee1ccab1c --- /dev/null +++ b/Userland/Utilities/misbehaving-application.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int, char**) +{ + Core::EventLoop event_loop; + + auto timer = Core::Timer::construct(10, [&] { + dbgln("Now hanging!"); + while (true) { + sleep(1); + } + }); + + return event_loop.exec(); +} diff --git a/Userland/Utilities/mkdir.cpp b/Userland/Utilities/mkdir.cpp new file mode 100644 index 0000000000..04badc9836 --- /dev/null +++ b/Userland/Utilities/mkdir.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2020, Linus Groh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio cpath rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool create_parents = false; + Vector directories; + + Core::ArgsParser args_parser; + args_parser.add_option(create_parents, "Create parent directories if they don't exist", "parents", 'p'); + args_parser.add_positional_argument(directories, "Directories to create", "directories"); + args_parser.parse(argc, argv); + + // FIXME: Support -m/--mode option + mode_t mode = 0755; + + bool has_errors = false; + + for (auto& directory : directories) { + LexicalPath lexical_path(directory); + if (!create_parents) { + if (mkdir(lexical_path.string().characters(), mode) < 0) { + perror("mkdir"); + has_errors = true; + } + continue; + } + StringBuilder path_builder; + if (lexical_path.is_absolute()) + path_builder.append("/"); + for (auto& part : lexical_path.parts()) { + path_builder.append(part); + auto path = path_builder.build(); + struct stat st; + if (stat(path.characters(), &st) < 0) { + if (errno != ENOENT) { + perror("stat"); + has_errors = true; + break; + } + if (mkdir(path.characters(), mode) < 0) { + perror("mkdir"); + has_errors = true; + break; + } + } else { + if (!S_ISDIR(st.st_mode)) { + fprintf(stderr, "mkdir: cannot create directory '%s': not a directory\n", path.characters()); + has_errors = true; + break; + } + } + path_builder.append("/"); + } + } + return has_errors ? 1 : 0; +} diff --git a/Userland/Utilities/mkfifo.cpp b/Userland/Utilities/mkfifo.cpp new file mode 100644 index 0000000000..ba06d1b06b --- /dev/null +++ b/Userland/Utilities/mkfifo.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020, Peter Elliott + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio dpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + mode_t mode = 0666; + Vector paths; + + Core::ArgsParser args_parser; + // FIXME: add -m for file modes + args_parser.add_positional_argument(paths, "Paths of FIFOs to create", "paths"); + args_parser.parse(argc, argv); + + int exit_code = 0; + + for (auto path : paths) { + if (mkfifo(path, mode) < 0) { + perror("mkfifo"); + exit_code = 1; + } + } + + return exit_code; +} diff --git a/Userland/Utilities/mknod.cpp b/Userland/Utilities/mknod.cpp new file mode 100644 index 0000000000..49ef4cdacc --- /dev/null +++ b/Userland/Utilities/mknod.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +constexpr unsigned encoded_device(unsigned major, unsigned minor) +{ + return (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12); +} + +static int usage() +{ + printf("usage: mknod [ ]\n"); + return 0; +} + +int main(int argc, char** argv) +{ + if (pledge("stdio dpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + // FIXME: Add some kind of option for specifying the file permissions. + if (argc < 3) + return usage(); + + if (argv[2][0] == 'p') { + if (argc != 3) + return usage(); + } else if (argc != 5) { + return usage(); + } + + const char* name = argv[1]; + mode_t mode = 0666; + switch (argv[2][0]) { + case 'c': + case 'u': + mode |= S_IFCHR; + break; + case 'b': + mode |= S_IFBLK; + break; + case 'p': + mode |= S_IFIFO; + break; + default: + return usage(); + } + + int major = 0; + int minor = 0; + if (argc == 5) { + major = atoi(argv[3]); + minor = atoi(argv[4]); + } + + int rc = mknod(name, mode, encoded_device(major, minor)); + if (rc < 0) { + perror("mknod"); + return 1; + } + return 0; +} diff --git a/Userland/Utilities/modload.cpp b/Userland/Utilities/modload.cpp new file mode 100644 index 0000000000..da86c93af4 --- /dev/null +++ b/Userland/Utilities/modload.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + const char* path = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(path, "Path to the module to load", "path"); + args_parser.parse(argc, argv); + + int rc = module_load(path, strlen(path)); + if (rc < 0) { + perror("module_load"); + return 1; + } + return 0; +} diff --git a/Userland/Utilities/modunload.cpp b/Userland/Utilities/modunload.cpp new file mode 100644 index 0000000000..b6e089bdd3 --- /dev/null +++ b/Userland/Utilities/modunload.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + const char* name = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(name, "Name of the module to unload", "name"); + args_parser.parse(argc, argv); + + int rc = module_unload(name, strlen(name)); + if (rc < 0) { + perror("module_unload"); + return 1; + } + return 0; +} diff --git a/Userland/Utilities/more.cpp b/Userland/Utilities/more.cpp new file mode 100644 index 0000000000..94b34e44cb --- /dev/null +++ b/Userland/Utilities/more.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +static int key_fd; + +static void wait_for_key() +{ + printf("\033[7m--[ more ]--\033[0m"); + fflush(stdout); + char dummy; + [[maybe_unused]] auto rc = read(key_fd, &dummy, 1); + printf("\n"); +} + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + if (pledge("stdio rpath tty", nullptr) < 0) { + perror("pledge"); + return 1; + } + + key_fd = STDOUT_FILENO; + + struct winsize ws; + ioctl(1, TIOCGWINSZ, &ws); + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + unsigned lines_printed = 0; + while (!feof(stdin)) { + char buffer[BUFSIZ]; + auto* str = fgets(buffer, sizeof(buffer), stdin); + if (!str) + break; + printf("%s", str); + ++lines_printed; + if ((lines_printed % (ws.ws_row - 1)) == 0) { + wait_for_key(); + } + } + + close(key_fd); + return 0; +} diff --git a/Userland/Utilities/mount.cpp b/Userland/Utilities/mount.cpp new file mode 100644 index 0000000000..9e313d7a19 --- /dev/null +++ b/Userland/Utilities/mount.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int parse_options(const StringView& options) +{ + int flags = 0; + Vector parts = options.split_view(','); + for (auto& part : parts) { + if (part == "defaults") + continue; + else if (part == "nodev") + flags |= MS_NODEV; + else if (part == "noexec") + flags |= MS_NOEXEC; + else if (part == "nosuid") + flags |= MS_NOSUID; + else if (part == "bind") + flags |= MS_BIND; + else if (part == "ro") + flags |= MS_RDONLY; + else if (part == "remount") + flags |= MS_REMOUNT; + else + fprintf(stderr, "Ignoring invalid option: %s\n", part.to_string().characters()); + } + return flags; +} + +static bool is_source_none(const char* source) +{ + return !strcmp("none", source); +} + +static int get_source_fd(const char* source) +{ + if (is_source_none(source)) + return -1; + int fd = open(source, O_RDWR); + if (fd < 0) + fd = open(source, O_RDONLY); + if (fd < 0) { + int saved_errno = errno; + auto message = String::format("Failed to open: %s\n", source); + errno = saved_errno; + perror(message.characters()); + } + return fd; +} + +static bool mount_all() +{ + // Mount all filesystems listed in /etc/fstab. + dbgln("Mounting all filesystems..."); + + auto fstab = Core::File::construct("/etc/fstab"); + if (!fstab->open(Core::IODevice::OpenMode::ReadOnly)) { + fprintf(stderr, "Failed to open /etc/fstab: %s\n", fstab->error_string()); + return false; + } + + bool all_ok = true; + while (fstab->can_read_line()) { + auto line = fstab->read_line(); + + // Skip comments and blank lines. + if (line.is_empty() || line.starts_with("#")) + continue; + + Vector parts = line.split('\t'); + if (parts.size() < 3) { + fprintf(stderr, "Invalid fstab entry: %s\n", line.characters()); + all_ok = false; + continue; + } + + const char* mountpoint = parts[1].characters(); + const char* fstype = parts[2].characters(); + int flags = parts.size() >= 4 ? parse_options(parts[3]) : 0; + + if (strcmp(mountpoint, "/") == 0) { + dbgln("Skipping mounting root"); + continue; + } + + const char* filename = parts[0].characters(); + + int fd = get_source_fd(filename); + + dbg() << "Mounting " << filename << "(" << fstype << ")" + << " on " << mountpoint; + int rc = mount(fd, mountpoint, fstype, flags); + if (rc != 0) { + fprintf(stderr, "Failed to mount %s (FD: %d) (%s) on %s: %s\n", filename, fd, fstype, mountpoint, strerror(errno)); + all_ok = false; + continue; + } + } + + return all_ok; +} + +static bool print_mounts() +{ + // Output info about currently mounted filesystems. + auto df = Core::File::construct("/proc/df"); + if (!df->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Failed to open /proc/df: %s\n", df->error_string()); + return false; + } + + auto content = df->read_all(); + auto json = JsonValue::from_string(content); + ASSERT(json.has_value()); + + json.value().as_array().for_each([](auto& value) { + auto fs_object = value.as_object(); + auto class_name = fs_object.get("class_name").to_string(); + auto mount_point = fs_object.get("mount_point").to_string(); + auto source = fs_object.get("source").as_string_or("none"); + auto readonly = fs_object.get("readonly").to_bool(); + auto mount_flags = fs_object.get("mount_flags").to_int(); + + printf("%s on %s type %s (", source.characters(), mount_point.characters(), class_name.characters()); + + if (readonly || mount_flags & MS_RDONLY) + printf("ro"); + else + printf("rw"); + + if (mount_flags & MS_NODEV) + printf(",nodev"); + if (mount_flags & MS_NOEXEC) + printf(",noexec"); + if (mount_flags & MS_NOSUID) + printf(",nosuid"); + if (mount_flags & MS_BIND) + printf(",bind"); + + printf(")\n"); + }); + + return true; +} + +int main(int argc, char** argv) +{ + const char* source = nullptr; + const char* mountpoint = nullptr; + const char* fs_type = nullptr; + const char* options = nullptr; + bool should_mount_all = false; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(source, "Source path", "source", Core::ArgsParser::Required::No); + args_parser.add_positional_argument(mountpoint, "Mount point", "mountpoint", Core::ArgsParser::Required::No); + args_parser.add_option(fs_type, "File system type", nullptr, 't', "fstype"); + args_parser.add_option(options, "Mount options", nullptr, 'o', "options"); + args_parser.add_option(should_mount_all, "Mount all file systems listed in /etc/fstab", nullptr, 'a'); + args_parser.parse(argc, argv); + + if (should_mount_all) { + return mount_all() ? 0 : 1; + } + + if (!source && !mountpoint) + return print_mounts() ? 0 : 1; + + if (source && mountpoint) { + if (!fs_type) + fs_type = "ext2"; + int flags = options ? parse_options(options) : 0; + + int fd = get_source_fd(source); + + if (mount(fd, mountpoint, fs_type, flags) < 0) { + perror("mount"); + return 1; + } + return 0; + } + + args_parser.print_usage(stderr, argv[0]); + return 1; +} diff --git a/Userland/Utilities/mv.cpp b/Userland/Utilities/mv.cpp new file mode 100644 index 0000000000..de07baf09b --- /dev/null +++ b/Userland/Utilities/mv.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath wpath cpath fattr", nullptr) < 0) { + perror("pledge"); + return 1; + } + + // NOTE: The "force" option is a dummy for now, it's just here to silence scripts that use "mv -f" + // In the future, it might be used to cancel out an "-i" interactive option. + bool force = false; + bool verbose = false; + + Vector paths; + + Core::ArgsParser args_parser; + args_parser.add_option(force, "Force", "force", 'f'); + args_parser.add_option(verbose, "Verbose", "verbose", 'v'); + args_parser.add_positional_argument(paths, "Paths to files being moved followed by target location", "paths"); + args_parser.parse(argc, argv); + + if (paths.size() < 2) { + args_parser.print_usage(stderr, argv[0]); + return 1; + } + + auto original_new_path = paths.take_last(); + + struct stat st; + + int rc = lstat(original_new_path, &st); + if (rc != 0 && errno != ENOENT) { + perror("lstat"); + return 1; + } + + if (paths.size() > 1 && !S_ISDIR(st.st_mode)) { + warnln("Target is not a directory: {}", original_new_path); + return 1; + } + + for (auto& old_path : paths) { + String combined_new_path; + const char* new_path = original_new_path; + if (rc == 0 && S_ISDIR(st.st_mode)) { + auto old_basename = LexicalPath(old_path).basename(); + combined_new_path = String::format("%s/%s", original_new_path, old_basename.characters()); + new_path = combined_new_path.characters(); + } + + rc = rename(old_path, new_path); + if (rc < 0) { + perror("rename"); + return 1; + } + + if (verbose) + printf("renamed '%s' -> '%s'\n", old_path, new_path); + } + + return 0; +} diff --git a/Userland/Utilities/nc.cpp b/Userland/Utilities/nc.cpp new file mode 100644 index 0000000000..8d4f0a7613 --- /dev/null +++ b/Userland/Utilities/nc.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + bool should_listen = false; + bool verbose = false; + bool should_close = false; + const char* addr = nullptr; + int port = 0; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Network cat: Connect to network sockets as if it were a file."); + args_parser.add_option(should_listen, "Listen instead of connecting", "listen", 'l'); + args_parser.add_option(verbose, "Log everything that's happening", "verbose", 'v'); + args_parser.add_option(should_close, "Close connection after reading stdin to the end", nullptr, 'N'); + args_parser.add_positional_argument(addr, "Address to connect to or listen on", "address"); + args_parser.add_positional_argument(port, "Port to connect to or listen on", "port"); + args_parser.parse(argc, argv); + + int fd; + + if (should_listen) { + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd < 0) { + perror("socket"); + return 1; + } + + struct sockaddr_in sa; + memset(&sa, 0, sizeof sa); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr.s_addr = htonl(INADDR_ANY); + if (addr) { + if (inet_pton(AF_INET, addr, &sa.sin_addr) < 0) { + perror("inet_pton"); + return 1; + } + } + + if (bind(listen_fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + perror("bind"); + return 1; + } + + if (listen(listen_fd, 1) == -1) { + perror("listen"); + return 1; + } + + char addr_str[100]; + + struct sockaddr_in sin; + socklen_t len; + + len = sizeof(sin); + if (getsockname(listen_fd, (struct sockaddr*)&sin, &len) == -1) { + perror("getsockname"); + return 1; + } + if (verbose) + fprintf(stderr, "waiting for a connection on %s:%d\n", inet_ntop(sin.sin_family, &sin.sin_addr, addr_str, sizeof(addr_str) - 1), ntohs(sin.sin_port)); + + len = sizeof(sin); + fd = accept(listen_fd, (struct sockaddr*)&sin, &len); + if (fd == -1) { + perror("accept"); + return 1; + } + + if (verbose) + fprintf(stderr, "got connection from %s:%d\n", inet_ntop(sin.sin_family, &sin.sin_addr, addr_str, sizeof(addr_str) - 1), ntohs(sin.sin_port)); + + if (close(listen_fd) == -1) { + perror("close"); + return 1; + }; + } else { + fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return 1; + } + + struct timeval timeout { + 3, 0 + }; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { + perror("setsockopt"); + return 1; + } + if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { + perror("setsockopt"); + return 1; + } + + char addr_str[100]; + + struct sockaddr_in dst_addr; + memset(&dst_addr, 0, sizeof(dst_addr)); + + dst_addr.sin_family = AF_INET; + dst_addr.sin_port = htons(port); + if (inet_pton(AF_INET, addr, &dst_addr.sin_addr) < 0) { + perror("inet_pton"); + return 1; + } + + if (verbose) + fprintf(stderr, "connecting to %s:%d\n", inet_ntop(dst_addr.sin_family, &dst_addr.sin_addr, addr_str, sizeof(addr_str) - 1), ntohs(dst_addr.sin_port)); + if (connect(fd, (struct sockaddr*)&dst_addr, sizeof(dst_addr)) < 0) { + perror("connect"); + return 1; + } + if (verbose) + fprintf(stderr, "connected!\n"); + } + + bool stdin_closed = false; + bool fd_closed = false; + + fd_set readfds, writefds, exceptfds; + + while (!stdin_closed || !fd_closed) { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&exceptfds); + + int highest_fd = 0; + + if (!stdin_closed) { + FD_SET(STDIN_FILENO, &readfds); + FD_SET(STDIN_FILENO, &exceptfds); + highest_fd = max(highest_fd, STDIN_FILENO); + } + if (!fd_closed) { + FD_SET(fd, &readfds); + FD_SET(fd, &exceptfds); + highest_fd = max(highest_fd, fd); + } + + int ready = select(highest_fd + 1, &readfds, &writefds, &exceptfds, nullptr); + if (ready == -1) { + if (errno == EINTR) + continue; + + perror("select"); + return 1; + } + + if (!stdin_closed && FD_ISSET(STDIN_FILENO, &readfds)) { + char buf[1024]; + int nread = read(STDIN_FILENO, buf, sizeof(buf)); + if (nread < 0) { + perror("read(STDIN_FILENO)"); + return 1; + } + + // stdin closed + if (nread == 0) { + stdin_closed = true; + if (verbose) + fprintf(stderr, "stdin closed\n"); + if (should_close) { + close(fd); + fd_closed = true; + } + } else if (write(fd, buf, nread) < 0) { + perror("write(fd)"); + return 1; + } + } + + if (!fd_closed && FD_ISSET(fd, &readfds)) { + char buf[1024]; + int nread = read(fd, buf, sizeof(buf)); + if (nread < 0) { + perror("read(fd)"); + return 1; + } + + // remote end closed + if (nread == 0) { + close(STDIN_FILENO); + stdin_closed = true; + fd_closed = true; + if (verbose) + fprintf(stderr, "remote closed\n"); + } else if (write(STDOUT_FILENO, buf, nread) < 0) { + perror("write(STDOUT_FILENO)"); + return 1; + } + } + } + + return 0; +} diff --git a/Userland/Utilities/nl.cpp b/Userland/Utilities/nl.cpp new file mode 100644 index 0000000000..ad6e965ae3 --- /dev/null +++ b/Userland/Utilities/nl.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include +#include + +enum NumberStyle { + NumberAllLines, + NumberNonEmptyLines, + NumberNoLines, +}; + +int main(int argc, char** argv) +{ + NumberStyle number_style = NumberNonEmptyLines; + int increment = 1; + const char* separator = " "; + int start_number = 1; + int number_width = 6; + Vector files; + + Core::ArgsParser args_parser; + + Core::ArgsParser::Option number_style_option { + true, + "Line numbering style: 't' for non-empty lines, 'a' for all lines, 'n' for no lines", + "body-numbering", + 'b', + "style", + [&number_style](const char* s) { + if (!strcmp(s, "t")) + number_style = NumberNonEmptyLines; + else if (!strcmp(s, "a")) + number_style = NumberAllLines; + else if (!strcmp(s, "n")) + number_style = NumberNoLines; + else + return false; + + return true; + } + }; + + args_parser.add_option(move(number_style_option)); + args_parser.add_option(increment, "Line count increment", "increment", 'i', "number"); + args_parser.add_option(separator, "Separator between line numbers and lines", "separator", 's', "string"); + args_parser.add_option(start_number, "Initial line number", "startnum", 'v', "number"); + args_parser.add_option(number_width, "Number width", "width", 'w', "number"); + args_parser.add_positional_argument(files, "Files to process", "file", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + Vector file_pointers; + if (!files.is_empty()) { + for (auto& file : files) { + FILE* file_pointer = fopen(file, "r"); + if (!file_pointer) { + fprintf(stderr, "unable to open %s\n", file); + continue; + } + file_pointers.append(file_pointer); + } + } else { + file_pointers.append(stdin); + } + + for (auto& file_pointer : file_pointers) { + int line_number = start_number - increment; // so the line number can start at 1 when added below + int previous_character = 0; + int next_character = 0; + while ((next_character = fgetc(file_pointer)) != EOF) { + if (previous_character == 0 || previous_character == '\n') { + if (next_character == '\n' && number_style != NumberAllLines) { + // Skip printing line count on empty lines. + printf("\n"); + continue; + } + if (number_style != NumberNoLines) + printf("%*d%s", number_width, (line_number += increment), separator); + else + printf("%*s", number_width, ""); + } + putchar(next_character); + previous_character = next_character; + } + fclose(file_pointer); + if (previous_character != '\n') + printf("\n"); // for cases where files have no trailing newline + } + return 0; +} diff --git a/Userland/Utilities/notify.cpp b/Userland/Utilities/notify.cpp new file mode 100644 index 0000000000..cb596e98e5 --- /dev/null +++ b/Userland/Utilities/notify.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + auto app = GUI::Application::construct(argc, argv); + + Core::ArgsParser args_parser; + const char* title = nullptr; + const char* message = nullptr; + const char* icon_path = nullptr; + args_parser.add_positional_argument(title, "Title of the notification", "title"); + args_parser.add_positional_argument(message, "Message to display in the notification", "message"); + args_parser.add_positional_argument(icon_path, "Path of icon to display in the notification", "icon-path", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + auto notification = GUI::Notification::construct(); + notification->set_text(message); + notification->set_title(title); + notification->set_icon(Gfx::Bitmap::load_from_file(icon_path)); + notification->show(); + + return 0; +} diff --git a/Userland/Utilities/ntpquery.cpp b/Userland/Utilities/ntpquery.cpp new file mode 100644 index 0000000000..017a6c0235 --- /dev/null +++ b/Userland/Utilities/ntpquery.cpp @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2020, Nico Weber + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define _BSD_SOURCE +#define _DEFAULT_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// An NtpTimestamp is a 64-bit integer that's a 32.32 binary-fixed point number. +// The integral part in the upper 32 bits represents seconds since 1900-01-01. +// The fractional part in the lower 32 bits stores fractional bits times 2 ** 32. +typedef uint64_t NtpTimestamp; + +struct [[gnu::packed]] NtpPacket { + uint8_t li_vn_mode; + uint8_t stratum; + int8_t poll; + int8_t precision; + + uint32_t root_delay; + uint32_t root_dispersion; + uint32_t reference_id; + + NtpTimestamp reference_timestamp; + NtpTimestamp origin_timestamp; + NtpTimestamp receive_timestamp; + NtpTimestamp transmit_timestamp; + + uint8_t leap_information() const { return li_vn_mode >> 6; } + uint8_t version_number() const { return (li_vn_mode >> 3) & 7; } + uint8_t mode() const { return li_vn_mode & 7; } +}; +static_assert(sizeof(NtpPacket) == 48); + +// NTP measures time in seconds since 1900-01-01, POSIX in seconds since 1970-01-01. +// 1900 wasn't a leap year, so there are 70/4 leap years between 1900 and 1970. +// Overflows a 32-bit signed int, but not a 32-bit unsigned int. +const unsigned SecondsFrom1900To1970 = (70u * 365u + 70u / 4u) * 24u * 60u * 60u; + +static NtpTimestamp ntp_timestamp_from_timeval(const timeval& t) +{ + ASSERT(t.tv_usec >= 0 && t.tv_usec < 1'000'000); // Fits in 20 bits when normalized. + + // Seconds just need translation to the different origin. + uint32_t seconds = t.tv_sec + SecondsFrom1900To1970; + + // Fractional bits are decimal fixed point (*1'000'000) in timeval, but binary fixed-point (* 2**32) in NTP timestamps. + uint32_t fractional_bits = static_cast((static_cast(t.tv_usec) << 32) / 1'000'000); + + return (static_cast(seconds) << 32) | fractional_bits; +} + +static timeval timeval_from_ntp_timestamp(const NtpTimestamp& ntp_timestamp) +{ + timeval t; + t.tv_sec = static_cast(ntp_timestamp >> 32) - SecondsFrom1900To1970; + t.tv_usec = static_cast((static_cast(ntp_timestamp & 0xFFFFFFFFu) * 1'000'000) >> 32); + return t; +} + +static String format_ntp_timestamp(NtpTimestamp ntp_timestamp) +{ + char buffer[28]; // YYYY-MM-DDTHH:MM:SS.UUUUUUZ is 27 characters long. + timeval t = timeval_from_ntp_timestamp(ntp_timestamp); + struct tm tm; + gmtime_r(&t.tv_sec, &tm); + size_t written = strftime(buffer, sizeof(buffer), "%Y-%m-%dT%T.", &tm); + ASSERT(written == 20); + written += snprintf(buffer + written, sizeof(buffer) - written, "%06d", (int)t.tv_usec); + ASSERT(written == 26); + buffer[written++] = 'Z'; + buffer[written] = '\0'; + return buffer; +} + +int main(int argc, char** argv) +{ +#ifdef __serenity__ + if (pledge("stdio inet dns settime", nullptr) < 0) { + perror("pledge"); + return 1; + } +#endif + + bool adjust_time = false; + bool set_time = false; + bool verbose = false; + // FIXME: Change to serenityos.pool.ntp.org once https://manage.ntppool.org/manage/vendor/zone?a=km5a8h&id=vz-14154g is approved. + // Other NTP servers: + // - time.nist.gov + // - time.apple.com + // - time.cloudflare.com (has NTS), https://blog.cloudflare.com/secure-time/ + // - time.windows.com + // + // Leap seconds smearing NTP servers: + // - time.facebook.com , https://engineering.fb.com/production-engineering/ntp-service/ , sine-smears over 18 hours + // - time.google.com , https://developers.google.com/time/smear , linear-smears over 24 hours + const char* host = "time.google.com"; + Core::ArgsParser args_parser; + args_parser.add_option(adjust_time, "Gradually adjust system time (requires root)", "adjust", 'a'); + args_parser.add_option(set_time, "Immediately set system time (requires root)", "set", 's'); + args_parser.add_option(verbose, "Verbose output", "verbose", 'v'); + args_parser.add_positional_argument(host, "NTP server", "host", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (adjust_time && set_time) { + fprintf(stderr, "-a and -s are mutually exclusive\n"); + return 1; + } + +#ifdef __serenity__ + if (!adjust_time && !set_time) { + if (pledge("stdio inet dns", nullptr) < 0) { + perror("pledge"); + return 1; + } + } +#endif + + auto* hostent = gethostbyname(host); + if (!hostent) { + fprintf(stderr, "Lookup failed for '%s'\n", host); + return 1; + } + +#ifdef __serenity__ + if (pledge((adjust_time || set_time) ? "stdio inet settime" : "stdio inet", nullptr) < 0) { + perror("pledge"); + return 1; + } + unveil(nullptr, nullptr); +#endif + + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + perror("socket"); + return 1; + } + + struct timeval timeout { + 5, 0 + }; + if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { + perror("setsockopt"); + return 1; + } + + int enable = 1; + if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable)) < 0) { + perror("setsockopt"); + return 1; + } + + sockaddr_in peer_address; + memset(&peer_address, 0, sizeof(peer_address)); + peer_address.sin_family = AF_INET; + peer_address.sin_port = htons(123); + peer_address.sin_addr.s_addr = *(const in_addr_t*)hostent->h_addr_list[0]; + + NtpPacket packet; + memset(&packet, 0, sizeof(packet)); + packet.li_vn_mode = (4 << 3) | 3; // Version 4, client connection. + + // The server will copy the transmit_timestamp to origin_timestamp in the reply. + // To not leak the local time, keep the time we sent the packet locally and + // send random bytes to the server. + auto random_transmit_timestamp = get_random(); + timeval local_transmit_time; + gettimeofday(&local_transmit_time, nullptr); + packet.transmit_timestamp = random_transmit_timestamp; + + ssize_t rc; + rc = sendto(fd, &packet, sizeof(packet), 0, (const struct sockaddr*)&peer_address, sizeof(peer_address)); + if (rc < 0) { + perror("sendto"); + return 1; + } + if ((size_t)rc < sizeof(packet)) { + fprintf(stderr, "incomplete packet send\n"); + return 1; + } + + iovec iov { &packet, sizeof(packet) }; + char control_message_buffer[CMSG_SPACE(sizeof(timeval))]; + msghdr msg = { &peer_address, sizeof(peer_address), &iov, 1, control_message_buffer, sizeof(control_message_buffer), 0 }; + rc = recvmsg(fd, &msg, 0); + if (rc < 0) { + perror("recvmsg"); + return 1; + } + timeval userspace_receive_time; + gettimeofday(&userspace_receive_time, nullptr); + if ((size_t)rc < sizeof(packet)) { + fprintf(stderr, "incomplete packet recv\n"); + return 1; + } + + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + ASSERT(cmsg->cmsg_level == SOL_SOCKET); + ASSERT(cmsg->cmsg_type == SCM_TIMESTAMP); + ASSERT(!CMSG_NXTHDR(&msg, cmsg)); + timeval kernel_receive_time; + memcpy(&kernel_receive_time, CMSG_DATA(cmsg), sizeof(kernel_receive_time)); + + // Checks 3 and 4 from end of section 5 of rfc4330. + if (packet.version_number() != 3 && packet.version_number() != 4) { + fprintf(stderr, "unexpected version number %d\n", packet.version_number()); + return 1; + } + if (packet.mode() != 4) { // 4 means "server", which should be the reply to our 3 ("client") request. + fprintf(stderr, "unexpected mode %d\n", packet.mode()); + return 1; + } + if (packet.stratum == 0 || packet.stratum >= 16) { + fprintf(stderr, "unexpected stratum value %d\n", packet.stratum); + return 1; + } + if (packet.origin_timestamp != random_transmit_timestamp) { + fprintf(stderr, "expected %#016" PRIx64 " as origin timestamp, got %#016" PRIx64 "\n", random_transmit_timestamp, packet.origin_timestamp); + return 1; + } + if (packet.transmit_timestamp == 0) { + fprintf(stderr, "got transmit_timestamp 0\n"); + return 1; + } + + NtpTimestamp origin_timestamp = ntp_timestamp_from_timeval(local_transmit_time); + NtpTimestamp receive_timestamp = be64toh(packet.receive_timestamp); + NtpTimestamp transmit_timestamp = be64toh(packet.transmit_timestamp); + NtpTimestamp destination_timestamp = ntp_timestamp_from_timeval(kernel_receive_time); + + timeval kernel_to_userspace_latency; + timersub(&userspace_receive_time, &kernel_receive_time, &kernel_to_userspace_latency); + + if (set_time) { + // FIXME: Do all the time filtering described in 5905, or at least correct for time of flight. + timeval t = timeval_from_ntp_timestamp(transmit_timestamp); + if (settimeofday(&t, nullptr) < 0) { + perror("settimeofday"); + return 1; + } + } + + if (verbose) { + printf("NTP response from %s:\n", inet_ntoa(peer_address.sin_addr)); + printf("Leap Information: %d\n", packet.leap_information()); + printf("Version Number: %d\n", packet.version_number()); + printf("Mode: %d\n", packet.mode()); + printf("Stratum: %d\n", packet.stratum); + printf("Poll: %d\n", packet.stratum); + printf("Precision: %d\n", packet.precision); + printf("Root delay: %#x\n", ntohl(packet.root_delay)); + printf("Root dispersion: %#x\n", ntohl(packet.root_dispersion)); + + u32 ref_id = ntohl(packet.reference_id); + printf("Reference ID: %#x", ref_id); + if (packet.stratum == 1) { + printf(" ('%c%c%c%c')", (ref_id & 0xff000000) >> 24, (ref_id & 0xff0000) >> 16, (ref_id & 0xff00) >> 8, ref_id & 0xff); + } + printf("\n"); + + printf("Reference timestamp: %#016" PRIx64 " (%s)\n", be64toh(packet.reference_timestamp), format_ntp_timestamp(be64toh(packet.reference_timestamp)).characters()); + printf("Origin timestamp: %#016" PRIx64 " (%s)\n", origin_timestamp, format_ntp_timestamp(origin_timestamp).characters()); + printf("Receive timestamp: %#016" PRIx64 " (%s)\n", receive_timestamp, format_ntp_timestamp(receive_timestamp).characters()); + printf("Transmit timestamp: %#016" PRIx64 " (%s)\n", transmit_timestamp, format_ntp_timestamp(transmit_timestamp).characters()); + printf("Destination timestamp: %#016" PRIx64 " (%s)\n", destination_timestamp, format_ntp_timestamp(destination_timestamp).characters()); + + // When the system isn't under load, user-space t and packet_t are identical. If a shell with `yes` is running, it can be as high as 30ms in this program, + // which gets user-space time immediately after the recvmsg() call. In programs that have an event loop reading from multiple sockets, it could be higher. + printf("Receive latency: %" PRId64 ".%06d s\n", (i64)kernel_to_userspace_latency.tv_sec, (int)kernel_to_userspace_latency.tv_usec); + } + + // Parts of the "Clock Filter" computations, https://tools.ietf.org/html/rfc5905#section-10 + NtpTimestamp T1 = origin_timestamp; + NtpTimestamp T2 = receive_timestamp; + NtpTimestamp T3 = transmit_timestamp; + NtpTimestamp T4 = destination_timestamp; + auto timestamp_difference_in_seconds = [](NtpTimestamp from, NtpTimestamp to) { + return static_cast(to - from) / pow(2.0, 32); + }; + + // The network round-trip time of the request. + // T4-T1 is the wall clock roundtrip time, in local ticks. + // T3-T2 is the server side processing time, in server ticks. + double delay_s = timestamp_difference_in_seconds(T1, T4) - timestamp_difference_in_seconds(T2, T3); + + // The offset from local time to server time, ignoring network delay. + // Both T2-T1 and T3-T4 estimate this; this takes the average of both. + // Or, equivalently, (T1+T4)/2 estimates local time, (T2+T3)/2 estimate server time, this is the difference. + double offset_s = 0.5 * (timestamp_difference_in_seconds(T1, T2) + timestamp_difference_in_seconds(T4, T3)); + if (verbose) + printf("Delay: %f\n", delay_s); + printf("Offset: %f\n", offset_s); + + if (adjust_time) { + long delta_us = static_cast(round(offset_s * 1'000'000)); + timeval delta_timeval; + delta_timeval.tv_sec = delta_us / 1'000'000; + delta_timeval.tv_usec = delta_us % 1'000'000; + if (delta_timeval.tv_usec < 0) { + delta_timeval.tv_sec--; + delta_timeval.tv_usec += 1'000'000; + } + if (adjtime(&delta_timeval, nullptr) < 0) { + perror("adjtime set"); + return 1; + } + } +} diff --git a/Userland/Utilities/open.cpp b/Userland/Utilities/open.cpp new file mode 100644 index 0000000000..c141eb817f --- /dev/null +++ b/Userland/Utilities/open.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2020, Sergey Bugaev + * Copyright (c) 2020, Linus Groh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + Core::EventLoop loop; + Vector urls_or_paths; + Core::ArgsParser parser; + parser.set_general_help("Open a file or URL by executing the appropriate program."); + parser.add_positional_argument(urls_or_paths, "URL or file path to open", "url-or-path"); + parser.parse(argc, argv); + + bool all_ok = true; + + for (auto& url_or_path : urls_or_paths) { + auto url = URL::create_with_url_or_path(url_or_path); + + if (url.protocol() == "file") { + auto real_path = Core::File::real_path_for(url.path()); + if (real_path.is_null()) { + // errno *should* be preserved from Core::File::real_path_for(). + warnln("Failed to open '{}': {}", url.path(), strerror(errno)); + all_ok = false; + continue; + } + url = URL::create_with_url_or_path(real_path); + } + + if (!Desktop::Launcher::open(url)) { + warnln("Failed to open '{}'", url); + all_ok = false; + } + } + + return all_ok ? 0 : 1; +} diff --git a/Userland/Utilities/pape.cpp b/Userland/Utilities/pape.cpp new file mode 100644 index 0000000000..9c79dd7d5c --- /dev/null +++ b/Userland/Utilities/pape.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int handle_show_all() +{ + Core::DirIterator di("/res/wallpapers", Core::DirIterator::SkipDots); + if (di.has_error()) { + fprintf(stderr, "DirIterator: %s\n", di.error_string()); + return 1; + } + + while (di.has_next()) { + String name = di.next_path(); + printf("%s\n", name.characters()); + } + return 0; +} + +static int handle_show_current() +{ + printf("%s\n", GUI::Desktop::the().wallpaper().characters()); + return 0; +} + +static int handle_set_pape(const String& name) +{ + StringBuilder builder; + builder.append("/res/wallpapers/"); + builder.append(name); + String path = builder.to_string(); + if (!GUI::Desktop::the().set_wallpaper(path)) { + fprintf(stderr, "pape: Failed to set wallpaper %s\n", path.characters()); + return 1; + } + return 0; +}; + +int main(int argc, char** argv) +{ + bool show_all = false; + bool show_current = false; + const char* name = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(show_all, "Show all wallpapers", "show-all", 'a'); + args_parser.add_option(show_current, "Show current wallpaper", "show-current", 'c'); + args_parser.add_positional_argument(name, "Wallpaper to set", "name", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + auto app = GUI::Application::construct(argc, argv); + + if (show_all) + return handle_show_all(); + else if (show_current) + return handle_show_current(); + + return handle_set_pape(name); +} diff --git a/Userland/Utilities/passwd.cpp b/Userland/Utilities/passwd.cpp new file mode 100644 index 0000000000..b0a36e437a --- /dev/null +++ b/Userland/Utilities/passwd.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2020, Peter Elliott + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (geteuid() != 0) { + warnln("Not running as root :^("); + return 1; + } + + if (pledge("stdio wpath rpath cpath tty id", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/etc/passwd", "rwc") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/group", "rwc") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/shadow", "rwc") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + bool del = false; + bool lock = false; + bool unlock = false; + const char* username = nullptr; + + auto args_parser = Core::ArgsParser(); + args_parser.set_general_help("Modify an account password."); + args_parser.add_option(del, "Delete password", "delete", 'd'); + args_parser.add_option(lock, "Lock password", "lock", 'l'); + args_parser.add_option(unlock, "Unlock password", "unlock", 'u'); + args_parser.add_positional_argument(username, "Username", "username", Core::ArgsParser::Required::No); + + args_parser.parse(argc, argv); + + uid_t current_uid = getuid(); + + auto account_or_error = (username) + ? Core::Account::from_name(username, Core::Account::OpenPasswdFile::ReadWrite, Core::Account::OpenShadowFile::ReadWrite) + : Core::Account::from_uid(current_uid, Core::Account::OpenPasswdFile::ReadWrite, Core::Account::OpenShadowFile::ReadWrite); + + if (account_or_error.is_error()) { + warnln("Core::Account::{}: {}", (username) ? "from_name" : "from_uid", account_or_error.error()); + return 1; + } + + // Drop privileges after opening all the files through the Core::Account object. + auto gid = getgid(); + if (setresgid(gid, gid, gid) < 0) { + perror("setresgid"); + return 1; + } + + auto uid = getuid(); + if (setresuid(uid, uid, uid) < 0) { + perror("setresuid"); + return 1; + } + + // Make sure /etc/passwd is open and ready for reading, then we can drop a bunch of pledge promises. + setpwent(); + + if (pledge("stdio tty", nullptr) < 0) { + perror("pledge"); + return 1; + } + + // target_account is the account we are changing the password of. + auto& target_account = account_or_error.value(); + + if (current_uid != 0 && current_uid != target_account.uid()) { + warnln("You can't modify passwd for {}", username); + return 1; + } + + if (del) { + target_account.delete_password(); + } else if (lock) { + target_account.set_password_enabled(false); + } else if (unlock) { + target_account.set_password_enabled(true); + } else { + auto new_password = Core::get_password("New password: "); + if (new_password.is_error()) { + warnln("{}", new_password.error()); + return 1; + } + + target_account.set_password(new_password.value().characters()); + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (!target_account.sync()) { + perror("Core::Account::Sync"); + } + + return 0; +} diff --git a/Userland/Utilities/paste.cpp b/Userland/Utilities/paste.cpp new file mode 100644 index 0000000000..df19f15e4c --- /dev/null +++ b/Userland/Utilities/paste.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019-2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + bool print_type = false; + bool no_newline = false; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Paste from the clipboard to stdout."); + args_parser.add_option(print_type, "Display the copied type", "print-type", 0); + args_parser.add_option(no_newline, "Do not append a newline", "no-newline", 'n'); + args_parser.parse(argc, argv); + + auto app = GUI::Application::construct(argc, argv); + + auto& clipboard = GUI::Clipboard::the(); + auto data_and_type = clipboard.data_and_type(); + + if (data_and_type.mime_type.is_null()) { + warnln("Nothing copied"); + return 1; + } + + if (!print_type) { + out("{}", StringView(data_and_type.data)); + // Append a newline to text contents, unless the caller says otherwise. + if (data_and_type.mime_type.starts_with("text/") && !no_newline) + outln(); + } else { + outln("{}", data_and_type.mime_type); + } + + return 0; +} diff --git a/Userland/Utilities/pidof.cpp b/Userland/Utilities/pidof.cpp new file mode 100644 index 0000000000..71ae5ea767 --- /dev/null +++ b/Userland/Utilities/pidof.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int pid_of(const String& process_name, bool single_shot, bool omit_pid, pid_t pid) +{ + bool displayed_at_least_one = false; + + auto processes = Core::ProcessStatisticsReader().get_all(); + if (!processes.has_value()) + return 1; + + for (auto& it : processes.value()) { + if (it.value.name == process_name) { + if (!omit_pid || it.value.pid != pid) { + printf(" %d" + (displayed_at_least_one ? 0 : 1), it.value.pid); + displayed_at_least_one = true; + + if (single_shot) + break; + } + } + } + + if (displayed_at_least_one) + printf("\n"); + + return 0; +} + +int main(int argc, char** argv) +{ + bool single_shot = false; + const char* omit_pid_value = nullptr; + const char* process_name = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(single_shot, "Only return one pid", nullptr, 's'); + args_parser.add_option(omit_pid_value, "Omit the given PID, or the parent process if the special value %PPID is passed", nullptr, 'o', "pid"); + args_parser.add_positional_argument(process_name, "Process name to search for", "process-name"); + + args_parser.parse(argc, argv); + + pid_t pid_to_omit = 0; + if (omit_pid_value) { + if (!strcmp(omit_pid_value, "%PPID")) { + pid_to_omit = getppid(); + } else { + auto number = StringView(omit_pid_value).to_uint(); + if (!number.has_value()) { + fprintf(stderr, "Invalid value for -o\n"); + args_parser.print_usage(stderr, argv[0]); + return 1; + } + pid_to_omit = number.value(); + } + } + return pid_of(process_name, single_shot, omit_pid_value != nullptr, pid_to_omit); +} diff --git a/Userland/Utilities/ping.cpp b/Userland/Utilities/ping.cpp new file mode 100644 index 0000000000..a960c73dc5 --- /dev/null +++ b/Userland/Utilities/ping.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static uint16_t internet_checksum(const void* ptr, size_t count) +{ + uint32_t checksum = 0; + auto* w = (const uint16_t*)ptr; + while (count > 1) { + checksum += ntohs(*w++); + if (checksum & 0x80000000) + checksum = (checksum & 0xffff) | (checksum >> 16); + count -= 2; + } + while (checksum >> 16) + checksum = (checksum & 0xffff) + (checksum >> 16); + return htons(~checksum); +} + +static int total_pings; +static int successful_pings; +static uint32_t total_ms; +static int min_ms; +static int max_ms; +static const char* host; + +int main(int argc, char** argv) +{ + if (pledge("stdio id inet dns sigaction", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(host, "Host to ping", "host"); + args_parser.parse(argc, argv); + + int fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (fd < 0) { + perror("socket"); + return 1; + } + + if (setgid(getgid()) || setuid(getuid())) { + fprintf(stderr, "Failed to drop privileges.\n"); + return 1; + } + + if (pledge("stdio inet dns sigaction", nullptr) < 0) { + perror("pledge"); + return 1; + } + + struct timeval timeout { + 1, 0 + }; + + int rc = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + if (rc < 0) { + perror("setsockopt"); + return 1; + } + + auto* hostent = gethostbyname(host); + if (!hostent) { + printf("Lookup failed for '%s'\n", host); + return 1; + } + + if (pledge("stdio inet sigaction", nullptr) < 0) { + perror("pledge"); + return 1; + } + + pid_t pid = getpid(); + + sockaddr_in peer_address; + memset(&peer_address, 0, sizeof(peer_address)); + peer_address.sin_family = AF_INET; + peer_address.sin_port = 0; + + peer_address.sin_addr.s_addr = *(const in_addr_t*)hostent->h_addr_list[0]; + + struct PingPacket { + struct icmphdr header; + char msg[64 - sizeof(struct icmphdr)]; + }; + + struct PongPacket { + // FIXME: IPv4 headers are not actually fixed-size, handle other sizes. + char ip_header[20]; + struct icmphdr header; + char msg[64 - sizeof(struct icmphdr)]; + }; + + uint16_t seq = 1; + + sighandler_t ret = signal(SIGINT, [](int) { + int packet_loss = 100; + + printf("\n--- %s ping statistics ---\n", host); + + if (total_pings) + packet_loss -= 100.0f * successful_pings / total_pings; + printf("%d packets transmitted, %d received, %d%% packet loss\n", + total_pings, successful_pings, packet_loss); + + int average_ms = 0; + if (successful_pings) + average_ms = total_ms / successful_pings; + printf("rtt min/avg/max = %d/%d/%d ms\n", min_ms, average_ms, max_ms); + + exit(0); + }); + + if (ret == SIG_ERR) { + perror("failed to install SIGINT handler"); + return 1; + } + + for (;;) { + PingPacket ping_packet; + memset(&ping_packet, 0, sizeof(PingPacket)); + + ping_packet.header.type = 8; // Echo request + ping_packet.header.code = 0; + ping_packet.header.un.echo.id = htons(pid); + ping_packet.header.un.echo.sequence = htons(seq++); + + bool fits = String("Hello there!\n").copy_characters_to_buffer(ping_packet.msg, sizeof(ping_packet.msg)); + // It's a constant string, we can be sure that it fits. + ASSERT(fits); + + ping_packet.header.checksum = internet_checksum(&ping_packet, sizeof(PingPacket)); + + struct timeval tv_send; + gettimeofday(&tv_send, nullptr); + + rc = sendto(fd, &ping_packet, sizeof(PingPacket), 0, (const struct sockaddr*)&peer_address, sizeof(sockaddr_in)); + if (rc < 0) { + perror("sendto"); + return 1; + } + + total_pings++; + + for (;;) { + PongPacket pong_packet; + socklen_t peer_address_size = sizeof(peer_address); + rc = recvfrom(fd, &pong_packet, sizeof(PongPacket), 0, (struct sockaddr*)&peer_address, &peer_address_size); + if (rc < 0) { + if (errno == EAGAIN) { + printf("Request (seq=%u) timed out.\n", ntohs(ping_packet.header.un.echo.sequence)); + break; + } + perror("recvfrom"); + return 1; + } + + if (pong_packet.header.type != 0) + continue; + if (pong_packet.header.code != 0) + continue; + if (ntohs(pong_packet.header.un.echo.id) != pid) + continue; + + struct timeval tv_receive; + gettimeofday(&tv_receive, nullptr); + + struct timeval tv_diff; + timersub(&tv_receive, &tv_send, &tv_diff); + + int ms = tv_diff.tv_sec * 1000 + tv_diff.tv_usec / 1000; + successful_pings++; + int seq_dif = ntohs(ping_packet.header.un.echo.sequence) - ntohs(pong_packet.header.un.echo.sequence); + + // Approximation about the timeout of the out of order packet + if (seq_dif) + ms += seq_dif * 1000 * timeout.tv_sec; + + total_ms += ms; + if (min_ms == 0) + min_ms = max_ms = ms; + else if (ms < min_ms) + min_ms = ms; + else if (ms > max_ms) + max_ms = ms; + + char addr_buf[64]; + printf("Pong from %s: id=%u, seq=%u%s, time=%dms\n", + inet_ntop(AF_INET, &peer_address.sin_addr, addr_buf, sizeof(addr_buf)), + ntohs(pong_packet.header.un.echo.id), + ntohs(pong_packet.header.un.echo.sequence), + pong_packet.header.un.echo.sequence != ping_packet.header.un.echo.sequence ? "(!)" : "", + ms); + + // If this was a response to an earlier packet, we still need to wait for the current one. + if (pong_packet.header.un.echo.sequence != ping_packet.header.un.echo.sequence) + continue; + break; + } + + sleep(1); + } + + return 0; +} diff --git a/Userland/Utilities/pmap.cpp b/Userland/Utilities/pmap.cpp new file mode 100644 index 0000000000..a0cdf12b72 --- /dev/null +++ b/Userland/Utilities/pmap.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/proc", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + const char* pid; + static bool extended = false; + + Core::ArgsParser args_parser; + args_parser.add_option(extended, "Extended output", nullptr, 'x'); + args_parser.add_positional_argument(pid, "PID", "PID", Core::ArgsParser::Required::Yes); + args_parser.parse(argc, argv); + + auto file = Core::File::construct(String::format("/proc/%s/vm", pid)); + if (!file->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Error: %s\n", file->error_string()); + return 1; + } + + printf("%s:\n", pid); + + if (extended) { + printf("Address Size Resident Dirty Access VMObject Type Purgeable CoW Pages Name\n"); + } else { + printf("Address Size Access Name\n"); + } + + auto file_contents = file->read_all(); + auto json = JsonValue::from_string(file_contents); + ASSERT(json.has_value()); + json.value().as_array().for_each([](auto& value) { + auto map = value.as_object(); + auto address = map.get("address").to_int(); + auto size = map.get("size").to_string(); + + auto readable = map.get("readable").to_bool(); + auto writable = map.get("writable").to_bool(); + auto executable = map.get("executable").to_bool(); + auto access = String::format("%s%s%s", (readable ? "r" : "-"), (writable ? "w" : "-"), (executable ? "x" : "-")); + + printf("%08x ", address); + printf("%-10s ", size.characters()); + if (extended) { + auto resident = map.get("amount_resident").to_string(); + auto dirty = map.get("amount_dirty").to_string(); + auto vmobject = map.get("vmobject").to_string(); + auto purgeable = map.get("purgeable").to_string(); + auto cow_pages = map.get("cow_pages").to_string(); + printf("%-10s ", resident.characters()); + printf("%-10s ", dirty.characters()); + printf("%-6s ", access.characters()); + printf("%-22s ", vmobject.characters()); + printf("%-10s ", purgeable.characters()); + printf("%-12s ", cow_pages.characters()); + } else { + printf("%-6s ", access.characters()); + } + auto name = map.get("name").to_string(); + printf("%-20s ", name.characters()); + printf("\n"); + }); + + return 0; +} diff --git a/Userland/Utilities/printf.cpp b/Userland/Utilities/printf.cpp new file mode 100644 index 0000000000..bb434e761c --- /dev/null +++ b/Userland/Utilities/printf.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +[[gnu::noreturn]] static void fail(const char* message) +{ + fputs("\e[31m", stderr); + fputs(message, stderr); + fputs("\e[0m\n", stderr); + exit(1); +} + +template typename NextArgument> +struct PrintfImpl : public PrintfImplementation::PrintfImpl { + ALWAYS_INLINE PrintfImpl(PutChFunc& putch, char*& bufptr, const int& nwritten) + : PrintfImplementation::PrintfImpl(putch, bufptr, nwritten) + { + } + + ALWAYS_INLINE int format_q(const PrintfImplementation::ModifierState& state, ArgumentListRefT& ap) const + { + auto state_copy = state; + auto str = NextArgument()(ap); + if (!str) + str = "(null)"; + + constexpr auto make_len_or_escape = [](auto str, bool mk_len, size_t field_width, auto putc) { + unsigned len = 2; + if (!mk_len) + putc('"'); + + for (size_t i = 0; str[i] && (mk_len ? true : (field_width >= len)); ++i) { + auto ch = str[i]; + switch (ch) { + case '"': + case '$': + case '\\': + ++len; + if (!mk_len) + putc('\\'); + } + ++len; + if (!mk_len) + putc(ch); + } + + if (!mk_len) + putc('"'); + + return len; + }; + + auto len = make_len_or_escape(str, true, state_copy.field_width, [&](auto c) { this->m_putch(this->m_bufptr, c); }); + + if (!state_copy.dot && (!state_copy.field_width || state_copy.field_width < len)) + state_copy.field_width = len; + size_t pad_amount = state_copy.field_width > len ? state_copy.field_width - len : 0; + + if (!state_copy.left_pad) { + for (size_t i = 0; i < pad_amount; ++i) + this->m_putch(this->m_bufptr, ' '); + } + + make_len_or_escape(str, false, state_copy.field_width, [&](auto c) { this->m_putch(this->m_bufptr, c); }); + + if (state_copy.left_pad) { + for (size_t i = 0; i < pad_amount; ++i) + this->m_putch(this->m_bufptr, ' '); + } + + return state_copy.field_width; + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE T operator()(V) const + { + static_assert(sizeof(V) != sizeof(V), "Base instantiated"); + return declval(); + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE char* operator()(V arg) const + { + if (arg.argc == 0) + fail("Not enough arguments"); + + auto result = *arg.argv++; + --arg.argc; + return result; + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE const char* operator()(V arg) const + { + if (arg.argc == 0) + return ""; + + auto result = *arg.argv++; + --arg.argc; + return result; + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE int operator()(V arg) const + { + if (arg.argc == 0) + return 0; + + auto result = *arg.argv++; + --arg.argc; + return atoi(result); + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE unsigned operator()(V arg) const + { + if (arg.argc == 0) + return 0; + + auto result = *arg.argv++; + --arg.argc; + return strtoul(result, nullptr, 10); + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE i64 operator()(V arg) const + { + if (arg.argc == 0) + return 0; + + auto result = *arg.argv++; + --arg.argc; + return strtoll(result, nullptr, 10); + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE u64 operator()(V arg) const + { + if (arg.argc == 0) + return 0; + + auto result = *arg.argv++; + --arg.argc; + return strtoull(result, nullptr, 10); + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE double operator()(V arg) const + { + if (arg.argc == 0) + return 0; + + auto result = *arg.argv++; + --arg.argc; + return strtod(result, nullptr); + } +}; + +template +struct ArgvNextArgument { + ALWAYS_INLINE int* operator()(V) const + { + ASSERT_NOT_REACHED(); + return nullptr; + } +}; + +struct ArgvWithCount { + char**& argv; + int& argc; +}; + +static String handle_escapes(const char* string) +{ + StringBuilder builder; + for (auto c = *string; c; c = *++string) { + if (c == '\\') { + if (string[1]) { + switch (c = *++string) { + case '\\': + case '"': + builder.append(c); + break; + case 'a': + builder.append('\a'); + break; + case 'b': + builder.append('\b'); + break; + case 'c': + return builder.build(); + case 'e': + builder.append('\e'); + break; + case 'f': + builder.append('\f'); + break; + case 'n': + builder.append('\n'); + break; + case 'r': + builder.append('\r'); + break; + case 't': + builder.append('\t'); + break; + case 'v': + builder.append('\v'); + break; + case 'x': + fail("Unsupported escape '\\x'"); + case 'u': + fail("Unsupported escape '\\u'"); + case 'U': + fail("Unsupported escape '\\U'"); + default: + builder.append(c); + } + } else { + builder.append(c); + } + } else { + builder.append(c); + } + } + + return builder.build(); +} + +int main(int argc, char** argv) +{ + if (argc < 2) + return 1; + + ++argv; + String format = handle_escapes(*(argv++)); + auto format_string = format.characters(); + + argc -= 2; + + ArgvWithCount arg { argv, argc }; + + auto putch = [](auto*, auto ch) { + putchar(ch); + }; + + auto previous_argc = 0; + do { + previous_argc = argc; + PrintfImplementation::printf_internal(putch, nullptr, format_string, arg); + } while (argc && previous_argc != argc); + + return 0; +} diff --git a/Userland/Utilities/pro.cpp b/Userland/Utilities/pro.cpp new file mode 100644 index 0000000000..95f9a74ed0 --- /dev/null +++ b/Userland/Utilities/pro.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// FIXME: Move this somewhere else when it's needed (e.g. in the Browser) +class ContentDispositionParser { +public: + ContentDispositionParser(const StringView& value) + { + GenericLexer lexer(value); + + lexer.ignore_while(isspace); + + if (lexer.consume_specific("inline")) { + m_kind = Kind::Inline; + if (!lexer.is_eof()) + m_might_be_wrong = true; + return; + } + + if (lexer.consume_specific("attachment")) { + m_kind = Kind::Attachment; + if (lexer.consume_specific(";")) { + lexer.ignore_while(isspace); + if (lexer.consume_specific("filename=")) { + // RFC 2183: "A short (length <= 78 characters) + // parameter value containing only non-`tspecials' characters SHOULD be + // represented as a single `token'." + // Some people seem to take this as generic advice of "if it doesn't have special characters, + // it's safe to specify as a single token" + // So let's just be as lenient as possible. + if (lexer.next_is('"')) + m_filename = lexer.consume_quoted_string(); + else + m_filename = lexer.consume_until(is_any_of("()<>@,;:\\\"/[]?= ")); + } else { + m_might_be_wrong = true; + } + } + return; + } + + if (lexer.consume_specific("form-data")) { + m_kind = Kind::FormData; + while (lexer.consume_specific(";")) { + lexer.ignore_while(isspace); + if (lexer.consume_specific("name=")) { + m_name = lexer.consume_quoted_string(); + } else if (lexer.consume_specific("filename=")) { + if (lexer.next_is('"')) + m_filename = lexer.consume_quoted_string(); + else + m_filename = lexer.consume_until(is_any_of("()<>@,;:\\\"/[]?= ")); + } else { + m_might_be_wrong = true; + } + } + + return; + } + + // FIXME: Support 'filename*' + m_might_be_wrong = true; + } + + enum class Kind { + Inline, + Attachment, + FormData, + }; + + const StringView& filename() const { return m_filename; } + const StringView& name() const { return m_name; } + Kind kind() const { return m_kind; } + bool might_be_wrong() const { return m_might_be_wrong; } + +private: + StringView m_filename; + StringView m_name; + Kind m_kind { Kind::Inline }; + bool m_might_be_wrong { false }; +}; + +template +class ConditionalOutputFileStream final : public OutputFileStream { +public: + template + ConditionalOutputFileStream(ConditionT&& condition, Args... args) + : OutputFileStream(args...) + , m_condition(condition) + { + } + + ~ConditionalOutputFileStream() + { + if (!m_condition()) + return; + + if (!m_buffer.is_empty()) { + OutputFileStream::write(m_buffer); + m_buffer.clear(); + } + } + +private: + size_t write(ReadonlyBytes bytes) override + { + if (!m_condition()) { + write_to_buffer:; + m_buffer.append(bytes.data(), bytes.size()); + return bytes.size(); + } + + if (!m_buffer.is_empty()) { + auto size = OutputFileStream::write(m_buffer); + m_buffer = m_buffer.slice(size, m_buffer.size() - size); + } + + if (!m_buffer.is_empty()) + goto write_to_buffer; + + return OutputFileStream::write(bytes); + } + + ConditionT m_condition; + ByteBuffer m_buffer; +}; + +int main(int argc, char** argv) +{ + const char* url_str = nullptr; + bool save_at_provided_name = false; + const char* data = nullptr; + String method = "GET"; + HashMap request_headers; + + Core::ArgsParser args_parser; + args_parser.set_general_help( + "Download a file from an arbitrary URL. This command uses ProtocolServer, " + "and thus supports at least http, https, and gemini."); + args_parser.add_option(save_at_provided_name, "Write to a file named as the remote file", nullptr, 'O'); + args_parser.add_option(data, "(HTTP only) Send the provided data via an HTTP POST request", "data", 'd', "data"); + args_parser.add_option(Core::ArgsParser::Option { + .requires_argument = true, + .help_string = "Add a header entry to the request", + .long_name = "header", + .short_name = 'H', + .value_name = "header-value", + .accept_value = [&](auto* s) { + StringView header { s }; + auto split = header.find_first_of(':'); + if (!split.has_value()) + return false; + request_headers.set(header.substring_view(0, split.value()), header.substring_view(split.value() + 1)); + return true; + } }); + args_parser.add_positional_argument(url_str, "URL to download from", "url"); + args_parser.parse(argc, argv); + + if (data) { + method = "POST"; + // FIXME: Content-Type? + } + + URL url(url_str); + if (!url.is_valid()) { + fprintf(stderr, "'%s' is not a valid URL\n", url_str); + return 1; + } + + Core::EventLoop loop; + auto protocol_client = Protocol::Client::construct(); + + auto download = protocol_client->start_download(method, url.to_string(), request_headers, data ? StringView { data }.bytes() : ReadonlyBytes {}); + if (!download) { + fprintf(stderr, "Failed to start download for '%s'\n", url_str); + return 1; + } + + u32 previous_downloaded_size { 0 }; + timeval prev_time, current_time, time_diff; + gettimeofday(&prev_time, nullptr); + + bool received_actual_headers = false; + + download->on_progress = [&](Optional maybe_total_size, u32 downloaded_size) { + fprintf(stderr, "\r\033[2K"); + if (maybe_total_size.has_value()) { + fprintf(stderr, "\033]9;%d;%d;\033\\", downloaded_size, maybe_total_size.value()); + fprintf(stderr, "Download progress: %s / %s", human_readable_size(downloaded_size).characters(), human_readable_size(maybe_total_size.value()).characters()); + } else { + fprintf(stderr, "Download progress: %s / ???", human_readable_size(downloaded_size).characters()); + } + + gettimeofday(¤t_time, nullptr); + timersub(¤t_time, &prev_time, &time_diff); + + auto time_diff_ms = time_diff.tv_sec * 1000 + time_diff.tv_usec / 1000; + auto size_diff = downloaded_size - previous_downloaded_size; + + fprintf(stderr, " at %s/s", human_readable_size(((float)size_diff / (float)time_diff_ms) * 1000).characters()); + + previous_downloaded_size = downloaded_size; + prev_time = current_time; + }; + + if (save_at_provided_name) { + download->on_headers_received = [&](auto& response_headers, auto status_code) { + if (received_actual_headers) + return; + dbgln("Received headers! response code = {}", status_code.value_or(0)); + received_actual_headers = true; // And not trailers! + String output_name; + if (auto content_disposition = response_headers.get("Content-Disposition"); content_disposition.has_value()) { + auto& value = content_disposition.value(); + ContentDispositionParser parser(value); + output_name = parser.filename(); + } + + if (output_name.is_empty()) + output_name = url.path(); + + LexicalPath path { output_name }; + output_name = path.basename(); + + // The URL didn't have a name component, e.g. 'serenityos.org' + if (output_name.is_empty() || output_name == "/") { + int i = -1; + do { + output_name = url.host(); + if (i > -1) + output_name = String::format("%s.%d", output_name.characters(), i); + ++i; + } while (Core::File::exists(output_name)); + } + + if (freopen(output_name.characters(), "w", stdout) == nullptr) { + perror("freopen"); + loop.quit(1); + return; + } + }; + } + download->on_finish = [&](bool success, auto) { + fprintf(stderr, "\033]9;-1;\033\\"); + fprintf(stderr, "\n"); + if (!success) + fprintf(stderr, "Download failed :(\n"); + loop.quit(0); + }; + + auto output_stream = ConditionalOutputFileStream { [&] { return save_at_provided_name ? received_actual_headers : true; }, stdout }; + download->stream_into(output_stream); + + dbgln("started download with id {}", download->id()); + + auto rc = loop.exec(); + // FIXME: This shouldn't be needed. + fclose(stdout); + return rc; +} diff --git a/Userland/Utilities/profile.cpp b/Userland/Utilities/profile.cpp new file mode 100644 index 0000000000..915518beb0 --- /dev/null +++ b/Userland/Utilities/profile.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + Core::ArgsParser args_parser; + + const char* pid_argument = nullptr; + const char* cmd_argument = nullptr; + bool enable = false; + bool disable = false; + + args_parser.add_option(pid_argument, "Target PID", nullptr, 'p', "PID"); + args_parser.add_option(enable, "Enable", nullptr, 'e'); + args_parser.add_option(disable, "Disable", nullptr, 'd'); + args_parser.add_option(cmd_argument, "Command", nullptr, 'c', "command"); + + args_parser.parse(argc, argv); + + if (!pid_argument && !cmd_argument) { + args_parser.print_usage(stdout, argv[0]); + return 0; + } + + if (pid_argument) { + if (!(enable ^ disable)) { + fprintf(stderr, "-p requires -e xor -d.\n"); + return 1; + } + + pid_t pid = atoi(pid_argument); + + if (enable) { + if (profiling_enable(pid) < 0) { + perror("profiling_enable"); + return 1; + } + return 0; + } + + if (profiling_disable(pid) < 0) { + perror("profiling_disable"); + return 1; + } + + return 0; + } + + auto cmd_parts = String(cmd_argument).split(' '); + Vector cmd_argv; + + for (auto& part : cmd_parts) + cmd_argv.append(part.characters()); + + cmd_argv.append(nullptr); + + dbgln("Enabling profiling for PID {}", getpid()); + profiling_enable(getpid()); + if (execvp(cmd_argv[0], const_cast(cmd_argv.data())) < 0) { + perror("execv"); + return 1; + } + + return 0; +} diff --git a/Userland/Utilities/ps.cpp b/Userland/Utilities/ps.cpp new file mode 100644 index 0000000000..48083c3a46 --- /dev/null +++ b/Userland/Utilities/ps.cpp @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath tty", nullptr) < 0) { + perror("pledge"); + return 1; + } + + String this_tty = ttyname(STDIN_FILENO); + + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/proc/all", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/passwd", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + enum class Alignment { + Left, + Right, + }; + + struct Column { + String title; + Alignment alignment { Alignment::Left }; + int width { 0 }; + String buffer; + }; + + bool every_process_flag = false; + bool full_format_flag = false; + + Core::ArgsParser args_parser; + args_parser.add_option(every_process_flag, "Show every process", nullptr, 'e'); + args_parser.add_option(full_format_flag, "Full format", nullptr, 'f'); + args_parser.parse(argc, argv); + + Vector columns; + + int uid_column = -1; + int pid_column = -1; + int ppid_column = -1; + int state_column = -1; + int tty_column = -1; + int cmd_column = -1; + + auto add_column = [&](auto title, auto alignment, auto width) { + columns.append({ title, alignment, width, {} }); + return columns.size() - 1; + }; + + if (full_format_flag) { + uid_column = add_column("UID", Alignment::Left, 9); + pid_column = add_column("PID", Alignment::Right, 5); + ppid_column = add_column("PPID", Alignment::Right, 5); + state_column = add_column("STATE", Alignment::Left, 12); + tty_column = add_column("TTY", Alignment::Left, 6); + cmd_column = add_column("CMD", Alignment::Left, 0); + } else { + pid_column = add_column("PID", Alignment::Right, 5); + tty_column = add_column("TTY", Alignment::Left, 6); + cmd_column = add_column("CMD", Alignment::Left, 0); + } + + auto print_column = [](auto& column, auto& string) { + if (!column.width) { + printf("%s", string.characters()); + return; + } + if (column.alignment == Alignment::Right) + printf("%*s ", column.width, string.characters()); + else + printf("%-*s ", column.width, string.characters()); + }; + + for (auto& column : columns) + print_column(column, column.title); + printf("\n"); + + auto all_processes = Core::ProcessStatisticsReader::get_all(); + if (!all_processes.has_value()) + return 1; + + for (const auto& it : all_processes.value()) { + const auto& proc = it.value; + auto tty = proc.tty; + + if (!every_process_flag && tty != this_tty) + continue; + + if (tty.starts_with("/dev/")) + tty = tty.characters() + 5; + else + tty = "n/a"; + + auto* state = proc.threads.is_empty() ? "Zombie" : proc.threads.first().state.characters(); + + if (uid_column != -1) + columns[uid_column].buffer = proc.username; + if (pid_column != -1) + columns[pid_column].buffer = String::number(proc.pid); + if (ppid_column != -1) + columns[ppid_column].buffer = String::number(proc.ppid); + if (tty_column != -1) + columns[tty_column].buffer = tty; + if (state_column != -1) + columns[state_column].buffer = state; + if (cmd_column != -1) + columns[cmd_column].buffer = proc.name; + + for (auto& column : columns) + print_column(column, column.buffer); + printf("\n"); + } + + return 0; +} diff --git a/Userland/Utilities/purge.cpp b/Userland/Utilities/purge.cpp new file mode 100644 index 0000000000..95257ec563 --- /dev/null +++ b/Userland/Utilities/purge.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + int mode = 0; + + bool purge_all_volatile = false; + bool purge_all_clean_inode = false; + + Core::ArgsParser args_parser; + args_parser.add_option(purge_all_volatile, "Mode PURGE_ALL_VOLATILE", nullptr, 'v'); + args_parser.add_option(purge_all_clean_inode, "Mode PURGE_ALL_CLEAN_INODE", nullptr, 'c'); + args_parser.parse(argc, argv); + + if (!purge_all_volatile && !purge_all_clean_inode) + purge_all_volatile = purge_all_clean_inode = true; + + if (purge_all_volatile) + mode |= PURGE_ALL_VOLATILE; + if (purge_all_clean_inode) + mode |= PURGE_ALL_CLEAN_INODE; + + int purged_page_count = purge(mode); + if (purged_page_count < 0) { + perror("purge"); + return 1; + } + printf("Purged page count: %d\n", purged_page_count); + return 0; +} diff --git a/Userland/Utilities/readelf.cpp b/Userland/Utilities/readelf.cpp new file mode 100644 index 0000000000..08bd2e6607 --- /dev/null +++ b/Userland/Utilities/readelf.cpp @@ -0,0 +1,452 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char* object_file_type_to_string(Elf32_Half type) +{ + switch (type) { + case ET_NONE: + return "None"; + case ET_REL: + return "Relocatable"; + case ET_EXEC: + return "Executable"; + case ET_DYN: + return "Shared object"; + case ET_CORE: + return "Core"; + default: + return "(?)"; + } +} + +static const char* object_machine_type_to_string(Elf32_Half type) +{ + switch (type) { + case ET_NONE: + return "None"; + case EM_M32: + return "AT&T WE 32100"; + case EM_SPARC: + return "SPARC"; + case EM_386: + return "Intel 80386"; + case EM_68K: + return "Motorola 68000"; + case EM_88K: + return "Motorola 88000"; + case EM_486: + return "Intel 80486"; + case EM_860: + return "Intel 80860"; + case EM_MIPS: + return "MIPS R3000 Big-Endian only"; + default: + return "(?)"; + } +} + +static const char* object_program_header_type_to_string(Elf32_Word type) +{ + 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_SHLIB: + return "SHLIB"; + case PT_PHDR: + return "PHDR"; + case PT_TLS: + return "TLS"; + case PT_LOOS: + return "LOOS"; + case PT_HIOS: + return "HIOS"; + case PT_LOPROC: + return "LOPROC"; + case PT_HIPROC: + return "HIPROC"; + case PT_GNU_EH_FRAME: + return "GNU_EH_FRAME"; + case PT_GNU_RELRO: + return "GNU_RELRO"; + case PT_GNU_STACK: + return "GNU_STACK"; + case PT_OPENBSD_RANDOMIZE: + return "OPENBSD_RANDOMIZE"; + case PT_OPENBSD_WXNEEDED: + return "OPENBSD_WXNEEDED"; + case PT_OPENBSD_BOOTDATA: + return "OPENBSD_BOOTDATA"; + default: + return "(?)"; + } +} + +static const char* object_section_header_type_to_string(Elf32_Word type) +{ + 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 SHT_NUM: + return "NUM"; + case SHT_INIT_ARRAY: + return "INIT_ARRAY"; + case SHT_FINI_ARRAY: + return "FINI_ARRAY"; + case SHT_PREINIT_ARRAY: + return "PREINIT_ARRAY"; + case SHT_GROUP: + return "GROUP"; + case SHT_SYMTAB_SHNDX: + return "SYMTAB_SHNDX"; + case SHT_LOOS: + return "SOOS"; + case SHT_SUNW_dof: + return "SUNW_dof"; + case SHT_GNU_LIBLIST: + return "GNU_LIBLIST"; + case SHT_SUNW_move: + return "SUNW_move"; + case SHT_SUNW_syminfo: + return "SUNW_syminfo"; + case SHT_SUNW_verdef: + return "SUNW_verdef"; + case SHT_SUNW_verneed: + return "SUNW_verneed"; + case SHT_SUNW_versym: // or SHT_HIOS + return "SUNW_versym"; + case SHT_LOPROC: + return "LOPROC"; + case SHT_HIPROC: + return "HIPROC"; + case SHT_LOUSER: + return "LOUSER"; + case SHT_HIUSER: + return "HIUSER"; + case SHT_GNU_HASH: + return "GNU_HASH"; + default: + return "(?)"; + } +} + +static const char* object_symbol_type_to_string(Elf32_Word type) +{ + 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"; + case STT_TLS: + return "TLS"; + case STT_LOPROC: + return "LOPROC"; + case STT_HIPROC: + return "HIPROC"; + default: + return "(?)"; + } +} + +static const char* object_symbol_binding_to_string(Elf32_Word type) +{ + switch (type) { + case STB_LOCAL: + return "LOCAL"; + case STB_GLOBAL: + return "GLOBAL"; + case STB_WEAK: + return "WEAK"; + case STB_NUM: + return "NUM"; + case STB_LOPROC: + return "LOPROC"; + case STB_HIPROC: + return "HIPROC"; + default: + return "(?)"; + } +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* path; + static bool display_all = false; + static bool display_elf_header = false; + static bool display_program_headers = false; + static bool display_section_headers = false; + static bool display_headers = false; + static bool display_symbol_table = false; + static bool display_dynamic_symbol_table = false; + static bool display_core_notes = false; + static bool display_relocations = false; + static bool display_unwind_info = false; + static bool display_dynamic_section = false; + + Core::ArgsParser args_parser; + args_parser.add_option(display_all, "Display all", "all", 'a'); + args_parser.add_option(display_elf_header, "Display ELF header", "file-header", 'h'); + args_parser.add_option(display_program_headers, "Display program headers", "program-headers", 'l'); + args_parser.add_option(display_section_headers, "Display section headers", "section-headers", 'S'); + args_parser.add_option(display_headers, "Equivalent to: -h -l -S", "headers", 'e'); + args_parser.add_option(display_symbol_table, "Display the symbol table", "syms", 's'); + args_parser.add_positional_argument(path, "ELF path", "path"); + args_parser.parse(argc, argv); + + if (argc < 3) { + args_parser.print_usage(stderr, argv[0]); + return -1; + } + + if (display_headers) { + display_elf_header = true; + display_program_headers = true; + display_section_headers = true; + } + + if (display_all) { + display_elf_header = true; + display_program_headers = true; + display_section_headers = true; + display_symbol_table = true; + } + + auto file_or_error = MappedFile::map(path); + + if (file_or_error.is_error()) { + warnln("Unable to map file {}: {}", path, file_or_error.error()); + return -1; + } + + auto elf_image_data = file_or_error.value()->bytes(); + ELF::Image executable_elf(elf_image_data); + + if (!executable_elf.is_valid()) { + fprintf(stderr, "File is not a valid ELF object\n"); + return -1; + } + + String interpreter_path; + + if (!ELF::validate_program_headers(*(const Elf32_Ehdr*)elf_image_data.data(), elf_image_data.size(), (const u8*)elf_image_data.data(), elf_image_data.size(), &interpreter_path)) { + fprintf(stderr, "Invalid ELF headers\n"); + return -1; + } + + if (executable_elf.is_dynamic() && interpreter_path.is_null()) { + fprintf(stderr, "Warning: Dynamic ELF object has no interpreter path\n"); + } + + ELF::Image interpreter_image(elf_image_data); + + if (!interpreter_image.is_valid()) { + fprintf(stderr, "ELF image is invalid\n"); + return -1; + } + + auto& header = *reinterpret_cast(elf_image_data.data()); + + if (display_elf_header) { + printf("ELF header:\n"); + + String magic = String::format("%s", header.e_ident); + printf(" Magic: "); + for (size_t i = 0; i < magic.length(); i++) { + if (isprint(magic[i])) { + printf("%c ", magic[i]); + } else { + printf("%02x ", magic[i]); + } + } + printf("\n"); + + printf(" Type: %d (%s)\n", header.e_type, object_file_type_to_string(header.e_type)); + printf(" Machine: %u (%s)\n", header.e_machine, object_machine_type_to_string(header.e_machine)); + printf(" Version: 0x%x\n", header.e_version); + printf(" Entry point address: 0x%x\n", header.e_entry); + printf(" Start of program headers: %u (bytes into file)\n", header.e_phoff); + printf(" Start of section headers: %u (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); + printf("\n"); + } + + if (display_section_headers) { + if (!display_all) { + printf("There are %u section headers, starting at offset 0x%x:\n", header.e_shnum, header.e_shoff); + printf("\n"); + } + + if (!interpreter_image.section_count()) { + printf("There are no sections in this file.\n"); + } else { + printf("Section Headers:\n"); + printf(" Name Type Address Offset Size Flags\n"); + + interpreter_image.for_each_section([](const ELF::Image::Section& section) { + printf(" %-15s ", section.name().characters_without_null_termination()); + printf("%-15s ", object_section_header_type_to_string(section.type())); + printf("%08x ", section.address()); + printf("%08x ", section.offset()); + printf("%08x ", section.size()); + printf("%u", section.flags()); + printf("\n"); + return IterationDecision::Continue; + }); + } + printf("\n"); + } + + if (display_program_headers) { + if (!display_all) { + printf("Elf file type is %d (%s)\n", header.e_type, object_file_type_to_string(header.e_type)); + printf("Entry point 0x%x\n", header.e_entry); + printf("There are %u program headers, starting at offset %u\n", header.e_phnum, header.e_phoff); + printf("\n"); + } + + printf("Program Headers:\n"); + printf(" Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align\n"); + + interpreter_image.for_each_program_header([](const ELF::Image::ProgramHeader& program_header) { + printf(" %-14s ", object_program_header_type_to_string(program_header.type())); + printf("0x%08x ", program_header.offset()); + printf("%p ", program_header.vaddr().as_ptr()); + printf("%p ", program_header.vaddr().as_ptr()); // FIXME: assumes PhysAddr = VirtAddr + printf("0x%08x ", program_header.size_in_image()); + printf("0x%08x ", program_header.size_in_memory()); + printf("%04x ", program_header.flags()); + printf("0x%08x", program_header.alignment()); + printf("\n"); + + if (program_header.type() == PT_INTERP) + printf(" [Interpreter: %s]\n", program_header.raw_data()); + + return IterationDecision::Continue; + }); + printf("\n"); + } + + if (display_dynamic_section) { + // TODO: Dynamic section + } + + if (display_relocations) { + // TODO: Relocations section + } + + if (display_unwind_info) { + // TODO: Unwind info + } + + if (display_core_notes) { + // TODO: Core notes + } + + if (display_dynamic_symbol_table || display_symbol_table) { + // TODO: dynamic symbol table + if (!interpreter_image.symbol_count()) { + printf("Dynamic symbol information is not available for displaying symbols.\n"); + printf("\n"); + } + } + + if (display_symbol_table) { + if (interpreter_image.symbol_count()) { + printf("Symbol table '.symtab' contains %u entries:\n", interpreter_image.symbol_count()); + printf(" Num: Value Size Type Bind Name\n"); + + interpreter_image.for_each_symbol([](const ELF::Image::Symbol& sym) { + printf(" %4u: ", sym.index()); + printf("%08x ", sym.value()); + printf("%08x ", sym.size()); + printf("%-8s ", object_symbol_type_to_string(sym.type())); + printf("%-8s ", object_symbol_binding_to_string(sym.bind())); + printf("%s", sym.name().characters_without_null_termination()); + printf("\n"); + return IterationDecision::Continue; + }); + } + printf("\n"); + } + + return 0; +} diff --git a/Userland/Utilities/readlink.cpp b/Userland/Utilities/readlink.cpp new file mode 100644 index 0000000000..de65e6212a --- /dev/null +++ b/Userland/Utilities/readlink.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool no_newline = false; + Vector paths; + + Core::ArgsParser args_parser; + args_parser.add_option(no_newline, "Do not append a newline", "no-newline", 'n'); + args_parser.add_positional_argument(paths, "Symlink path", "path"); + args_parser.parse(argc, argv); + + for (const char* path : paths) { + auto destination = Core::File::read_link(path); + if (destination.is_null()) { + perror(path); + return 1; + } + printf("%s", destination.characters()); + if (!no_newline) + putchar('\n'); + } + + return 0; +} diff --git a/Userland/Utilities/realpath.cpp b/Userland/Utilities/realpath.cpp new file mode 100644 index 0000000000..328f84a1f7 --- /dev/null +++ b/Userland/Utilities/realpath.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* path; + + Core::ArgsParser args_parser; + args_parser.set_general_help( + "Show the 'real' path of a file, by resolving all symbolic links along the way."); + args_parser.add_positional_argument(path, "Path to resolve", "path"); + args_parser.parse(argc, argv); + + char* value = realpath(path, nullptr); + if (value == nullptr) { + perror("realpath"); + return 1; + } + printf("%s\n", value); + free(value); + return 0; +} diff --git a/Userland/Utilities/reboot.cpp b/Userland/Utilities/reboot.cpp new file mode 100644 index 0000000000..ddca508b98 --- /dev/null +++ b/Userland/Utilities/reboot.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +int main(int, char**) +{ + if (reboot() < 0) { + perror("reboot"); + return 1; + } + return 0; +} diff --git a/Userland/Utilities/rm.cpp b/Userland/Utilities/rm.cpp new file mode 100644 index 0000000000..d563a294df --- /dev/null +++ b/Userland/Utilities/rm.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int remove(bool recursive, bool force, String path) +{ + struct stat path_stat; + if (lstat(path.characters(), &path_stat) < 0) { + if (!force) + perror("lstat"); + return force ? 0 : 1; + } + + if (S_ISDIR(path_stat.st_mode) && recursive) { + auto di = Core::DirIterator(path, Core::DirIterator::SkipParentAndBaseDir); + if (di.has_error()) { + if (!force) + fprintf(stderr, "DirIterator: %s\n", di.error_string()); + return 1; + } + + while (di.has_next()) { + int s = remove(true, force, di.next_full_path()); + if (s != 0 && !force) + return s; + } + + int s = rmdir(path.characters()); + if (s < 0 && !force) { + perror("rmdir"); + return 1; + } + } else { + int rc = unlink(path.characters()); + if (rc < 0 && !force) { + perror("unlink"); + return 1; + } + } + return 0; +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath cpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool recursive = false; + bool force = false; + bool verbose = false; + Vector paths; + + Core::ArgsParser args_parser; + args_parser.add_option(recursive, "Delete directories recursively", "recursive", 'r'); + args_parser.add_option(force, "Force", "force", 'f'); + args_parser.add_option(verbose, "Verbose", "verbose", 'v'); + args_parser.add_positional_argument(paths, "Path(s) to remove", "path"); + args_parser.parse(argc, argv); + + int rc = 0; + for (auto& path : paths) { + rc |= remove(recursive, force, path); + if (verbose && rc == 0) + printf("removed '%s'\n", path); + } + return rc; +} diff --git a/Userland/Utilities/rmdir.cpp b/Userland/Utilities/rmdir.cpp new file mode 100644 index 0000000000..4d321bb9ab --- /dev/null +++ b/Userland/Utilities/rmdir.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio cpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* path; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(path, "Directory to remove", "path"); + args_parser.parse(argc, argv); + + int rc = rmdir(path); + if (rc < 0) { + perror("rmdir"); + return 1; + } + return 0; +} diff --git a/Userland/Utilities/seq.cpp b/Userland/Utilities/seq.cpp new file mode 100644 index 0000000000..a7a6ea7c22 --- /dev/null +++ b/Userland/Utilities/seq.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2020, Nico Weber + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +const char* const g_usage = R"(Usage: + seq [-h|--help] + seq LAST + seq FIRST LAST + seq FIRST INCREMENT LAST +)"; + +static void print_usage(FILE* stream) +{ + fputs(g_usage, stream); + return; +} + +static double get_double(const char* name, const char* d_string, int* number_of_decimals) +{ + char* end; + double d = strtod(d_string, &end); + if (d == 0 && end == d_string) { + fprintf(stderr, "%s: invalid argument \"%s\"\n", name, d_string); + print_usage(stderr); + exit(1); + } + if (char* dot = strchr(d_string, '.')) + *number_of_decimals = strlen(dot + 1); + else + *number_of_decimals = 0; + return d; +} + +int main(int argc, const char* argv[]) +{ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + if (unveil(nullptr, nullptr) < 0) { + perror("unveil"); + return 1; + } + + for (int i = 1; i < argc; i++) { + if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { + print_usage(stdout); + exit(0); + } + } + + double start = 1, step = 1, end = 1; + int number_of_start_decimals = 0, number_of_step_decimals = 0, number_of_end_decimals = 0; + switch (argc) { + case 2: + end = get_double(argv[0], argv[1], &number_of_end_decimals); + break; + case 3: + start = get_double(argv[0], argv[1], &number_of_start_decimals); + end = get_double(argv[0], argv[2], &number_of_end_decimals); + break; + case 4: + start = get_double(argv[0], argv[1], &number_of_start_decimals); + step = get_double(argv[0], argv[2], &number_of_step_decimals); + end = get_double(argv[0], argv[3], &number_of_end_decimals); + break; + default: + fprintf(stderr, "%s: unexpected number of arguments\n", argv[0]); + print_usage(stderr); + return 1; + } + + if (step == 0) { + fprintf(stderr, "%s: increment must not be 0\n", argv[0]); + return 1; + } + + if (__builtin_isnan(start) || __builtin_isnan(step) || __builtin_isnan(end)) { + fprintf(stderr, "%s: start, step, and end must not be NaN\n", argv[0]); + return 1; + } + + int number_of_decimals = max(number_of_start_decimals, max(number_of_step_decimals, number_of_end_decimals)); + + int n = (end - start) / step; + double d = start; + for (int i = 0; i <= n; ++i) { + char buf[40]; + snprintf(buf, sizeof(buf), "%f", d); + if (char* dot = strchr(buf, '.')) { + if (number_of_decimals == 0) + *dot = '\0'; + else if ((dot - buf) + 1 + number_of_decimals < (int)sizeof(buf)) + dot[1 + number_of_decimals] = '\0'; + } + printf("%s\n", buf); + d += step; + } + + return 0; +} diff --git a/Userland/Utilities/shutdown.cpp b/Userland/Utilities/shutdown.cpp new file mode 100644 index 0000000000..9c080771e8 --- /dev/null +++ b/Userland/Utilities/shutdown.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + bool now = false; + + Core::ArgsParser args_parser; + args_parser.add_option(now, "Shut down now", "now", 'n'); + args_parser.parse(argc, argv); + + if (now) { + if (halt() < 0) { + perror("shutdown"); + return 1; + } + } else { + args_parser.print_usage(stderr, argv[0]); + return 1; + } +} diff --git a/Userland/Utilities/sleep.cpp b/Userland/Utilities/sleep.cpp new file mode 100644 index 0000000000..6cbc4465a6 --- /dev/null +++ b/Userland/Utilities/sleep.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +static volatile bool g_interrupted; +static void handle_sigint(int) +{ + g_interrupted = true; +} + +int main(int argc, char** argv) +{ + int secs; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(secs, "Number of seconds to sleep for", "num-seconds"); + args_parser.parse(argc, argv); + + struct sigaction sa; + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = handle_sigint; + sigaction(SIGINT, &sa, nullptr); + + if (pledge("stdio sigaction", nullptr) < 0) { + perror("pledge"); + return 1; + } + + unsigned remaining = sleep(secs); + if (remaining) { + printf("Sleep interrupted with %u seconds remaining.\n", remaining); + } + + signal(SIGINT, SIG_DFL); + if (g_interrupted) + raise(SIGINT); + + return 0; +} diff --git a/Userland/Utilities/sort.cpp b/Userland/Utilities/sort.cpp new file mode 100644 index 0000000000..351d196057 --- /dev/null +++ b/Userland/Utilities/sort.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) +{ + if (pledge("stdio", nullptr) > 0) { + perror("pledge"); + return 1; + } + + Vector lines; + + for (;;) { + char* buffer = nullptr; + ssize_t buflen = 0; + size_t n; + errno = 0; + buflen = getline(&buffer, &n, stdin); + if (buflen == -1 && errno != 0) { + perror("getline"); + exit(1); + } + if (buflen == -1) + break; + lines.append(buffer); + } + + quick_sort(lines, [](auto& a, auto& b) { + return strcmp(a.characters(), b.characters()) < 0; + }); + + for (auto& line : lines) { + fputs(line.characters(), stdout); + } + + return 0; +} diff --git a/Userland/Utilities/stat.cpp b/Userland/Utilities/stat.cpp new file mode 100644 index 0000000000..05501ced25 --- /dev/null +++ b/Userland/Utilities/stat.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2020, Shannon Booth + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int stat(const char* file, bool should_follow_links) +{ + struct stat st; + int rc = should_follow_links ? stat(file, &st) : lstat(file, &st); + if (rc < 0) { + perror("lstat"); + return 1; + } + printf(" File: %s\n", file); + printf(" Inode: %u\n", st.st_ino); + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) + printf(" Device: %u,%u\n", major(st.st_rdev), minor(st.st_rdev)); + else + printf(" Size: %zd\n", st.st_size); + printf(" Links: %u\n", st.st_nlink); + printf(" Blocks: %u\n", st.st_blocks); + printf(" UID: %u", st.st_uid); + if (auto* pwd = getpwuid(st.st_uid)) { + printf(" (%s)", pwd->pw_name); + } + printf("\n"); + printf(" GID: %u", st.st_gid); + if (auto* grp = getgrgid(st.st_gid)) { + printf(" (%s)", grp->gr_name); + } + printf("\n"); + printf(" Mode: (%o/", st.st_mode); + + if (S_ISDIR(st.st_mode)) + printf("d"); + else if (S_ISLNK(st.st_mode)) + printf("l"); + else if (S_ISBLK(st.st_mode)) + printf("b"); + else if (S_ISCHR(st.st_mode)) + printf("c"); + else if (S_ISFIFO(st.st_mode)) + printf("f"); + else if (S_ISSOCK(st.st_mode)) + printf("s"); + else if (S_ISREG(st.st_mode)) + printf("-"); + else + printf("?"); + + printf("%c%c%c%c%c%c%c%c", + st.st_mode & S_IRUSR ? 'r' : '-', + st.st_mode & S_IWUSR ? 'w' : '-', + st.st_mode & S_ISUID ? 's' : (st.st_mode & S_IXUSR ? 'x' : '-'), + st.st_mode & S_IRGRP ? 'r' : '-', + st.st_mode & S_IWGRP ? 'w' : '-', + st.st_mode & S_ISGID ? 's' : (st.st_mode & S_IXGRP ? 'x' : '-'), + st.st_mode & S_IROTH ? 'r' : '-', + st.st_mode & S_IWOTH ? 'w' : '-'); + + if (st.st_mode & S_ISVTX) + printf("t"); + else + printf("%c", st.st_mode & S_IXOTH ? 'x' : '-'); + + printf(")\n"); + + auto print_time = [](time_t t) { + printf("%s\n", Core::DateTime::from_timestamp(t).to_string().characters()); + }; + + printf("Accessed: "); + print_time(st.st_atime); + printf("Modified: "); + print_time(st.st_mtime); + printf(" Changed: "); + print_time(st.st_ctime); + + return 0; +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool should_follow_links = false; + Vector files; + + auto args_parser = Core::ArgsParser(); + args_parser.add_option(should_follow_links, "Follow links to files", nullptr, 'L'); + args_parser.add_positional_argument(files, "File(s) to stat", "file", Core::ArgsParser::Required::Yes); + args_parser.parse(argc, argv); + + int ret = 0; + for (auto& file : files) + ret |= stat(file, should_follow_links); + + return ret; +} diff --git a/Userland/Utilities/strace.cpp b/Userland/Utilities/strace.cpp new file mode 100644 index 0000000000..e915e7d3f2 --- /dev/null +++ b/Userland/Utilities/strace.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2018-2021, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int g_pid = -1; + +static void handle_sigint(int) +{ + if (g_pid == -1) + return; + + if (ptrace(PT_DETACH, g_pid, 0, 0) == -1) { + perror("detach"); + } +} + +int main(int argc, char** argv) +{ + if (pledge("stdio proc exec ptrace sigaction", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector child_argv; + + const char* output_filename = nullptr; + auto trace_file = Core::File::standard_output(); + + Core::ArgsParser parser; + parser.set_general_help( + "Trace all syscalls and their result."); + parser.add_option(g_pid, "Trace the given PID", "pid", 'p', "pid"); + parser.add_option(output_filename, "Filename to write output to", "output", 'o', "output"); + parser.add_positional_argument(child_argv, "Arguments to exec", "argument", Core::ArgsParser::Required::No); + + parser.parse(argc, argv); + + if (output_filename != nullptr) { + auto open_result = Core::File::open(output_filename, Core::IODevice::OpenMode::WriteOnly); + if (open_result.is_error()) { + outln(stderr, "Failed to open output file: {}", open_result.error()); + return 1; + } + trace_file = open_result.value(); + } + + int status; + if (g_pid == -1) { + if (child_argv.is_empty()) { + outln(stderr, "strace: Expected either a pid or some arguments\n"); + return 1; + } + + child_argv.append(nullptr); + int pid = fork(); + if (pid < 0) { + perror("fork"); + return 1; + } + + if (!pid) { + if (ptrace(PT_TRACE_ME, 0, 0, 0) == -1) { + perror("traceme"); + return 1; + } + int rc = execvp(child_argv.first(), const_cast(child_argv.data())); + if (rc < 0) { + perror("execvp"); + exit(1); + } + ASSERT_NOT_REACHED(); + } + + g_pid = pid; + if (waitpid(pid, &status, WSTOPPED | WEXITED) != pid || !WIFSTOPPED(status)) { + perror("waitpid"); + return 1; + } + } + + struct sigaction sa; + memset(&sa, 0, sizeof(struct sigaction)); + sa.sa_handler = handle_sigint; + sigaction(SIGINT, &sa, nullptr); + + if (ptrace(PT_ATTACH, g_pid, 0, 0) == -1) { + perror("attach"); + return 1; + } + if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { + perror("waitpid"); + return 1; + } + + for (;;) { + if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) { + perror("syscall"); + return 1; + } + if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { + perror("wait_pid"); + return 1; + } + PtraceRegisters regs = {}; + if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) { + perror("getregs"); + return 1; + } + u32 syscall_index = regs.eax; + u32 arg1 = regs.edx; + u32 arg2 = regs.ecx; + u32 arg3 = regs.ebx; + + if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) { + perror("syscall"); + return 1; + } + if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { + perror("wait_pid"); + return 1; + } + + if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) { + perror("getregs"); + return 1; + } + + u32 res = regs.eax; + + trace_file->printf("%s(0x%x, 0x%x, 0x%x)\t=%d\n", + Syscall::to_string( + (Syscall::Function)syscall_index), + arg1, + arg2, + arg3, + res); + } + + return 0; +} diff --git a/Userland/Utilities/su.cpp b/Userland/Utilities/su.cpp new file mode 100644 index 0000000000..3377615570 --- /dev/null +++ b/Userland/Utilities/su.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +extern "C" int main(int, char**); + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath tty exec id", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (!isatty(STDIN_FILENO)) { + warnln("{}: standard in is not a terminal", argv[0]); + return 1; + } + + const char* user = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(user, "User to switch to (defaults to user with UID 0)", "user", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (geteuid() != 0) { + warnln("Not running as root :("); + return 1; + } + + auto account_or_error = (user) + ? Core::Account::from_name(user, Core::Account::OpenPasswdFile::No, Core::Account::OpenShadowFile::ReadOnly) + : Core::Account::from_uid(0, Core::Account::OpenPasswdFile::No, Core::Account::OpenShadowFile::ReadOnly); + if (account_or_error.is_error()) { + fprintf(stderr, "Core::Account::from_name: %s\n", account_or_error.error().characters()); + return 1; + } + + if (pledge("stdio tty exec id", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const auto& account = account_or_error.value(); + + if (getuid() != 0 && account.has_password()) { + auto password = Core::get_password(); + if (password.is_error()) { + warnln("{}", password.error()); + return 1; + } + + if (!account.authenticate(password.value().characters())) { + warnln("Incorrect or disabled password."); + return 1; + } + } + + if (pledge("stdio exec id", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (!account.login()) { + perror("Core::Account::login"); + return 1; + } + + execl(account.shell().characters(), account.shell().characters(), nullptr); + perror("execl"); + return 1; +} diff --git a/Userland/Utilities/sync.cpp b/Userland/Utilities/sync.cpp new file mode 100644 index 0000000000..db0c91630b --- /dev/null +++ b/Userland/Utilities/sync.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +int main(int, char**) +{ + sync(); + return 0; +} diff --git a/Userland/Utilities/syscall.cpp b/Userland/Utilities/syscall.cpp new file mode 100644 index 0000000000..fcb7862e3c --- /dev/null +++ b/Userland/Utilities/syscall.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined __ENUMERATE_SYSCALL +# define __ENUMERATE_SYSCALL(x) SC_##x, +#endif + +#define SC_NARG 4 + +Syscall::Function syscall_table[] = { + ENUMERATE_SYSCALLS(__ENUMERATE_SYSCALL) +}; + +FlatPtr arg[SC_NARG]; +char buf[BUFSIZ]; + +FlatPtr parse(char* s); + +int main(int argc, char** argv) +{ + int oflag; + int opt; + while ((opt = getopt(argc, argv, "olh")) != -1) { + switch (opt) { + case 'o': + oflag = 1; + break; + case 'l': + for (auto sc : syscall_table) { + fprintf(stdout, "%s ", Syscall::to_string(sc)); + } + return EXIT_SUCCESS; + case 'h': + fprintf(stderr, "usage: \tsyscall [-o] [-l] [-h] [buf==BUFSIZ buffer]\n"); + fprintf(stderr, "\tsyscall write 1 hello 5\n"); + fprintf(stderr, "\tsyscall -o read 0 buf 5\n"); + fprintf(stderr, "\tsyscall sleep 3\n"); + break; + default: + exit(EXIT_FAILURE); + } + } + + if (optind >= argc) { + fprintf(stderr, "No entry specified\n"); + return -1; + } + + for (int i = 0; i < argc - optind; i++) { + arg[i] = parse(argv[i + optind]); + } + + for (auto sc : syscall_table) { + if (strcmp(Syscall::to_string(sc), (char*)arg[0]) == 0) { + int rc = syscall(sc, arg[1], arg[2], arg[3]); + if (rc == -1) { + perror("syscall"); + } else { + if (oflag) + fwrite(buf, 1, sizeof(buf), stdout); + } + + fprintf(stderr, "Syscall return: %d\n", rc); + return 0; + } + } + + fprintf(stderr, "Invalid syscall entry %s\n", (char*)arg[0]); + return -1; +} + +FlatPtr parse(char* s) +{ + char* t; + FlatPtr l; + + if (strcmp(s, "buf") == 0) { + return (FlatPtr)buf; + } + + l = strtoul(s, &t, 0); + if (t > s && *t == 0) { + return l; + } + + return (FlatPtr)s; +} diff --git a/Userland/Utilities/sysctl.cpp b/Userland/Utilities/sysctl.cpp new file mode 100644 index 0000000000..74336b6304 --- /dev/null +++ b/Userland/Utilities/sysctl.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static String read_var(const String& name) +{ + StringBuilder builder; + builder.append("/proc/sys/"); + builder.append(name); + auto path = builder.to_string(); + auto f = Core::File::construct(path); + if (!f->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "open: %s\n", f->error_string()); + exit(1); + } + const auto& b = f->read_all(); + if (f->error() < 0) { + fprintf(stderr, "read: %s\n", f->error_string()); + exit(1); + } + return String((const char*)b.data(), b.size(), Chomp); +} + +static void write_var(const String& name, const String& value) +{ + StringBuilder builder; + builder.append("/proc/sys/"); + builder.append(name); + auto path = builder.to_string(); + auto f = Core::File::construct(path); + if (!f->open(Core::IODevice::WriteOnly)) { + fprintf(stderr, "open: %s\n", f->error_string()); + exit(1); + } + f->write(value); + if (f->error() < 0) { + fprintf(stderr, "write: %s\n", f->error_string()); + exit(1); + } +} + +static int handle_show_all() +{ + Core::DirIterator di("/proc/sys", Core::DirIterator::SkipDots); + if (di.has_error()) { + fprintf(stderr, "DirIterator: %s\n", di.error_string()); + return 1; + } + + while (di.has_next()) { + String variable_name = di.next_path(); + printf("%s = %s\n", variable_name.characters(), read_var(variable_name).characters()); + } + return 0; +} + +static int handle_var(const String& var) +{ + String spec(var.characters(), Chomp); + auto parts = spec.split('='); + String variable_name = parts[0]; + bool is_write = parts.size() > 1; + + if (!is_write) { + printf("%s = %s\n", variable_name.characters(), read_var(variable_name).characters()); + return 0; + } + + printf("%s = %s", variable_name.characters(), read_var(variable_name).characters()); + write_var(variable_name, parts[1]); + printf(" -> %s\n", read_var(variable_name).characters()); + return 0; +} + +int main(int argc, char** argv) +{ + bool show_all = false; + const char* var = nullptr; + + Core::ArgsParser args_parser; + args_parser.set_general_help( + "Show or modify system-internal values. This requires root, and can crash your system."); + args_parser.add_option(show_all, "Show all variables", nullptr, 'a'); + args_parser.add_positional_argument(var, "Command (var[=value])", "command", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (var == nullptr) { + // Not supplied; assume `-a`. + show_all = true; + } + + if (show_all) { + // Ignore `var`, even if it was supplied. Just like the real procps does. + return handle_show_all(); + } + + return handle_var(var); +} diff --git a/Userland/Utilities/tail.cpp b/Userland/Utilities/tail.cpp new file mode 100644 index 0000000000..60fb351f19 --- /dev/null +++ b/Userland/Utilities/tail.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_LINE_COUNT 10 + +static int tail_from_pos(Core::File& file, off_t startline, bool want_follow) +{ + if (!file.seek(startline + 1)) + return 1; + + while (true) { + const auto& b = file.read(4096); + if (b.is_empty()) { + if (!want_follow) { + break; + } else { + while (!file.can_read()) { + // FIXME: would be nice to have access to can_read_from_fd with an infinite timeout + usleep(100); + } + continue; + } + } + + if (write(STDOUT_FILENO, b.data(), b.size()) < 0) + return 1; + } + + return 0; +} + +static off_t find_seek_pos(Core::File& file, int wanted_lines) +{ + // Rather than reading the whole file, start at the end and work backwards, + // stopping when we've found the number of lines we want. + off_t pos = 0; + if (!file.seek(0, Core::IODevice::SeekMode::FromEndPosition, &pos)) { + fprintf(stderr, "Failed to find end of file: %s\n", file.error_string()); + return 1; + } + + off_t end = pos; + int lines = 0; + + // FIXME: Reading char-by-char is only OK if IODevice's read buffer + // is smart enough to not read char-by-char. Fix it there, or fix it here :) + for (; pos >= 0; pos--) { + file.seek(pos); + const auto& ch = file.read(1); + if (ch.is_empty()) { + // Presumably the file got truncated? + // Keep trying to read backwards... + } else { + if (*ch.data() == '\n' && (end - pos) > 1) { + lines++; + if (lines == wanted_lines) + break; + } + } + } + + return pos; +} + +int main(int argc, char* argv[]) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool follow = false; + int line_count = DEFAULT_LINE_COUNT; + const char* file = nullptr; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Print the end ('tail') of a file."); + args_parser.add_option(follow, "Output data as it is written to the file", "follow", 'f'); + args_parser.add_option(line_count, "Fetch the specified number of lines", "lines", 'n', "number"); + args_parser.add_positional_argument(file, "File path", "file"); + args_parser.parse(argc, argv); + + auto f = Core::File::construct(file); + if (!f->open(Core::IODevice::ReadOnly)) { + fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno)); + exit(1); + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + auto pos = find_seek_pos(*f, line_count); + return tail_from_pos(*f, pos, follow); +} diff --git a/Userland/Utilities/tar.cpp b/Userland/Utilities/tar.cpp new file mode 100644 index 0000000000..b828e3d8d1 --- /dev/null +++ b/Userland/Utilities/tar.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020, Peter Elliott + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr size_t buffer_size = 4096; + +int main(int argc, char** argv) +{ + bool create = false; + bool extract = false; + bool list = false; + bool verbose = false; + bool gzip = false; + const char* archive_file = nullptr; + Vector paths; + + Core::ArgsParser args_parser; + args_parser.add_option(create, "Create archive", "create", 'c'); + args_parser.add_option(extract, "Extract archive", "extract", 'x'); + args_parser.add_option(list, "List contents", "list", 't'); + args_parser.add_option(verbose, "Print paths", "verbose", 'v'); + args_parser.add_option(gzip, "compress or uncompress file using gzip", "gzip", 'z'); + args_parser.add_option(archive_file, "Archive file", "file", 'f', "FILE"); + args_parser.add_positional_argument(paths, "Paths", "PATHS", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (create + extract + list != 1) { + warnln("exactly one of -c, -x, and -t can be used"); + return 1; + } + + if (list || extract) { + auto file = Core::File::standard_input(); + + if (archive_file) { + auto maybe_file = Core::File::open(archive_file, Core::IODevice::OpenMode::ReadOnly); + if (maybe_file.is_error()) { + warnln("Core::File::open: {}", maybe_file.error()); + return 1; + } + file = maybe_file.value(); + } + + Core::InputFileStream file_stream(file); + Compress::GzipDecompressor gzip_stream(file_stream); + + InputStream& file_input_stream = file_stream; + InputStream& gzip_input_stream = gzip_stream; + Tar::TarStream tar_stream((gzip) ? gzip_input_stream : file_input_stream); + if (!tar_stream.valid()) { + warnln("the provided file is not a well-formatted ustar file"); + return 1; + } + for (; !tar_stream.finished(); tar_stream.advance()) { + if (list || verbose) + outln("{}", tar_stream.header().file_name()); + + if (extract) { + Tar::TarFileStream file_stream = tar_stream.file_contents(); + + const Tar::Header& header = tar_stream.header(); + switch (header.type_flag()) { + case Tar::NormalFile: + case Tar::AlternateNormalFile: { + int fd = open(String(header.file_name()).characters(), O_CREAT | O_WRONLY, header.mode()); + if (fd < 0) { + perror("open"); + return 1; + } + + Array buffer; + size_t nread; + while ((nread = file_stream.read(buffer)) > 0) { + if (write(fd, buffer.data(), nread) < 0) { + perror("write"); + return 1; + } + } + close(fd); + break; + } + case Tar::Directory: { + if (mkdir(String(header.file_name()).characters(), header.mode())) { + perror("mkdir"); + return 1; + } + break; + } + default: + // FIXME: Implement other file types + ASSERT_NOT_REACHED(); + } + } + } + file_stream.close(); + return 0; + } + + // FIXME: Implement other operations. + ASSERT_NOT_REACHED(); + + return 0; +} diff --git a/Userland/Utilities/tee.cpp b/Userland/Utilities/tee.cpp new file mode 100644 index 0000000000..92c58c7031 --- /dev/null +++ b/Userland/Utilities/tee.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +static Vector collect_fds(Vector paths, bool append, bool* err) +{ + int oflag; + mode_t mode; + if (append) { + oflag = O_APPEND; + mode = 0; + } else { + oflag = O_CREAT | O_WRONLY | O_TRUNC; + mode = S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR; + } + + Vector fds; + for (const char* path : paths) { + int fd = open(path, oflag, mode); + if (fd < 0) { + perror("failed to open file for writing"); + *err = true; + } else { + fds.append(fd); + } + } + fds.append(STDOUT_FILENO); + return fds; +} + +static void copy_stdin(Vector& fds, bool* err) +{ + for (;;) { + char buf[4096]; + ssize_t nread = read(STDIN_FILENO, buf, sizeof(buf)); + if (nread == 0) + break; + if (nread < 0) { + perror("read() error"); + *err = true; + // a failure to read from stdin should lead to an early exit + return; + } + + Vector broken_fds; + for (size_t i = 0; i < fds.size(); ++i) { + auto fd = fds.at(i); + int twrite = 0; + while (twrite != nread) { + ssize_t nwrite = write(fd, buf + twrite, nread - twrite); + if (nwrite < 0) { + if (errno == EINTR) { + continue; + } else { + perror("write() failed"); + *err = true; + broken_fds.append(fd); + // write failures to a successfully opened fd shall + // prevent further writes, but shall not block writes + // to the other open fds + break; + } + } else { + twrite += nwrite; + } + } + } + + // remove any fds which we can no longer write to for subsequent copies + for (auto to_remove : broken_fds) + fds.remove_first_matching([&](int fd) { return to_remove == fd; }); + } +} + +static void close_fds(Vector& fds) +{ + for (int fd : fds) { + int closed = close(fd); + if (closed < 0) { + perror("failed to close output file"); + } + } +} + +static void int_handler(int) +{ + // pass +} + +int main(int argc, char** argv) +{ + bool append = false; + bool ignore_interrupts = false; + Vector paths; + + Core::ArgsParser args_parser; + args_parser.add_option(append, "Append, don't overwrite", "append", 'a'); + args_parser.add_option(ignore_interrupts, "Ignore SIGINT", "ignore-interrupts", 'i'); + args_parser.add_positional_argument(paths, "Files to copy stdin to", "file", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (ignore_interrupts) { + if (signal(SIGINT, int_handler) == SIG_ERR) + perror("failed to install SIGINT handler"); + } + + bool err_open = false; + bool err_write = false; + auto fds = collect_fds(paths, append, &err_open); + copy_stdin(fds, &err_write); + close_fds(fds); + + return (err_open || err_write) ? 1 : 0; +} diff --git a/Userland/Utilities/test-bindtodevice.cpp b/Userland/Utilities/test-bindtodevice.cpp new file mode 100644 index 0000000000..8213e5c0ce --- /dev/null +++ b/Userland/Utilities/test-bindtodevice.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2020, the SerenityOS developers + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static void test_invalid(int); +static void test_no_route(int); +static void test_valid(int); +static void test_send(int); + +static void test(AK::Function test_fn) +{ + + int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (fd < 0) { + perror("socket"); + return; + } + + test_fn(fd); + + // be a responsible boi + close(fd); +} + +auto main() -> int +{ + test(test_invalid); + test(test_valid); + test(test_no_route); + test(test_send); +} + +void test_invalid(int fd) +{ + // bind to an interface that does not exist + char buf[IFNAMSIZ]; + socklen_t buflen = IFNAMSIZ; + memcpy(buf, "foodev", 7); + + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) { + perror("setsockopt(SO_BINDTODEVICE) :: invalid (Should fail with ENODEV)"); + puts("PASS invalid"); + } else { + puts("FAIL invalid"); + } +} + +void test_valid(int fd) +{ + // bind to an interface that exists + char buf[IFNAMSIZ]; + socklen_t buflen = IFNAMSIZ; + memcpy(buf, "loop0", 6); + + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) { + perror("setsockopt(SO_BINDTODEVICE) :: valid"); + puts("FAIL valid"); + } else { + puts("PASS valid"); + } +} + +void test_no_route(int fd) +{ + // bind to an interface that cannot deliver + char buf[IFNAMSIZ]; + socklen_t buflen = IFNAMSIZ; + memcpy(buf, "loop0", 6); + + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) { + perror("setsockopt(SO_BINDTODEVICE) :: no_route"); + puts("FAIL no_route"); + return; + } + sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + + sin.sin_addr.s_addr = IPv4Address { 10, 0, 2, 15 }.to_u32(); + sin.sin_port = 8080; + sin.sin_family = AF_INET; + + if (bind(fd, (sockaddr*)&sin, sizeof(sin)) < 0) { + perror("bind() :: no_route"); + puts("FAIL no_route"); + return; + } + + if (sendto(fd, "TEST", 4, 0, (sockaddr*)&sin, sizeof(sin)) < 0) { + perror("sendto() :: no_route (Should fail with EHOSTUNREACH)"); + puts("PASS no_route"); + } else + puts("FAIL no_route"); +} + +void test_send(int fd) +{ + // bind to an interface that cannot deliver + char buf[IFNAMSIZ]; + socklen_t buflen = IFNAMSIZ; + memcpy(buf, "e1k0", 5); + + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) { + perror("setsockopt(SO_BINDTODEVICE) :: send"); + puts("FAIL send"); + return; + } + sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + + sin.sin_addr.s_addr = IPv4Address { 10, 0, 2, 15 }.to_u32(); + sin.sin_port = 8080; + sin.sin_family = AF_INET; + + if (bind(fd, (sockaddr*)&sin, sizeof(sin)) < 0) { + perror("bind() :: send"); + puts("FAIL send"); + return; + } + + if (sendto(fd, "TEST", 4, 0, (sockaddr*)&sin, sizeof(sin)) < 0) { + perror("sendto() :: send"); + puts("FAIL send"); + return; + } + puts("PASS send"); +} diff --git a/Userland/Utilities/test-compress.cpp b/Userland/Utilities/test-compress.cpp new file mode 100644 index 0000000000..7e1dbb0d63 --- /dev/null +++ b/Userland/Utilities/test-compress.cpp @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include + +TEST_CASE(canonical_code_simple) +{ + const Array code { + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05 + }; + const Array input { + 0x00, 0x42, 0x84, 0xa9, 0xb0, 0x15 + }; + const Array output { + 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, 0x15 + }; + + const auto huffman = Compress::CanonicalCode::from_bytes(code).value(); + auto memory_stream = InputMemoryStream { input }; + auto bit_stream = InputBitStream { memory_stream }; + + for (size_t idx = 0; idx < 9; ++idx) + EXPECT_EQ(huffman.read_symbol(bit_stream), output[idx]); +} + +TEST_CASE(canonical_code_complex) +{ + const Array code { + 0x03, 0x02, 0x03, 0x03, 0x02, 0x03 + }; + const Array input { + 0xa1, 0xf3, 0xa1, 0xf3 + }; + const Array output { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 + }; + + const auto huffman = Compress::CanonicalCode::from_bytes(code).value(); + auto memory_stream = InputMemoryStream { input }; + auto bit_stream = InputBitStream { memory_stream }; + + for (size_t idx = 0; idx < 12; ++idx) + EXPECT_EQ(huffman.read_symbol(bit_stream), output[idx]); +} + +TEST_CASE(deflate_decompress_compressed_block) +{ + const Array compressed { + 0x0B, 0xC9, 0xC8, 0x2C, 0x56, 0x00, 0xA2, 0x44, 0x85, 0xE2, 0xCC, 0xDC, + 0x82, 0x9C, 0x54, 0x85, 0x92, 0xD4, 0x8A, 0x12, 0x85, 0xB4, 0x4C, 0x20, + 0xCB, 0x4A, 0x13, 0x00 + }; + + const u8 uncompressed[] = "This is a simple text file :)"; + + const auto decompressed = Compress::DeflateDecompressor::decompress_all(compressed); + EXPECT(decompressed.value().bytes() == ReadonlyBytes({ uncompressed, sizeof(uncompressed) - 1 })); +} + +TEST_CASE(deflate_decompress_uncompressed_block) +{ + const Array compressed { + 0x01, 0x0d, 0x00, 0xf2, 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, + 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21 + }; + + const u8 uncompressed[] = "Hello, World!"; + + const auto decompressed = Compress::DeflateDecompressor::decompress_all(compressed); + EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); +} + +TEST_CASE(deflate_decompress_multiple_blocks) +{ + const Array compressed { + 0x00, 0x1f, 0x00, 0xe0, 0xff, 0x54, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, + 0x73, 0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x69, 0x73, 0x20, + 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, + 0x53, 0x48, 0xcc, 0x4b, 0x51, 0x28, 0xc9, 0x48, 0x55, 0x28, 0x4e, 0x4d, + 0xce, 0x07, 0x32, 0x93, 0x72, 0xf2, 0x93, 0xb3, 0x15, 0x32, 0x8b, 0x15, + 0x92, 0xf3, 0x73, 0x0b, 0x8a, 0x52, 0x8b, 0x8b, 0x53, 0x53, 0xf4, 0x00 + }; + + const u8 uncompressed[] = "The first block is uncompressed and the second block is compressed."; + + const auto decompressed = Compress::DeflateDecompressor::decompress_all(compressed); + EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); +} + +TEST_CASE(deflate_decompress_zeroes) +{ + const Array compressed { + 0xed, 0xc1, 0x01, 0x0d, 0x00, 0x00, 0x00, 0xc2, 0xa0, 0xf7, 0x4f, 0x6d, + 0x0f, 0x07, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x6e + }; + + const Array uncompressed { 0 }; + + const auto decompressed = Compress::DeflateDecompressor::decompress_all(compressed); + EXPECT(uncompressed == decompressed.value().bytes()); +} + +TEST_CASE(zlib_decompress_simple) +{ + const Array compressed { + 0x78, 0x01, 0x01, 0x1D, 0x00, 0xE2, 0xFF, 0x54, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x20, + 0x74, 0x65, 0x78, 0x74, 0x20, 0x66, 0x69, 0x6C, 0x65, 0x20, 0x3A, 0x29, + 0x99, 0x5E, 0x09, 0xE8 + }; + + const u8 uncompressed[] = "This is a simple text file :)"; + + const auto decompressed = Compress::Zlib::decompress_all(compressed); + EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); +} + +TEST_CASE(gzip_decompress_simple) +{ + const Array compressed { + 0x1f, 0x8b, 0x08, 0x00, 0x77, 0xff, 0x47, 0x5f, 0x02, 0xff, 0x2b, 0xcf, + 0x2f, 0x4a, 0x31, 0x54, 0x48, 0x4c, 0x4a, 0x56, 0x28, 0x07, 0xb2, 0x8c, + 0x00, 0xc2, 0x1d, 0x22, 0x15, 0x0f, 0x00, 0x00, 0x00 + }; + + const u8 uncompressed[] = "word1 abc word2"; + + const auto decompressed = Compress::GzipDecompressor::decompress_all(compressed); + EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); +} + +TEST_CASE(gzip_decompress_multiple_members) +{ + const Array compressed { + 0x1f, 0x8b, 0x08, 0x00, 0xe0, 0x03, 0x48, 0x5f, 0x02, 0xff, 0x4b, 0x4c, + 0x4a, 0x4e, 0x4c, 0x4a, 0x06, 0x00, 0x4c, 0x99, 0x6e, 0x72, 0x06, 0x00, + 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0xe0, 0x03, 0x48, 0x5f, 0x02, 0xff, + 0x4b, 0x4c, 0x4a, 0x4e, 0x4c, 0x4a, 0x06, 0x00, 0x4c, 0x99, 0x6e, 0x72, + 0x06, 0x00, 0x00, 0x00 + }; + + const u8 uncompressed[] = "abcabcabcabc"; + + const auto decompressed = Compress::GzipDecompressor::decompress_all(compressed); + EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); +} + +TEST_CASE(gzip_decompress_zeroes) +{ + const Array compressed { + 0x1f, 0x8b, 0x08, 0x00, 0x6e, 0x7a, 0x4b, 0x5f, 0x02, 0xff, 0xed, 0xc1, + 0x31, 0x01, 0x00, 0x00, 0x00, 0xc2, 0xa0, 0xf5, 0x4f, 0xed, 0x61, 0x0d, + 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xcd, 0xcd, 0xe8, + 0x7e, 0x00, 0x00, 0x02, 0x00 + }; + + const Array uncompressed = { 0 }; + + const auto decompressed = Compress::GzipDecompressor::decompress_all(compressed); + EXPECT(uncompressed == decompressed.value().bytes()); +} + +TEST_CASE(gzip_decompress_repeat_around_buffer) +{ + const Array compressed { + 0x1f, 0x8b, 0x08, 0x00, 0xc6, 0x74, 0x53, 0x5f, 0x02, 0xff, 0xed, 0xc1, + 0x01, 0x0d, 0x00, 0x00, 0x0c, 0x02, 0xa0, 0xdb, 0xbf, 0xf4, 0x37, 0x6b, + 0x08, 0x24, 0xdb, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xca, + 0xb8, 0x07, 0xcd, 0xe5, 0x38, 0xfa, 0x00, 0x80, 0x00, 0x00 + }; + + Array uncompressed; + uncompressed.span().slice(0x0000, 0x0100).fill(1); + uncompressed.span().slice(0x0100, 0x7e00).fill(0); + uncompressed.span().slice(0x7f00, 0x0100).fill(1); + + const auto decompressed = Compress::GzipDecompressor::decompress_all(compressed); + EXPECT(uncompressed == decompressed.value().bytes()); +} + +TEST_MAIN(Compress) diff --git a/Userland/Utilities/test-crypto.cpp b/Userland/Utilities/test-crypto.cpp new file mode 100644 index 0000000000..55d9c69611 --- /dev/null +++ b/Userland/Utilities/test-crypto.cpp @@ -0,0 +1,2736 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const char* secret_key = "WellHelloFreinds"; +static const char* suite = nullptr; +static const char* filename = nullptr; +static const char* server = nullptr; +static const char* ca_certs_file = "/etc/ca_certs.ini"; +static int key_bits = 128; +static bool binary = false; +static bool interactive = false; +static bool run_tests = false; +static int port = 443; +static bool in_ci = false; + +static struct timeval start_time { + 0, 0 +}; +static bool g_some_test_failed = false; +static bool encrypting = true; + +constexpr const char* DEFAULT_DIGEST_SUITE { "HMAC-SHA256" }; +constexpr const char* DEFAULT_CHECKSUM_SUITE { "CRC32" }; +constexpr const char* DEFAULT_HASH_SUITE { "SHA256" }; +constexpr const char* DEFAULT_CIPHER_SUITE { "AES_CBC" }; +constexpr const char* DEFAULT_SERVER { "www.google.com" }; + +static Vector s_root_ca_certificates; + +// listAllTests +// Cipher +static int aes_cbc_tests(); +static int aes_ctr_tests(); +static int aes_gcm_tests(); + +// Hash +static int md5_tests(); +static int sha1_tests(); +static int sha256_tests(); +static int sha512_tests(); + +// Authentication +static int hmac_md5_tests(); +static int hmac_sha256_tests(); +static int hmac_sha512_tests(); +static int hmac_sha1_tests(); +static int ghash_tests(); + +// Public-Key +static int rsa_tests(); + +// TLS +static int tls_tests(); + +// Big Integer +static int bigint_tests(); + +// Checksum +static int adler32_tests(); +static int crc32_tests(); + +// stop listing tests + +static void print_buffer(ReadonlyBytes buffer, int split) +{ + for (size_t i = 0; i < buffer.size(); ++i) { + if (split > 0) { + if (i % split == 0 && i) { + printf(" "); + for (size_t j = i - split; j < i; ++j) { + auto ch = buffer[j]; + printf("%c", ch >= 32 && ch <= 127 ? ch : '.'); // silly hack + } + puts(""); + } + } + printf("%02x ", buffer[i]); + } + puts(""); +} + +static Core::EventLoop g_loop; + +static int run(Function fn) +{ + if (interactive) { + auto editor = Line::Editor::construct(); + editor->initialize(); + for (;;) { + auto line_result = editor->get_line("> "); + + if (line_result.is_error()) + break; + auto& line = line_result.value(); + + if (line == ".wait") { + g_loop.exec(); + } else { + fn(line.characters(), line.length()); + g_loop.pump(); + } + } + } else { + if (filename == nullptr) { + puts("must specify a file name"); + return 1; + } + if (!Core::File::exists(filename)) { + puts("File does not exist"); + return 1; + } + auto file = Core::File::open(filename, Core::IODevice::OpenMode::ReadOnly); + if (file.is_error()) { + printf("That's a weird file man...\n"); + return 1; + } + auto buffer = file.value()->read_all(); + fn((const char*)buffer.data(), buffer.size()); + g_loop.exec(); + } + return 0; +} + +static void tls(const char* message, size_t len) +{ + static RefPtr tls; + static ByteBuffer write {}; + if (!tls) { + tls = TLS::TLSv12::construct(nullptr); + tls->set_root_certificates(s_root_ca_certificates); + tls->connect(server ?: DEFAULT_SERVER, port); + tls->on_tls_ready_to_read = [](auto& tls) { + auto buffer = tls.read(); + if (buffer.has_value()) + fprintf(stdout, "%.*s", (int)buffer.value().size(), buffer.value().data()); + }; + tls->on_tls_ready_to_write = [&](auto&) { + if (write.size()) { + tls->write(write); + write.clear(); + } + }; + tls->on_tls_error = [&](auto) { + g_loop.quit(1); + }; + tls->on_tls_finished = [&]() { + g_loop.quit(0); + }; + } + write.append(message, len); + write.append("\r\n", 2); +} + +static void aes_cbc(const char* message, size_t len) +{ + ReadonlyBytes buffer { message, len }; + // FIXME: Take iv as an optional parameter + auto iv = ByteBuffer::create_zeroed(Crypto::Cipher::AESCipher::block_size()); + + if (encrypting) { + Crypto::Cipher::AESCipher::CBCMode cipher( + StringView(secret_key).bytes(), + key_bits, + Crypto::Cipher::Intent::Encryption); + + auto enc = cipher.create_aligned_buffer(buffer.size()); + auto enc_span = enc.bytes(); + cipher.encrypt(buffer, enc_span, iv); + + if (binary) + printf("%.*s", (int)enc_span.size(), enc_span.data()); + else + print_buffer(enc_span, Crypto::Cipher::AESCipher::block_size()); + } else { + Crypto::Cipher::AESCipher::CBCMode cipher( + StringView(secret_key).bytes(), + key_bits, + Crypto::Cipher::Intent::Decryption); + auto dec = cipher.create_aligned_buffer(buffer.size()); + auto dec_span = dec.bytes(); + cipher.decrypt(buffer, dec_span, iv); + printf("%.*s\n", (int)dec_span.size(), dec_span.data()); + } +} + +static void adler32(const char* message, size_t len) +{ + auto checksum = Crypto::Checksum::Adler32({ (const u8*)message, len }); + printf("%#10X\n", checksum.digest()); +} + +static void crc32(const char* message, size_t len) +{ + auto checksum = Crypto::Checksum::CRC32({ (const u8*)message, len }); + printf("%#10X\n", checksum.digest()); +} + +static void md5(const char* message, size_t len) +{ + auto digest = Crypto::Hash::MD5::hash((const u8*)message, len); + if (binary) + printf("%.*s", (int)Crypto::Hash::MD5::digest_size(), digest.data); + else + print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); +} + +static void hmac_md5(const char* message, size_t len) +{ + Crypto::Authentication::HMAC hmac(secret_key); + auto mac = hmac.process((const u8*)message, len); + if (binary) + printf("%.*s", (int)hmac.digest_size(), mac.data); + else + print_buffer({ mac.data, hmac.digest_size() }, -1); +} + +static void sha1(const char* message, size_t len) +{ + auto digest = Crypto::Hash::SHA1::hash((const u8*)message, len); + if (binary) + printf("%.*s", (int)Crypto::Hash::SHA1::digest_size(), digest.data); + else + print_buffer({ digest.data, Crypto::Hash::SHA1::digest_size() }, -1); +} + +static void sha256(const char* message, size_t len) +{ + auto digest = Crypto::Hash::SHA256::hash((const u8*)message, len); + if (binary) + printf("%.*s", (int)Crypto::Hash::SHA256::digest_size(), digest.data); + else + print_buffer({ digest.data, Crypto::Hash::SHA256::digest_size() }, -1); +} + +static void hmac_sha256(const char* message, size_t len) +{ + Crypto::Authentication::HMAC hmac(secret_key); + auto mac = hmac.process((const u8*)message, len); + if (binary) + printf("%.*s", (int)hmac.digest_size(), mac.data); + else + print_buffer({ mac.data, hmac.digest_size() }, -1); +} + +static void sha512(const char* message, size_t len) +{ + auto digest = Crypto::Hash::SHA512::hash((const u8*)message, len); + if (binary) + printf("%.*s", (int)Crypto::Hash::SHA512::digest_size(), digest.data); + else + print_buffer({ digest.data, Crypto::Hash::SHA512::digest_size() }, -1); +} + +static void hmac_sha512(const char* message, size_t len) +{ + Crypto::Authentication::HMAC hmac(secret_key); + auto mac = hmac.process((const u8*)message, len); + if (binary) + printf("%.*s", (int)hmac.digest_size(), mac.data); + else + print_buffer({ mac.data, hmac.digest_size() }, -1); +} + +auto main(int argc, char** argv) -> int +{ + const char* mode = nullptr; + Core::ArgsParser parser; + parser.add_positional_argument(mode, "mode to operate in ('list' to see modes and descriptions)", "mode"); + + parser.add_option(secret_key, "Set the secret key (default key is 'WellHelloFriends')", "secret-key", 'k', "secret key"); + parser.add_option(key_bits, "Size of the key", "key-bits", 'b', "key-bits"); + parser.add_option(filename, "Read from file", "file", 'f', "from file"); + parser.add_option(binary, "Force binary output", "force-binary", 0); + parser.add_option(interactive, "REPL mode", "interactive", 'i'); + parser.add_option(run_tests, "Run tests for the specified suite", "tests", 't'); + parser.add_option(suite, "Set the suite used", "suite-name", 'n', "suite name"); + parser.add_option(server, "Set the server to talk to (only for `tls')", "server-address", 's', "server-address"); + parser.add_option(port, "Set the port to talk to (only for `tls')", "port", 'p', "port"); + parser.add_option(ca_certs_file, "INI file to read root CA certificates from (only for `tls')", "ca-certs-file", 0, "file"); + parser.add_option(in_ci, "CI Test mode", "ci-mode", 'c'); + parser.parse(argc, argv); + + StringView mode_sv { mode }; + if (mode_sv == "list") { + puts("test-crypto modes"); + puts("\tdigest - Access digest (authentication) functions"); + puts("\thash - Access hash functions"); + puts("\tchecksum - Access checksum functions"); + puts("\tencrypt -- Access encryption functions"); + puts("\tdecrypt -- Access decryption functions"); + puts("\ttls -- Connect to a peer over TLS 1.2"); + puts("\tlist -- List all known modes"); + puts("these modes only contain tests"); + puts("\ttest -- Run every test suite"); + puts("\tbigint -- Run big integer test suite"); + puts("\tpk -- Run Public-key system tests"); + return 0; + } + + if (mode_sv == "hash") { + if (suite == nullptr) + suite = DEFAULT_HASH_SUITE; + StringView suite_sv { suite }; + + if (suite_sv == "MD5") { + if (run_tests) + return md5_tests(); + return run(md5); + } + if (suite_sv == "SHA1") { + if (run_tests) + return sha1_tests(); + return run(sha1); + } + if (suite_sv == "SHA256") { + if (run_tests) + return sha256_tests(); + return run(sha256); + } + if (suite_sv == "SHA512") { + if (run_tests) + return sha512_tests(); + return run(sha512); + } + printf("unknown hash function '%s'\n", suite); + return 1; + } + if (mode_sv == "checksum") { + if (suite == nullptr) + suite = DEFAULT_CHECKSUM_SUITE; + StringView suite_sv { suite }; + + if (suite_sv == "CRC32") { + if (run_tests) + return crc32_tests(); + return run(crc32); + } + if (suite_sv == "Adler32") { + if (run_tests) + return adler32_tests(); + return run(adler32); + } + printf("unknown checksum function '%s'\n", suite); + return 1; + } + if (mode_sv == "digest") { + if (suite == nullptr) + suite = DEFAULT_DIGEST_SUITE; + StringView suite_sv { suite }; + + if (suite_sv == "HMAC-MD5") { + if (run_tests) + return hmac_md5_tests(); + return run(hmac_md5); + } + if (suite_sv == "HMAC-SHA256") { + if (run_tests) + return hmac_sha256_tests(); + return run(hmac_sha256); + } + if (suite_sv == "HMAC-SHA512") { + if (run_tests) + return hmac_sha512_tests(); + return run(hmac_sha512); + } + if (suite_sv == "HMAC-SHA1") { + if (run_tests) + return hmac_sha1_tests(); + } + if (suite_sv == "GHash") { + if (run_tests) + return ghash_tests(); + } + printf("unknown hash function '%s'\n", suite); + return 1; + } + if (mode_sv == "pk") { + return rsa_tests(); + } + if (mode_sv == "bigint") { + return bigint_tests(); + } + if (mode_sv == "tls") { + if (!Core::File::exists(ca_certs_file)) { + warnln("Nonexistent CA certs file '{}'", ca_certs_file); + return 1; + } + auto config = Core::ConfigFile::open(ca_certs_file); + for (auto& entity : config->groups()) { + Certificate cert; + cert.subject = entity; + cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity); + cert.country = config->read_entry(entity, "country"); + s_root_ca_certificates.append(move(cert)); + } + if (run_tests) + return tls_tests(); + return run(tls); + } + if (mode_sv == "test") { + encrypting = true; + aes_cbc_tests(); + aes_ctr_tests(); + aes_gcm_tests(); + + encrypting = false; + aes_cbc_tests(); + aes_ctr_tests(); + aes_gcm_tests(); + + md5_tests(); + sha1_tests(); + sha256_tests(); + sha512_tests(); + + hmac_md5_tests(); + hmac_sha256_tests(); + hmac_sha512_tests(); + hmac_sha1_tests(); + + ghash_tests(); + + rsa_tests(); + + if (!in_ci) { + // Do not run these in CI to avoid tests with variables outside our control. + if (!Core::File::exists(ca_certs_file)) { + warnln("Nonexistent CA certs file '{}'", ca_certs_file); + return 1; + } + auto config = Core::ConfigFile::open(ca_certs_file); + for (auto& entity : config->groups()) { + Certificate cert; + cert.subject = entity; + cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity); + cert.country = config->read_entry(entity, "country"); + s_root_ca_certificates.append(move(cert)); + } + tls_tests(); + } + + bigint_tests(); + + return g_some_test_failed ? 1 : 0; + } + encrypting = mode_sv == "encrypt"; + if (encrypting || mode_sv == "decrypt") { + if (suite == nullptr) + suite = DEFAULT_CIPHER_SUITE; + StringView suite_sv { suite }; + + if (StringView(suite) == "AES_CBC") { + if (run_tests) + return aes_cbc_tests(); + + if (!Crypto::Cipher::AESCipher::KeyType::is_valid_key_size(key_bits)) { + printf("Invalid key size for AES: %d\n", key_bits); + return 1; + } + if (strlen(secret_key) != (size_t)key_bits / 8) { + printf("Key must be exactly %d bytes long\n", key_bits / 8); + return 1; + } + return run(aes_cbc); + } + if (StringView(suite) == "AES_GCM") { + if (run_tests) + return aes_gcm_tests(); + + return 1; + } else { + printf("Unknown cipher suite '%s'\n", suite); + return 1; + } + } + printf("Unknown mode '%s', check out the list of modes\n", mode); + return 1; +} + +#define I_TEST(thing) \ + { \ + printf("Testing " #thing "... "); \ + fflush(stdout); \ + gettimeofday(&start_time, nullptr); \ + } +#define PASS \ + { \ + struct timeval end_time { \ + 0, 0 \ + }; \ + gettimeofday(&end_time, nullptr); \ + time_t interval_s = end_time.tv_sec - start_time.tv_sec; \ + suseconds_t interval_us = end_time.tv_usec; \ + if (interval_us < start_time.tv_usec) { \ + interval_s -= 1; \ + interval_us += 1000000; \ + } \ + interval_us -= start_time.tv_usec; \ + printf("PASS %llds %lldus\n", (long long)interval_s, (long long)interval_us); \ + } +#define FAIL(reason) \ + do { \ + printf("FAIL: " #reason "\n"); \ + g_some_test_failed = true; \ + } while (0) + +static ByteBuffer operator""_b(const char* string, size_t length) +{ + return ByteBuffer::copy(string, length); +} + +// tests go after here +// please be reasonable with orders kthx +static void aes_cbc_test_name(); +static void aes_cbc_test_encrypt(); +static void aes_cbc_test_decrypt(); +static void aes_ctr_test_name(); +static void aes_ctr_test_encrypt(); +static void aes_ctr_test_decrypt(); +static void aes_gcm_test_name(); +static void aes_gcm_test_encrypt(); +static void aes_gcm_test_decrypt(); + +static void md5_test_name(); +static void md5_test_hash(); +static void md5_test_consecutive_updates(); + +static void sha1_test_name(); +static void sha1_test_hash(); + +static void sha256_test_name(); +static void sha256_test_hash(); + +static void sha512_test_name(); +static void sha512_test_hash(); + +static void ghash_test_name(); +static void ghash_test_process(); + +static void hmac_md5_test_name(); +static void hmac_md5_test_process(); + +static void hmac_sha256_test_name(); +static void hmac_sha256_test_process(); + +static void hmac_sha512_test_name(); +static void hmac_sha512_test_process(); + +static void hmac_sha1_test_name(); +static void hmac_sha1_test_process(); + +static void rsa_test_encrypt(); +static void rsa_test_der_parse(); +static void rsa_test_encrypt_decrypt(); +static void rsa_emsa_pss_test_create(); +static void bigint_test_number_theory(); // FIXME: we should really move these num theory stuff out + +static void tls_test_client_hello(); + +static void bigint_test_fibo500(); +static void bigint_addition_edgecases(); +static void bigint_subtraction(); +static void bigint_multiplication(); +static void bigint_division(); +static void bigint_base10(); +static void bigint_import_export(); +static void bigint_bitwise(); + +static void bigint_test_signed_fibo500(); +static void bigint_signed_addition_edgecases(); +static void bigint_signed_subtraction(); +static void bigint_signed_multiplication(); +static void bigint_signed_division(); +static void bigint_signed_base10(); +static void bigint_signed_import_export(); +static void bigint_signed_bitwise(); + +static int aes_cbc_tests() +{ + aes_cbc_test_name(); + if (encrypting) { + aes_cbc_test_encrypt(); + } else { + aes_cbc_test_decrypt(); + } + + return g_some_test_failed ? 1 : 0; +} + +static void aes_cbc_test_name() +{ + I_TEST((AES CBC class name)); + Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Encryption); + if (cipher.class_name() != "AES_CBC") + FAIL(Invalid class name); + else + PASS; +} + +static void aes_cbc_test_encrypt() +{ + auto test_it = [](auto& cipher, auto& result) { + auto in = "This is a test! This is another test!"_b; + auto out = cipher.create_aligned_buffer(in.size()); + auto iv = ByteBuffer::create_zeroed(Crypto::Cipher::AESCipher::block_size()); + auto out_span = out.bytes(); + cipher.encrypt(in, out_span, iv); + if (out.size() != sizeof(result)) + FAIL(size mismatch); + else if (memcmp(out_span.data(), result, out_span.size()) != 0) { + FAIL(invalid data); + print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); + } else + PASS; + }; + { + I_TEST((AES CBC with 128 bit key | Encrypt)) + u8 result[] { + 0xb8, 0x06, 0x7c, 0xf2, 0xa9, 0x56, 0x63, 0x58, 0x2d, 0x5c, 0xa1, 0x4b, 0xc5, 0xe3, 0x08, + 0xcf, 0xb5, 0x93, 0xfb, 0x67, 0xb6, 0xf7, 0xaf, 0x45, 0x34, 0x64, 0x70, 0x9e, 0xc9, 0x1a, + 0x8b, 0xd3, 0x70, 0x45, 0xf0, 0x79, 0x65, 0xca, 0xb9, 0x03, 0x88, 0x72, 0x1c, 0xdd, 0xab, + 0x45, 0x6b, 0x1c + }; + Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Encryption); + test_it(cipher, result); + } + { + I_TEST((AES CBC with 192 bit key | Encrypt)) + u8 result[] { + 0xae, 0xd2, 0x70, 0xc4, 0x9c, 0xaa, 0x83, 0x33, 0xd3, 0xd3, 0xac, 0x11, 0x65, 0x35, 0xf7, + 0x19, 0x48, 0x7c, 0x7a, 0x8a, 0x95, 0x64, 0xe7, 0xc6, 0x0a, 0xdf, 0x10, 0x06, 0xdc, 0x90, + 0x68, 0x51, 0x09, 0xd7, 0x3b, 0x48, 0x1b, 0x8a, 0xd3, 0x50, 0x09, 0xba, 0xfc, 0xde, 0x11, + 0xe0, 0x3f, 0xcb + }; + Crypto::Cipher::AESCipher::CBCMode cipher("Well Hello Friends! whf!"_b, 192, Crypto::Cipher::Intent::Encryption); + test_it(cipher, result); + } + { + I_TEST((AES CBC with 256 bit key | Encrypt)) + u8 result[] { + 0x0a, 0x44, 0x4d, 0x62, 0x9e, 0x8b, 0xd8, 0x11, 0x80, 0x48, 0x2a, 0x32, 0x53, 0x61, 0xe7, + 0x59, 0x62, 0x55, 0x9e, 0xf4, 0xe6, 0xad, 0xea, 0xc5, 0x0b, 0xf6, 0xbc, 0x6a, 0xcb, 0x9c, + 0x47, 0x9f, 0xc2, 0x21, 0xe6, 0x19, 0x62, 0xc3, 0x75, 0xca, 0xab, 0x2d, 0x18, 0xa1, 0x54, + 0xd1, 0x41, 0xe6 + }; + Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriendsWellHelloFriends"_b, 256, Crypto::Cipher::Intent::Encryption); + test_it(cipher, result); + } + { + I_TEST((AES CBC with 256 bit key | Encrypt with unsigned key)) + u8 result[] { + 0x18, 0x71, 0x80, 0x4c, 0x28, 0x07, 0x55, 0x3c, 0x05, 0x33, 0x36, 0x3f, 0x19, 0x38, 0x5c, + 0xbe, 0xf8, 0xb8, 0x0e, 0x0e, 0x66, 0x67, 0x63, 0x9c, 0xbf, 0x73, 0xcd, 0x82, 0xf9, 0xcb, + 0x9d, 0x81, 0x56, 0xc6, 0x75, 0x14, 0x8b, 0x79, 0x60, 0xb0, 0xdf, 0xaa, 0x2c, 0x2b, 0xd4, + 0xd6, 0xa0, 0x46 + }; + u8 key[] { 0x0a, 0x8c, 0x5b, 0x0d, 0x8a, 0x68, 0x43, 0xf7, 0xaf, 0xc0, 0xe3, 0x4e, 0x4b, 0x43, 0xaa, 0x28, 0x69, 0x9b, 0x6f, 0xe7, 0x24, 0x82, 0x1c, 0x71, 0x86, 0xf6, 0x2b, 0x87, 0xd6, 0x8b, 0x8f, 0xf1 }; + Crypto::Cipher::AESCipher::CBCMode cipher(ReadonlyBytes { key, sizeof(key) }, 256, Crypto::Cipher::Intent::Encryption); + test_it(cipher, result); + } + // TODO: Test non-CMS padding options +} +static void aes_cbc_test_decrypt() +{ + auto test_it = [](auto& cipher, auto& result, auto result_len) { + auto true_value = "This is a test! This is another test!"; + auto in = ByteBuffer::copy(result, result_len); + auto out = cipher.create_aligned_buffer(in.size()); + auto iv = ByteBuffer::create_zeroed(Crypto::Cipher::AESCipher::block_size()); + auto out_span = out.bytes(); + cipher.decrypt(in, out_span, iv); + if (out_span.size() != strlen(true_value)) { + FAIL(size mismatch); + printf("Expected %zu bytes but got %zu\n", strlen(true_value), out_span.size()); + } else if (memcmp(out_span.data(), true_value, strlen(true_value)) != 0) { + FAIL(invalid data); + print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); + } else + PASS; + }; + { + I_TEST((AES CBC with 128 bit key | Decrypt)) + u8 result[] { + 0xb8, 0x06, 0x7c, 0xf2, 0xa9, 0x56, 0x63, 0x58, 0x2d, 0x5c, 0xa1, 0x4b, 0xc5, 0xe3, 0x08, + 0xcf, 0xb5, 0x93, 0xfb, 0x67, 0xb6, 0xf7, 0xaf, 0x45, 0x34, 0x64, 0x70, 0x9e, 0xc9, 0x1a, + 0x8b, 0xd3, 0x70, 0x45, 0xf0, 0x79, 0x65, 0xca, 0xb9, 0x03, 0x88, 0x72, 0x1c, 0xdd, 0xab, + 0x45, 0x6b, 0x1c + }; + Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Decryption); + test_it(cipher, result, 48); + } + { + I_TEST((AES CBC with 192 bit key | Decrypt)) + u8 result[] { + 0xae, 0xd2, 0x70, 0xc4, 0x9c, 0xaa, 0x83, 0x33, 0xd3, 0xd3, 0xac, 0x11, 0x65, 0x35, 0xf7, + 0x19, 0x48, 0x7c, 0x7a, 0x8a, 0x95, 0x64, 0xe7, 0xc6, 0x0a, 0xdf, 0x10, 0x06, 0xdc, 0x90, + 0x68, 0x51, 0x09, 0xd7, 0x3b, 0x48, 0x1b, 0x8a, 0xd3, 0x50, 0x09, 0xba, 0xfc, 0xde, 0x11, + 0xe0, 0x3f, 0xcb + }; + Crypto::Cipher::AESCipher::CBCMode cipher("Well Hello Friends! whf!"_b, 192, Crypto::Cipher::Intent::Decryption); + test_it(cipher, result, 48); + } + { + I_TEST((AES CBC with 256 bit key | Decrypt)) + u8 result[] { + 0x0a, 0x44, 0x4d, 0x62, 0x9e, 0x8b, 0xd8, 0x11, 0x80, 0x48, 0x2a, 0x32, 0x53, 0x61, 0xe7, + 0x59, 0x62, 0x55, 0x9e, 0xf4, 0xe6, 0xad, 0xea, 0xc5, 0x0b, 0xf6, 0xbc, 0x6a, 0xcb, 0x9c, + 0x47, 0x9f, 0xc2, 0x21, 0xe6, 0x19, 0x62, 0xc3, 0x75, 0xca, 0xab, 0x2d, 0x18, 0xa1, 0x54, + 0xd1, 0x41, 0xe6 + }; + Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriendsWellHelloFriends"_b, 256, Crypto::Cipher::Intent::Decryption); + test_it(cipher, result, 48); + } + // TODO: Test non-CMS padding options +} + +static int aes_ctr_tests() +{ + aes_ctr_test_name(); + if (encrypting) { + aes_ctr_test_encrypt(); + } else { + aes_ctr_test_decrypt(); + } + + return g_some_test_failed ? 1 : 0; +} + +static void aes_ctr_test_name() +{ + I_TEST((AES CTR class name)); + Crypto::Cipher::AESCipher::CTRMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Encryption); + if (cipher.class_name() != "AES_CTR") + FAIL(Invalid class name); + else + PASS; +} + +#define AS_BB(x) (ReadonlyBytes { (x), sizeof((x)) / sizeof((x)[0]) }) +static void aes_ctr_test_encrypt() +{ + auto test_it = [](auto key, auto ivec, auto in, auto out_expected) { + // nonce is already included in ivec. + Crypto::Cipher::AESCipher::CTRMode cipher(key, 8 * key.size(), Crypto::Cipher::Intent::Encryption); + ByteBuffer out_actual = ByteBuffer::create_zeroed(in.size()); + Bytes out_span = out_actual.bytes(); + cipher.encrypt(in, out_span, ivec); + if (out_expected.size() != out_actual.size()) { + FAIL(size mismatch); + printf("Expected %zu bytes but got %zu\n", out_expected.size(), out_span.size()); + print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); + } else if (memcmp(out_expected.data(), out_span.data(), out_expected.size()) != 0) { + FAIL(invalid data); + print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); + } else + PASS; + }; + // From RFC 3686, Section 6 + { + // Test Vector #1 + I_TEST((AES CTR 16 octets with 128 bit key | Encrypt)) + u8 key[] { + 0xae, 0x68, 0x52, 0xf8, 0x12, 0x10, 0x67, 0xcc, 0x4b, 0xf7, 0xa5, 0x76, 0x55, 0x77, 0xf3, 0x9e + }; + u8 ivec[] { + 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 + }; + u8 out[] { + 0xe4, 0x09, 0x5d, 0x4f, 0xb7, 0xa7, 0xb3, 0x79, 0x2d, 0x61, 0x75, 0xa3, 0x26, 0x13, 0x11, 0xb8 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + { + // Test Vector #2 + I_TEST((AES CTR 32 octets with 128 bit key | Encrypt)) + u8 key[] { + 0x7e, 0x24, 0x06, 0x78, 0x17, 0xfa, 0xe0, 0xd7, 0x43, 0xd6, 0xce, 0x1f, 0x32, 0x53, 0x91, 0x63 + }; + u8 ivec[] { + 0x00, 0x6c, 0xb6, 0xdb, 0xc0, 0x54, 0x3b, 0x59, 0xda, 0x48, 0xd9, 0x0b, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + u8 out[] { + 0x51, 0x04, 0xa1, 0x06, 0x16, 0x8a, 0x72, 0xd9, 0x79, 0x0d, 0x41, 0xee, 0x8e, 0xda, 0xd3, 0x88, + 0xeb, 0x2e, 0x1e, 0xfc, 0x46, 0xda, 0x57, 0xc8, 0xfc, 0xe6, 0x30, 0xdf, 0x91, 0x41, 0xbe, 0x28 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + { + // Test Vector #3 + I_TEST((AES CTR 36 octets with 128 bit key | Encrypt)) + u8 key[] { + 0x76, 0x91, 0xbe, 0x03, 0x5e, 0x50, 0x20, 0xa8, 0xac, 0x6e, 0x61, 0x85, 0x29, 0xf9, 0xa0, 0xdc + }; + u8 ivec[] { + 0x00, 0xe0, 0x01, 0x7b, 0x27, 0x77, 0x7f, 0x3f, 0x4a, 0x17, 0x86, 0xf0, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 + }; + u8 out[] { + 0xc1, 0xcf, 0x48, 0xa8, 0x9f, 0x2f, 0xfd, 0xd9, 0xcf, 0x46, 0x52, 0xe9, 0xef, 0xdb, 0x72, 0xd7, 0x45, 0x40, 0xa4, 0x2b, 0xde, 0x6d, 0x78, 0x36, 0xd5, 0x9a, 0x5c, 0xea, 0xae, 0xf3, 0x10, 0x53, 0x25, 0xb2, 0x07, 0x2f + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + { + // Test Vector #4 + I_TEST((AES CTR 16 octets with 192 bit key | Encrypt)) + u8 key[] { + 0x16, 0xaf, 0x5b, 0x14, 0x5f, 0xc9, 0xf5, 0x79, 0xc1, 0x75, 0xf9, 0x3e, 0x3b, 0xfb, 0x0e, 0xed, 0x86, 0x3d, 0x06, 0xcc, 0xfd, 0xb7, 0x85, 0x15 + }; + u8 ivec[] { + 0x00, 0x00, 0x00, 0x48, 0x36, 0x73, 0x3c, 0x14, 0x7d, 0x6d, 0x93, 0xcb, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 + }; + u8 out[] { + 0x4b, 0x55, 0x38, 0x4f, 0xe2, 0x59, 0xc9, 0xc8, 0x4e, 0x79, 0x35, 0xa0, 0x03, 0xcb, 0xe9, 0x28 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + { + // Test Vector #5 + I_TEST((AES CTR 32 octets with 192 bit key | Encrypt)) + u8 key[] { + 0x7c, 0x5c, 0xb2, 0x40, 0x1b, 0x3d, 0xc3, 0x3c, 0x19, 0xe7, 0x34, 0x08, 0x19, 0xe0, 0xf6, 0x9c, 0x67, 0x8c, 0x3d, 0xb8, 0xe6, 0xf6, 0xa9, 0x1a + }; + u8 ivec[] { + 0x00, 0x96, 0xb0, 0x3b, 0x02, 0x0c, 0x6e, 0xad, 0xc2, 0xcb, 0x50, 0x0d, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + u8 out[] { + 0x45, 0x32, 0x43, 0xfc, 0x60, 0x9b, 0x23, 0x32, 0x7e, 0xdf, 0xaa, 0xfa, 0x71, 0x31, 0xcd, 0x9f, 0x84, 0x90, 0x70, 0x1c, 0x5a, 0xd4, 0xa7, 0x9c, 0xfc, 0x1f, 0xe0, 0xff, 0x42, 0xf4, 0xfb, 0x00 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + { + // Test Vector #6 + I_TEST((AES CTR 36 octets with 192 bit key | Encrypt)) + u8 key[] { + 0x02, 0xbf, 0x39, 0x1e, 0xe8, 0xec, 0xb1, 0x59, 0xb9, 0x59, 0x61, 0x7b, 0x09, 0x65, 0x27, 0x9b, 0xf5, 0x9b, 0x60, 0xa7, 0x86, 0xd3, 0xe0, 0xfe + }; + u8 ivec[] { + 0x00, 0x07, 0xbd, 0xfd, 0x5c, 0xbd, 0x60, 0x27, 0x8d, 0xcc, 0x09, 0x12, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 + }; + u8 out[] { + 0x96, 0x89, 0x3f, 0xc5, 0x5e, 0x5c, 0x72, 0x2f, 0x54, 0x0b, 0x7d, 0xd1, 0xdd, 0xf7, 0xe7, 0x58, 0xd2, 0x88, 0xbc, 0x95, 0xc6, 0x91, 0x65, 0x88, 0x45, 0x36, 0xc8, 0x11, 0x66, 0x2f, 0x21, 0x88, 0xab, 0xee, 0x09, 0x35 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + { + // Test Vector #7 + I_TEST((AES CTR 16 octets with 256 bit key | Encrypt)) + u8 key[] { + 0x77, 0x6b, 0xef, 0xf2, 0x85, 0x1d, 0xb0, 0x6f, 0x4c, 0x8a, 0x05, 0x42, 0xc8, 0x69, 0x6f, 0x6c, 0x6a, 0x81, 0xaf, 0x1e, 0xec, 0x96, 0xb4, 0xd3, 0x7f, 0xc1, 0xd6, 0x89, 0xe6, 0xc1, 0xc1, 0x04 + }; + u8 ivec[] { + 0x00, 0x00, 0x00, 0x60, 0xdb, 0x56, 0x72, 0xc9, 0x7a, 0xa8, 0xf0, 0xb2, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 + }; + u8 out[] { + 0x14, 0x5a, 0xd0, 0x1d, 0xbf, 0x82, 0x4e, 0xc7, 0x56, 0x08, 0x63, 0xdc, 0x71, 0xe3, 0xe0, 0xc0 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + { + // Test Vector #8 + I_TEST((AES CTR 32 octets with 256 bit key | Encrypt)) + u8 key[] { + 0xf6, 0xd6, 0x6d, 0x6b, 0xd5, 0x2d, 0x59, 0xbb, 0x07, 0x96, 0x36, 0x58, 0x79, 0xef, 0xf8, 0x86, 0xc6, 0x6d, 0xd5, 0x1a, 0x5b, 0x6a, 0x99, 0x74, 0x4b, 0x50, 0x59, 0x0c, 0x87, 0xa2, 0x38, 0x84 + }; + u8 ivec[] { + 0x00, 0xfa, 0xac, 0x24, 0xc1, 0x58, 0x5e, 0xf1, 0x5a, 0x43, 0xd8, 0x75, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f + }; + u8 out[] { + 0xf0, 0x5e, 0x23, 0x1b, 0x38, 0x94, 0x61, 0x2c, 0x49, 0xee, 0x00, 0x0b, 0x80, 0x4e, 0xb2, 0xa9, 0xb8, 0x30, 0x6b, 0x50, 0x8f, 0x83, 0x9d, 0x6a, 0x55, 0x30, 0x83, 0x1d, 0x93, 0x44, 0xaf, 0x1c + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + { + // Test Vector #9 + I_TEST((AES CTR 36 octets with 256 bit key | Encrypt)) + u8 key[] { + 0xff, 0x7a, 0x61, 0x7c, 0xe6, 0x91, 0x48, 0xe4, 0xf1, 0x72, 0x6e, 0x2f, 0x43, 0x58, 0x1d, 0xe2, 0xaa, 0x62, 0xd9, 0xf8, 0x05, 0x53, 0x2e, 0xdf, 0xf1, 0xee, 0xd6, 0x87, 0xfb, 0x54, 0x15, 0x3d + }; + u8 ivec[] { + 0x00, 0x1c, 0xc5, 0xb7, 0x51, 0xa5, 0x1d, 0x70, 0xa1, 0xc1, 0x11, 0x48, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 in[] { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 + }; + u8 out[] { + 0xeb, 0x6c, 0x52, 0x82, 0x1d, 0x0b, 0xbb, 0xf7, 0xce, 0x75, 0x94, 0x46, 0x2a, 0xca, 0x4f, 0xaa, 0xb4, 0x07, 0xdf, 0x86, 0x65, 0x69, 0xfd, 0x07, 0xf4, 0x8c, 0xc0, 0xb5, 0x83, 0xd6, 0x07, 0x1f, 0x1e, 0xc0, 0xe6, 0xb8 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + // Manual test case + { + // This test checks whether counter overflow crashes. + I_TEST((AES CTR 36 octets with 256 bit key, high counter | Encrypt)) + u8 key[] { + 0xff, 0x7a, 0x61, 0x7c, 0xe6, 0x91, 0x48, 0xe4, 0xf1, 0x72, 0x6e, 0x2f, 0x43, 0x58, 0x1d, 0xe2, 0xaa, 0x62, 0xd9, 0xf8, 0x05, 0x53, 0x2e, 0xdf, 0xf1, 0xee, 0xd6, 0x87, 0xfb, 0x54, 0x15, 0x3d + }; + u8 ivec[] { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + u8 in[] { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 + }; + u8 out[] { + // Pasted from the output. The actual success condition is + // not crashing when incrementing the counter. + 0x6e, 0x8c, 0xfc, 0x59, 0x08, 0xa8, 0xc0, 0xf1, 0xe6, 0x85, 0x96, 0xe9, 0xc5, 0x40, 0xb6, 0x8b, 0xfe, 0x28, 0x72, 0xe2, 0x24, 0x11, 0x7e, 0x59, 0xef, 0xac, 0x5c, 0xe1, 0x06, 0x89, 0x09, 0xab, 0xf8, 0x90, 0x1c, 0x66 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } +} + +static void aes_ctr_test_decrypt() +{ + auto test_it = [](auto key, auto ivec, auto in, auto out_expected) { + // nonce is already included in ivec. + Crypto::Cipher::AESCipher::CTRMode cipher(key, 8 * key.size(), Crypto::Cipher::Intent::Decryption); + ByteBuffer out_actual = ByteBuffer::create_zeroed(in.size()); + auto out_span = out_actual.bytes(); + cipher.decrypt(in, out_span, ivec); + if (out_expected.size() != out_span.size()) { + FAIL(size mismatch); + printf("Expected %zu bytes but got %zu\n", out_expected.size(), out_span.size()); + print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); + } else if (memcmp(out_expected.data(), out_span.data(), out_expected.size()) != 0) { + FAIL(invalid data); + print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); + } else + PASS; + }; + // From RFC 3686, Section 6 + { + // Test Vector #1 + I_TEST((AES CTR 16 octets with 128 bit key | Decrypt)) + u8 key[] { + 0xae, 0x68, 0x52, 0xf8, 0x12, 0x10, 0x67, 0xcc, 0x4b, 0xf7, 0xa5, 0x76, 0x55, 0x77, 0xf3, 0x9e + }; + u8 ivec[] { + 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h + }; + u8 out[] { + 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 + }; + u8 in[] { + 0xe4, 0x09, 0x5d, 0x4f, 0xb7, 0xa7, 0xb3, 0x79, 0x2d, 0x61, 0x75, 0xa3, 0x26, 0x13, 0x11, 0xb8 + }; + test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); + } + // If encryption works, then decryption works, too. +} + +static int aes_gcm_tests() +{ + aes_gcm_test_name(); + if (encrypting) { + aes_gcm_test_encrypt(); + } else { + aes_gcm_test_decrypt(); + } + + return g_some_test_failed ? 1 : 0; +} + +static void aes_gcm_test_name() +{ + I_TEST((AES GCM class name)); + Crypto::Cipher::AESCipher::GCMMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Encryption); + if (cipher.class_name() != "AES_GCM") + FAIL(Invalid class name); + else + PASS; +} + +static void aes_gcm_test_encrypt() +{ + { + I_TEST((AES GCM Encrypt | Empty)); + Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 result_tag[] { 0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61, 0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, 0x45, 0x5a }; + Bytes out; + auto tag = ByteBuffer::create_uninitialized(16); + cipher.encrypt({}, out, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), {}, tag); + if (memcmp(result_tag, tag.data(), tag.size()) != 0) { + FAIL(Invalid auth tag); + print_buffer(tag, -1); + } else + PASS; + } + { + I_TEST((AES GCM Encrypt | Zeros)); + Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 result_tag[] { 0xab, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd, 0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd, 0xdf }; + u8 result_ct[] { 0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78 }; + auto tag = ByteBuffer::create_uninitialized(16); + auto out = ByteBuffer::create_uninitialized(16); + auto out_bytes = out.bytes(); + cipher.encrypt("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), {}, tag); + if (memcmp(result_ct, out.data(), out.size()) != 0) { + FAIL(Invalid ciphertext); + print_buffer(out, -1); + } else if (memcmp(result_tag, tag.data(), tag.size()) != 0) { + FAIL(Invalid auth tag); + print_buffer(tag, -1); + } else + PASS; + } + { + I_TEST((AES GCM Encrypt | Multiple Blocks With IV)); + Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 result_tag[] { 0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6, 0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4 }; + u8 result_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; + auto tag = ByteBuffer::create_uninitialized(16); + auto out = ByteBuffer::create_uninitialized(64); + auto out_bytes = out.bytes(); + cipher.encrypt( + "\xd9\x31\x32\x25\xf8\x84\x06\xe5\xa5\x59\x09\xc5\xaf\xf5\x26\x9a\x86\xa7\xa9\x53\x15\x34\xf7\xda\x2e\x4c\x30\x3d\x8a\x31\x8a\x72\x1c\x3c\x0c\x95\x95\x68\x09\x53\x2f\xcf\x0e\x24\x49\xa6\xb5\x25\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57\xba\x63\x7b\x39\x1a\xaf\xd2\x55"_b.bytes(), + out_bytes, + "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), + {}, + tag); + if (memcmp(result_ct, out.data(), out.size()) != 0) { + FAIL(Invalid ciphertext); + print_buffer(out, -1); + } else if (memcmp(result_tag, tag.data(), tag.size()) != 0) { + FAIL(Invalid auth tag); + print_buffer(tag, -1); + } else + PASS; + } + { + I_TEST((AES GCM Encrypt | With AAD)); + Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 result_tag[] { 0x93, 0xae, 0x16, 0x97, 0x49, 0xa3, 0xbf, 0x39, 0x4f, 0x61, 0xb7, 0xc1, 0xb1, 0x2, 0x4f, 0x60 }; + u8 result_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; + auto tag = ByteBuffer::create_uninitialized(16); + auto out = ByteBuffer::create_uninitialized(64); + auto out_bytes = out.bytes(); + cipher.encrypt( + "\xd9\x31\x32\x25\xf8\x84\x06\xe5\xa5\x59\x09\xc5\xaf\xf5\x26\x9a\x86\xa7\xa9\x53\x15\x34\xf7\xda\x2e\x4c\x30\x3d\x8a\x31\x8a\x72\x1c\x3c\x0c\x95\x95\x68\x09\x53\x2f\xcf\x0e\x24\x49\xa6\xb5\x25\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57\xba\x63\x7b\x39\x1a\xaf\xd2\x55"_b.bytes(), + out_bytes, + "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), + "\xde\xad\xbe\xef\xfa\xaf\x11\xcc"_b.bytes(), + tag); + if (memcmp(result_ct, out.data(), out.size()) != 0) { + FAIL(Invalid ciphertext); + print_buffer(out, -1); + } else if (memcmp(result_tag, tag.data(), tag.size()) != 0) { + FAIL(Invalid auth tag); + print_buffer(tag, -1); + } else + PASS; + } +} + +static void aes_gcm_test_decrypt() +{ + { + I_TEST((AES GCM Decrypt | Empty)); + Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 input_tag[] { 0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61, 0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, 0x45, 0x5a }; + Bytes out; + auto consistency = cipher.decrypt({}, out, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), {}, { input_tag, 16 }); + if (consistency != Crypto::VerificationConsistency::Consistent) { + FAIL(Verification reported inconsistent); + } else if (out.size() != 0) { + FAIL(Invalid plain text); + } else + PASS; + } + { + I_TEST((AES GCM Decrypt | Zeros)); + Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 input_tag[] { 0xab, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd, 0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd, 0xdf }; + u8 input_ct[] { 0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78 }; + u8 result_pt[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + auto out = ByteBuffer::create_uninitialized(16); + auto out_bytes = out.bytes(); + auto consistency = cipher.decrypt({ input_ct, 16 }, out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), {}, { input_tag, 16 }); + if (consistency != Crypto::VerificationConsistency::Consistent) { + FAIL(Verification reported inconsistent); + } else if (memcmp(result_pt, out.data(), out.size()) != 0) { + FAIL(Invalid plaintext); + print_buffer(out, -1); + } else + PASS; + } + { + I_TEST((AES GCM Decrypt | Multiple Blocks With IV)); + Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 input_tag[] { 0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6, 0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4 }; + u8 input_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; + u8 result_pt[] { 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 }; + auto out = ByteBuffer::create_uninitialized(64); + auto out_bytes = out.bytes(); + + auto consistency = cipher.decrypt( + { input_ct, 64 }, + out_bytes, + "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), + {}, + { input_tag, 16 }); + if (memcmp(result_pt, out.data(), out.size()) != 0) { + FAIL(Invalid plaintext); + print_buffer(out, -1); + } else if (consistency != Crypto::VerificationConsistency::Consistent) { + FAIL(Verification reported inconsistent); + } else + PASS; + } + { + I_TEST((AES GCM Decrypt | With AAD)); + Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 input_tag[] { 0x93, 0xae, 0x16, 0x97, 0x49, 0xa3, 0xbf, 0x39, 0x4f, 0x61, 0xb7, 0xc1, 0xb1, 0x2, 0x4f, 0x60 }; + u8 input_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; + u8 result_pt[] { 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 }; + auto out = ByteBuffer::create_uninitialized(64); + auto out_bytes = out.bytes(); + auto consistency = cipher.decrypt( + { input_ct, 64 }, + out_bytes, + "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), + "\xde\xad\xbe\xef\xfa\xaf\x11\xcc"_b.bytes(), + { input_tag, 16 }); + if (memcmp(result_pt, out.data(), out.size()) != 0) { + FAIL(Invalid plaintext); + print_buffer(out, -1); + } else if (consistency != Crypto::VerificationConsistency::Consistent) { + FAIL(Verification reported inconsistent); + } else + PASS; + } + { + I_TEST((AES GCM Decrypt | With AAD - Invalid Tag)); + Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); + u8 input_tag[] { 0x94, 0xae, 0x16, 0x97, 0x49, 0xa3, 0xbf, 0x39, 0x4f, 0x61, 0xb7, 0xc1, 0xb1, 0x2, 0x4f, 0x60 }; + u8 input_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; + auto out = ByteBuffer::create_uninitialized(64); + auto out_bytes = out.bytes(); + auto consistency = cipher.decrypt( + { input_ct, 64 }, + out_bytes, + "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), + "\xde\xad\xbe\xef\xfa\xaf\x11\xcc"_b.bytes(), + { input_tag, 16 }); + + if (consistency != Crypto::VerificationConsistency::Inconsistent) + FAIL(Verification reported consistent); + else + PASS; + } +} + +static int md5_tests() +{ + md5_test_name(); + md5_test_hash(); + md5_test_consecutive_updates(); + return g_some_test_failed ? 1 : 0; +} + +static void md5_test_name() +{ + I_TEST((MD5 class name)); + Crypto::Hash::MD5 md5; + if (md5.class_name() != "MD5") + FAIL(Invalid class name); + else + PASS; +} + +static void md5_test_hash() +{ + { + I_TEST((MD5 Hashing | "Well hello friends")); + u8 result[] { + 0xaf, 0x04, 0x3a, 0x08, 0x94, 0x38, 0x6e, 0x7f, 0xbf, 0x73, 0xe4, 0xaa, 0xf0, 0x8e, 0xee, 0x4c + }; + auto digest = Crypto::Hash::MD5::hash("Well hello friends"); + + if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); + } else { + PASS; + } + } + // RFC tests + { + I_TEST((MD5 Hashing | "")); + u8 result[] { + 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e + }; + auto digest = Crypto::Hash::MD5::hash(""); + + if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); + } else { + PASS; + } + } + { + I_TEST((MD5 Hashing | "a")); + u8 result[] { + 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61 + }; + auto digest = Crypto::Hash::MD5::hash("a"); + + if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); + } else { + PASS; + } + } + { + I_TEST((MD5 Hashing | "abcdefghijklmnopqrstuvwxyz")); + u8 result[] { + 0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67, 0xe1, 0x3b + }; + auto digest = Crypto::Hash::MD5::hash("abcdefghijklmnopqrstuvwxyz"); + + if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); + } else { + PASS; + } + } + { + I_TEST((MD5 Hashing | Long Sequence)); + u8 result[] { + 0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07, 0xb6, 0x7a + }; + auto digest = Crypto::Hash::MD5::hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"); + + if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); + } else { + PASS; + } + } +} + +static void md5_test_consecutive_updates() +{ + { + I_TEST((MD5 Hashing | Multiple Updates)); + u8 result[] { + 0xaf, 0x04, 0x3a, 0x08, 0x94, 0x38, 0x6e, 0x7f, 0xbf, 0x73, 0xe4, 0xaa, 0xf0, 0x8e, 0xee, 0x4c + }; + Crypto::Hash::MD5 md5; + + md5.update("Well"); + md5.update(" hello "); + md5.update("friends"); + auto digest = md5.digest(); + + if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) + FAIL(Invalid hash); + else + PASS; + } + { + I_TEST((MD5 Hashing | Reuse)); + Crypto::Hash::MD5 md5; + + md5.update("Well"); + md5.update(" hello "); + md5.update("friends"); + auto digest0 = md5.digest(); + + md5.update("Well"); + md5.update(" hello "); + md5.update("friends"); + auto digest1 = md5.digest(); + + if (memcmp(digest0.data, digest1.data, Crypto::Hash::MD5::digest_size()) != 0) + FAIL(Cannot reuse); + else + PASS; + } +} + +static int hmac_md5_tests() +{ + hmac_md5_test_name(); + hmac_md5_test_process(); + return g_some_test_failed ? 1 : 0; +} + +static int hmac_sha256_tests() +{ + hmac_sha256_test_name(); + hmac_sha256_test_process(); + return g_some_test_failed ? 1 : 0; +} + +static int hmac_sha512_tests() +{ + hmac_sha512_test_name(); + hmac_sha512_test_process(); + return g_some_test_failed ? 1 : 0; +} + +static int hmac_sha1_tests() +{ + hmac_sha1_test_name(); + hmac_sha1_test_process(); + return g_some_test_failed ? 1 : 0; +} + +static void hmac_md5_test_name() +{ + I_TEST((HMAC - MD5 | Class name)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + if (hmac.class_name() != "HMAC-MD5") + FAIL(Invalid class name); + else + PASS; +} + +static void hmac_md5_test_process() +{ + { + I_TEST((HMAC - MD5 | Basic)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + u8 result[] { + 0x3b, 0x5b, 0xde, 0x30, 0x3a, 0x54, 0x7b, 0xbb, 0x09, 0xfe, 0x78, 0x89, 0xbc, 0x9f, 0x22, 0xa3 + }; + auto mac = hmac.process("Some bogus data"); + if (memcmp(result, mac.data, hmac.digest_size()) != 0) { + FAIL(Invalid mac); + print_buffer({ mac.data, hmac.digest_size() }, -1); + } else + PASS; + } + { + I_TEST((HMAC - MD5 | Reuse)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + + auto mac_0 = hmac.process("Some bogus data"); + auto mac_1 = hmac.process("Some bogus data"); + + if (memcmp(mac_0.data, mac_1.data, hmac.digest_size()) != 0) { + FAIL(Cannot reuse); + } else + PASS; + } +} + +static int ghash_tests() +{ + ghash_test_name(); + ghash_test_process(); + return g_some_test_failed ? 1 : 0; +} + +static void ghash_test_name() +{ + I_TEST((GHash class name)); + Crypto::Authentication::GHash ghash("WellHelloFriends"); + if (ghash.class_name() != "GHash") + FAIL(Invalid class name); + else + PASS; +} + +static void hmac_sha1_test_name() +{ + I_TEST((HMAC - SHA1 | Class name)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + if (hmac.class_name() != "HMAC-SHA1") + FAIL(Invalid class name); + else + PASS; +} + +static void hmac_sha1_test_process() +{ + { + I_TEST((HMAC - SHA1 | Basic)); + u8 key[] { 0xc8, 0x52, 0xe5, 0x4a, 0x2c, 0x03, 0x2b, 0xc9, 0x63, 0xd3, 0xc2, 0x79, 0x0f, 0x76, 0x43, 0xef, 0x36, 0xc3, 0x7a, 0xca }; + Crypto::Authentication::HMAC hmac(ReadonlyBytes { key, sizeof(key) }); + u8 result[] { + 0x2c, 0x57, 0x32, 0x61, 0x3b, 0xa7, 0x84, 0x87, 0x0e, 0x4f, 0x42, 0x07, 0x2f, 0xf0, 0xe7, 0x41, 0xd7, 0x15, 0xf4, 0x56 + }; + u8 value[] { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x03, 0x03, 0x00, 0x10, 0x14, 0x00, 0x00, 0x0c, 0xa1, 0x91, 0x1a, 0x20, 0x59, 0xb5, 0x45, 0xa9, 0xb4, 0xad, 0x75, 0x3e + }; + auto mac = hmac.process(value, 29); + if (memcmp(result, mac.data, hmac.digest_size()) != 0) { + FAIL(Invalid mac); + print_buffer({ mac.data, hmac.digest_size() }, -1); + } else + PASS; + } + { + I_TEST((HMAC - SHA1 | Reuse)); + u8 key[] { 0xc8, 0x52, 0xe5, 0x4a, 0x2c, 0x03, 0x2b, 0xc9, 0x63, 0xd3, 0xc2, 0x79, 0x0f, 0x76, 0x43, 0xef, 0x36, 0xc3, 0x7a, 0xca }; + Crypto::Authentication::HMAC hmac(ReadonlyBytes { key, sizeof(key) }); + u8 result[] { + 0x2c, 0x57, 0x32, 0x61, 0x3b, 0xa7, 0x84, 0x87, 0x0e, 0x4f, 0x42, 0x07, 0x2f, 0xf0, 0xe7, 0x41, 0xd7, 0x15, 0xf4, 0x56 + }; + u8 value[] { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x03, 0x03, 0x00, 0x10, 0x14, 0x00, 0x00, 0x0c, 0xa1, 0x91, 0x1a, 0x20, 0x59, 0xb5, 0x45, 0xa9, 0xb4, 0xad, 0x75, 0x3e + }; + hmac.update(value, 8); + hmac.update(value + 8, 5); + hmac.update(value + 13, 16); + auto mac = hmac.digest(); + if (memcmp(result, mac.data, hmac.digest_size()) != 0) { + FAIL(Invalid mac); + print_buffer({ mac.data, hmac.digest_size() }, -1); + } else + PASS; + } +} + +static void ghash_test_process() +{ + { + I_TEST((GHash | Galois Field Multiply)); + u32 x[4] { 0x42831ec2, 0x21777424, 0x4b7221b7, 0x84d0d49c }, + y[4] { 0xb83b5337, 0x08bf535d, 0x0aa6e529, 0x80d53b78 }, z[4] { 0, 0, 0, 0 }; + static constexpr u32 result[4] { 0x59ed3f2b, 0xb1a0aaa0, 0x7c9f56c6, 0xa504647b }; + + Crypto::Authentication::galois_multiply(z, x, y); + if (memcmp(result, z, 4 * sizeof(u32)) != 0) { + FAIL(Invalid multiply value); + print_buffer({ z, 4 * sizeof(u32) }, -1); + print_buffer({ result, 4 * sizeof(u32) }, -1); + } else + PASS; + } + { + I_TEST((GHash | Galois Field Multiply #2)); + u32 x[4] { 59300558, 1622582162, 4079534777, 1907555960 }, + y[4] { 1726565332, 4018809915, 2286746201, 3392416558 }, z[4]; + constexpr static u32 result[4] { 1580123974, 2440061576, 746958952, 1398005431 }; + + Crypto::Authentication::galois_multiply(z, x, y); + if (memcmp(result, z, 4 * sizeof(u32)) != 0) { + FAIL(Invalid multiply value); + print_buffer({ z, 4 * sizeof(u32) }, -1); + print_buffer({ result, 4 * sizeof(u32) }, -1); + } else + PASS; + } + // TODO: Add some GHash tests? + // Kinda hard, as there are no vectors and existing tools don't have an interface to it. +} + +static int sha1_tests() +{ + sha1_test_name(); + sha1_test_hash(); + return g_some_test_failed ? 1 : 0; +} + +static void sha1_test_name() +{ + I_TEST((SHA1 class name)); + Crypto::Hash::SHA1 sha; + if (sha.class_name() != "SHA1") { + FAIL(Invalid class name); + printf("%s\n", sha.class_name().characters()); + } else + PASS; +} + +static void sha1_test_hash() +{ + { + I_TEST((SHA256 Hashing | "")); + u8 result[] { + 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 + }; + auto digest = Crypto::Hash::SHA1::hash(""); + if (memcmp(result, digest.data, Crypto::Hash::SHA1::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::SHA1::digest_size() }, -1); + } else + PASS; + } + { + I_TEST((SHA256 Hashing | Long String)); + u8 result[] { + 0x12, 0x15, 0x1f, 0xb1, 0x04, 0x44, 0x93, 0xcc, 0xed, 0x54, 0xa6, 0xb8, 0x7e, 0x93, 0x37, 0x7b, 0xb2, 0x13, 0x39, 0xdb + }; + auto digest = Crypto::Hash::SHA1::hash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + if (memcmp(result, digest.data, Crypto::Hash::SHA1::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::SHA1::digest_size() }, -1); + } else + PASS; + } + { + I_TEST((SHA256 Hashing | Successive Updates)); + u8 result[] { + 0xd6, 0x6e, 0xce, 0xd1, 0xf4, 0x08, 0xc6, 0xd8, 0x35, 0xab, 0xf0, 0xc9, 0x05, 0x26, 0xa4, 0xb2, 0xb8, 0xa3, 0x7c, 0xd3 + }; + auto hasher = Crypto::Hash::SHA1 {}; + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaaaaaaaa"); + hasher.update("aaaaaaaaa"); + auto digest = hasher.digest(); + if (memcmp(result, digest.data, Crypto::Hash::SHA1::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::SHA1::digest_size() }, -1); + } else + PASS; + } +} + +static int sha256_tests() +{ + sha256_test_name(); + sha256_test_hash(); + return g_some_test_failed ? 1 : 0; +} + +static void sha256_test_name() +{ + I_TEST((SHA256 class name)); + Crypto::Hash::SHA256 sha; + if (sha.class_name() != "SHA256") { + FAIL(Invalid class name); + printf("%s\n", sha.class_name().characters()); + } else + PASS; +} + +static void sha256_test_hash() +{ + { + I_TEST((SHA256 Hashing | "Well hello friends")); + u8 result[] { + 0x9a, 0xcd, 0x50, 0xf9, 0xa2, 0xaf, 0x37, 0xe4, 0x71, 0xf7, 0x61, 0xc3, 0xfe, 0x7b, 0x8d, 0xea, 0x56, 0x17, 0xe5, 0x1d, 0xac, 0x80, 0x2f, 0xe6, 0xc1, 0x77, 0xb7, 0x4a, 0xbf, 0x0a, 0xbb, 0x5a + }; + auto digest = Crypto::Hash::SHA256::hash("Well hello friends"); + if (memcmp(result, digest.data, Crypto::Hash::SHA256::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::SHA256::digest_size() }, -1); + } else + PASS; + } + { + I_TEST((SHA256 Hashing | "")); + u8 result[] { + 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 + }; + auto digest = Crypto::Hash::SHA256::hash(""); + if (memcmp(result, digest.data, Crypto::Hash::SHA256::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::SHA256::digest_size() }, -1); + } else + PASS; + } +} + +static void hmac_sha256_test_name() +{ + I_TEST((HMAC - SHA256 | Class name)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + if (hmac.class_name() != "HMAC-SHA256") + FAIL(Invalid class name); + else + PASS; +} + +static void hmac_sha256_test_process() +{ + { + I_TEST((HMAC - SHA256 | Basic)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + u8 result[] { + 0x1a, 0xf2, 0x20, 0x62, 0xde, 0x3b, 0x84, 0x65, 0xc1, 0x25, 0x23, 0x99, 0x76, 0x15, 0x1b, 0xec, 0x15, 0x21, 0x82, 0x1f, 0x23, 0xca, 0x11, 0x66, 0xdd, 0x8c, 0x6e, 0xf1, 0x81, 0x3b, 0x7f, 0x1b + }; + auto mac = hmac.process("Some bogus data"); + if (memcmp(result, mac.data, hmac.digest_size()) != 0) { + FAIL(Invalid mac); + print_buffer({ mac.data, hmac.digest_size() }, -1); + } else + PASS; + } + { + I_TEST((HMAC - SHA256 | DataSize > FinalBlockDataSize)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + u8 result[] = { + 0x9b, 0xa3, 0x9e, 0xf3, 0xb4, 0x30, 0x5f, 0x6f, 0x67, 0xd0, 0xa8, 0xb0, 0xf0, 0xcb, 0x12, 0xf5, 0x85, 0xe2, 0x19, 0xba, 0x0c, 0x8b, 0xe5, 0x43, 0xf0, 0x93, 0x39, 0xa8, 0xa3, 0x07, 0xf1, 0x95 + }; + auto mac = hmac.process("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + if (memcmp(result, mac.data, hmac.digest_size()) != 0) { + FAIL(Invalid mac); + print_buffer({ mac.data, hmac.digest_size() }, -1); + } else + PASS; + } + { + I_TEST((HMAC - SHA256 | DataSize == BlockSize)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + u8 result[] = { + 0x1d, 0x90, 0xce, 0x68, 0x45, 0x0b, 0xba, 0xd6, 0xbe, 0x1c, 0xb2, 0x3a, 0xea, 0x7f, 0xac, 0x4b, 0x68, 0x08, 0xa4, 0x77, 0x81, 0x2a, 0xad, 0x5d, 0x05, 0xe2, 0x15, 0xe8, 0xf4, 0xcb, 0x06, 0xaf + }; + auto mac = hmac.process("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + if (memcmp(result, mac.data, hmac.digest_size()) != 0) { + FAIL(Invalid mac); + print_buffer({ mac.data, hmac.digest_size() }, -1); + } else + PASS; + } + { + I_TEST((HMAC - SHA256 | Reuse)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + + auto mac_0 = hmac.process("Some bogus data"); + auto mac_1 = hmac.process("Some bogus data"); + + if (memcmp(mac_0.data, mac_1.data, hmac.digest_size()) != 0) { + FAIL(Cannot reuse); + } else + PASS; + } +} + +static int sha512_tests() +{ + sha512_test_name(); + sha512_test_hash(); + return g_some_test_failed ? 1 : 0; +} + +static void sha512_test_name() +{ + I_TEST((SHA512 class name)); + Crypto::Hash::SHA512 sha; + if (sha.class_name() != "SHA512") { + FAIL(Invalid class name); + printf("%s\n", sha.class_name().characters()); + } else + PASS; +} + +static void sha512_test_hash() +{ + { + I_TEST((SHA512 Hashing | "Well hello friends")); + u8 result[] { + 0x00, 0xfe, 0x68, 0x09, 0x71, 0x0e, 0xcb, 0x2b, 0xe9, 0x58, 0x00, 0x13, 0x69, 0x6a, 0x9e, 0x9e, 0xbd, 0x09, 0x1b, 0xfe, 0x14, 0xc9, 0x13, 0x82, 0xc7, 0x40, 0x34, 0xfe, 0xca, 0xe6, 0x87, 0xcb, 0x26, 0x36, 0x92, 0xe6, 0x34, 0x94, 0x3a, 0x11, 0xe5, 0xbb, 0xb5, 0xeb, 0x8e, 0x70, 0xef, 0x64, 0xca, 0xf7, 0x21, 0xb1, 0xde, 0xf2, 0x34, 0x85, 0x6f, 0xa8, 0x56, 0xd8, 0x23, 0xa1, 0x3b, 0x29 + }; + auto digest = Crypto::Hash::SHA512::hash("Well hello friends"); + if (memcmp(result, digest.data, Crypto::Hash::SHA512::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::SHA512::digest_size() }, -1); + } else + PASS; + } + { + I_TEST((SHA512 Hashing | "")); + u8 result[] { + 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e + }; + auto digest = Crypto::Hash::SHA512::hash(""); + if (memcmp(result, digest.data, Crypto::Hash::SHA512::digest_size()) != 0) { + FAIL(Invalid hash); + print_buffer({ digest.data, Crypto::Hash::SHA512::digest_size() }, -1); + } else + PASS; + } +} + +static void hmac_sha512_test_name() +{ + I_TEST((HMAC - SHA512 | Class name)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + if (hmac.class_name() != "HMAC-SHA512") + FAIL(Invalid class name); + else + PASS; +} + +static void hmac_sha512_test_process() +{ + { + I_TEST((HMAC - SHA512 | Basic)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + u8 result[] { + 0xeb, 0xa8, 0x34, 0x11, 0xfd, 0x5b, 0x46, 0x5b, 0xef, 0xbb, 0x67, 0x5e, 0x7d, 0xc2, 0x7c, 0x2c, 0x6b, 0xe1, 0xcf, 0xe6, 0xc7, 0xe4, 0x7d, 0xeb, 0xca, 0x97, 0xb7, 0x4c, 0xd3, 0x4d, 0x6f, 0x08, 0x9f, 0x0d, 0x3a, 0xf1, 0xcb, 0x00, 0x79, 0x78, 0x2f, 0x05, 0x8e, 0xeb, 0x94, 0x48, 0x0d, 0x50, 0x64, 0x3b, 0xca, 0x70, 0xe2, 0x69, 0x38, 0x4f, 0xe4, 0xb0, 0x49, 0x0f, 0xc5, 0x4c, 0x7a, 0xa7 + }; + auto mac = hmac.process("Some bogus data"); + if (memcmp(result, mac.data, hmac.digest_size()) != 0) { + FAIL(Invalid mac); + print_buffer({ mac.data, hmac.digest_size() }, -1); + } else + PASS; + } + { + I_TEST((HMAC - SHA512 | Reuse)); + Crypto::Authentication::HMAC hmac("Well Hello Friends"); + + auto mac_0 = hmac.process("Some bogus data"); + auto mac_1 = hmac.process("Some bogus data"); + + if (memcmp(mac_0.data, mac_1.data, hmac.digest_size()) != 0) { + FAIL(Cannot reuse); + } else + PASS; + } +} + +static int rsa_tests() +{ + rsa_test_encrypt(); + rsa_test_der_parse(); + bigint_test_number_theory(); + rsa_test_encrypt_decrypt(); + rsa_emsa_pss_test_create(); + return g_some_test_failed ? 1 : 0; +} + +static void rsa_test_encrypt() +{ + { + I_TEST((RSA RAW | Encryption)); + ByteBuffer data { "hellohellohellohellohellohellohellohellohellohellohellohello123-"_b }; + u8 result[] { 0x6f, 0x7b, 0xe2, 0xd3, 0x95, 0xf8, 0x8d, 0x87, 0x6d, 0x10, 0x5e, 0xc3, 0xcd, 0xf7, 0xbb, 0xa6, 0x62, 0x8e, 0x45, 0xa0, 0xf1, 0xe5, 0x0f, 0xdf, 0x69, 0xcb, 0xb6, 0xd5, 0x42, 0x06, 0x7d, 0x72, 0xa9, 0x5e, 0xae, 0xbf, 0xbf, 0x0f, 0xe0, 0xeb, 0x31, 0x31, 0xca, 0x8a, 0x81, 0x1e, 0xb9, 0xec, 0x6d, 0xcc, 0xb8, 0xa4, 0xac, 0xa3, 0x31, 0x05, 0xa9, 0xac, 0xc9, 0xd3, 0xe6, 0x2a, 0x18, 0xfe }; + Crypto::PK::RSA rsa( + "8126832723025844890518845777858816391166654950553329127845898924164623511718747856014227624997335860970996746552094406240834082304784428582653994490504519"_bigint, + "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint, + "65537"_bigint); + u8 buffer[rsa.output_size()]; + auto buf = Bytes { buffer, sizeof(buffer) }; + rsa.encrypt(data, buf); + if (memcmp(result, buf.data(), buf.size())) { + FAIL(Invalid encryption result); + print_buffer(buf, 16); + } else { + PASS; + } + } + { + I_TEST((RSA PKCS #1 1.5 | Encryption)); + ByteBuffer data { "hellohellohellohellohellohellohellohellohello123-"_b }; + Crypto::PK::RSA_PKCS1_EME rsa( + "8126832723025844890518845777858816391166654950553329127845898924164623511718747856014227624997335860970996746552094406240834082304784428582653994490504519"_bigint, + "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint, + "65537"_bigint); + u8 buffer[rsa.output_size()]; + auto buf = Bytes { buffer, sizeof(buffer) }; + rsa.encrypt(data, buf); + rsa.decrypt(buf, buf); + + if (memcmp(buf.data(), "hellohellohellohellohellohellohellohellohello123-", 49)) + FAIL(Invalid encryption); + else { + dbg() << "out size " << buf.size() << " values: " << StringView { (char*)buf.data(), buf.size() }; + + PASS; + } + } +} + +static void bigint_test_number_theory() +{ + { + I_TEST((Number Theory | Modular Inverse)); + if (Crypto::NumberTheory::ModularInverse(7, 87) == 25) { + PASS; + } else { + FAIL(Invalid result); + } + } + { + struct { + Crypto::UnsignedBigInteger base; + Crypto::UnsignedBigInteger exp; + Crypto::UnsignedBigInteger mod; + Crypto::UnsignedBigInteger expected; + } mod_pow_tests[] = { + { "2988348162058574136915891421498819466320163312926952423791023078876139"_bigint, "2351399303373464486466122544523690094744975233415544072992656881240319"_bigint, "10000"_bigint, "3059"_bigint }, + { "24231"_bigint, "12448"_bigint, "14679"_bigint, "4428"_bigint }, + { "1005404"_bigint, "8352654"_bigint, "8161408"_bigint, "2605696"_bigint }, + { "3665005778"_bigint, "3244425589"_bigint, "565668506"_bigint, "524766494"_bigint }, + { "10662083169959689657"_bigint, "11605678468317533000"_bigint, "1896834583057209739"_bigint, "1292743154593945858"_bigint }, + { "99667739213529524852296932424683448520"_bigint, "123394910770101395416306279070921784207"_bigint, "238026722756504133786938677233768788719"_bigint, "197165477545023317459748215952393063201"_bigint }, + { "49368547511968178788919424448914214709244872098814465088945281575062739912239"_bigint, "25201856190991298572337188495596990852134236115562183449699512394891190792064"_bigint, "45950460777961491021589776911422805972195170308651734432277141467904883064645"_bigint, "39917885806532796066922509794537889114718612292469285403012781055544152450051"_bigint }, + { "48399385336454791246880286907257136254351739111892925951016159217090949616810"_bigint, "5758661760571644379364752528081901787573279669668889744323710906207949658569"_bigint, "32812120644405991429173950312949738783216437173380339653152625840449006970808"_bigint, "7948464125034399875323770213514649646309423451213282653637296324080400293584"_bigint }, + }; + + for (auto test_case : mod_pow_tests) { + I_TEST((Number Theory | Modular Power)); + auto actual = Crypto::NumberTheory::ModularPower( + test_case.base, test_case.exp, test_case.mod); + + if (actual == test_case.expected) { + PASS; + } else { + FAIL(Wrong result); + printf("b: %s\ne: %s\nm: %s\nexpect: %s\nactual: %s\n", + test_case.base.to_base10().characters(), test_case.exp.to_base10().characters(), test_case.mod.to_base10().characters(), test_case.expected.to_base10().characters(), actual.to_base10().characters()); + } + } + } + { + struct { + Crypto::UnsignedBigInteger candidate; + bool expected_result; + } primality_tests[] = { + { "1180591620717411303424"_bigint, false }, // 2**70 + { "620448401733239439360000"_bigint, false }, // 25! + { "953962166440690129601298432"_bigint, false }, // 12**25 + { "620448401733239439360000"_bigint, false }, // 25! + { "147926426347074375"_bigint, false }, // 35! / 2**32 + { "340282366920938429742726440690708343523"_bigint, false }, // 2 factors near 2^64 + { "73"_bigint, true }, + { "6967"_bigint, true }, + { "787649"_bigint, true }, + { "73513949"_bigint, true }, + { "6691236901"_bigint, true }, + { "741387182759"_bigint, true }, + { "67466615915827"_bigint, true }, + { "9554317039214687"_bigint, true }, + { "533344522150170391"_bigint, true }, + { "18446744073709551557"_bigint, true }, // just below 2**64 + }; + + for (auto test_case : primality_tests) { + I_TEST((Number Theory | Primality)); + bool actual_result = Crypto::NumberTheory::is_probably_prime(test_case.candidate); + if (test_case.expected_result == actual_result) { + PASS; + } else { + FAIL(Wrong primality guess); + printf("The number %s is %sa prime, but the test said it is %sa prime!\n", + test_case.candidate.to_base10().characters(), test_case.expected_result ? "" : "not ", actual_result ? "" : "not "); + } + } + } + { + struct { + Crypto::UnsignedBigInteger min; + Crypto::UnsignedBigInteger max; + } primality_tests[] = { + { "1"_bigint, "1000000"_bigint }, + { "10000000000"_bigint, "20000000000"_bigint }, + { "1000"_bigint, "200000000000000000"_bigint }, + { "200000000000000000"_bigint, "200000000000010000"_bigint }, + }; + + for (auto test_case : primality_tests) { + I_TEST((Number Theory | Random numbers)); + auto actual_result = Crypto::NumberTheory::random_number(test_case.min, test_case.max); + if (actual_result < test_case.min) { + FAIL(Too small); + printf("The generated number %s is smaller than the requested minimum %s. (max = %s)\n", actual_result.to_base10().characters(), test_case.min.to_base10().characters(), test_case.max.to_base10().characters()); + } else if (!(actual_result < test_case.max)) { + FAIL(Too large); + printf("The generated number %s is larger-or-equal to the requested maximum %s. (min = %s)\n", actual_result.to_base10().characters(), test_case.max.to_base10().characters(), test_case.min.to_base10().characters()); + } else { + PASS; + } + } + } + { + I_TEST((Number Theory | Random distribution)); + auto actual_result = Crypto::NumberTheory::random_number( + "1"_bigint, + "100000000000000000000000000000"_bigint); // 10**29 + if (actual_result < "100000000000000000000"_bigint) { // 10**20 + FAIL(Too small); + printf("The generated number %s is extremely small. This *can* happen by pure chance, but should happen only once in a billion times. So it's probably an error.\n", actual_result.to_base10().characters()); + } else if ("99999999900000000000000000000"_bigint < actual_result) { // 10**29 - 10**20 + FAIL(Too large); + printf("The generated number %s is extremely large. This *can* happen by pure chance, but should happen only once in a billion times. So it's probably an error.\n", actual_result.to_base10().characters()); + } else { + PASS; + } + } +} + +static void rsa_emsa_pss_test_create() +{ + { + // This is a template validity test + I_TEST((RSA EMSA_PSS | Construction)); + Crypto::PK::RSA rsa; + Crypto::PK::RSA_EMSA_PSS rsa_esma_pss(rsa); + PASS; + } +} + +static void rsa_test_der_parse() +{ + I_TEST((RSA | ASN1 DER / PEM encoded Key import)); + auto privkey = R"(-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAJsrIYHxs1YL9tpfodaWs1lJoMdF4kgFisUFSj6nvBhJUlmBh607AlgTaX0E +DGPYycXYGZ2n6rqmms5lpDXBpUcCAwEAAQJAUNpPkmtEHDENxsoQBUXvXDYeXdePSiIBJhpU +joNOYoR5R9z5oX2cpcyykQ58FC2vKKg+x8N6xczG7qO95tw5UQIhAN354CP/FA+uTeJ6KJ+i +zCBCl58CjNCzO0s5HTc56el5AiEAsvPKXo5/9gS/S4UzDRP6abq7GreixTfjR8LXidk3FL8C +IQCTjYI861Y+hjMnlORkGSdvWlTHUj6gjEOh4TlWeJzQoQIgAxMZOQKtxCZUuxFwzRq4xLRG +nrDlBQpuxz7bwSyQO7UCIHrYMnDohgNbwtA5ZpW3H1cKKQQvueWm6sxW9P5sUrZ3 +-----END RSA PRIVATE KEY-----)"; + + Crypto::PK::RSA rsa(privkey); + if (rsa.public_key().public_exponent() == 65537) { + if (rsa.private_key().private_exponent() == "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint) { + PASS; + } else + FAIL(Invalid private exponent); + } else { + FAIL(Invalid public exponent); + } +} + +static void rsa_test_encrypt_decrypt() +{ + I_TEST((RSA | Encrypt)); + dbgln(" creating rsa object"); + Crypto::PK::RSA rsa( + "9527497237087650398000977129550904920919162360737979403539302312977329868395261515707123424679295515888026193056908173564681660256268221509339074678416049"_bigint, + "39542231845947188736992321577701849924317746648774438832456325878966594812143638244746284968851807975097653255909707366086606867657273809465195392910913"_bigint, + "65537"_bigint); + dbg() << "Output size: " << rsa.output_size(); + + u8 enc_buffer[rsa.output_size()]; + u8 dec_buffer[rsa.output_size()]; + + auto enc = Bytes { enc_buffer, rsa.output_size() }; + auto dec = Bytes { dec_buffer, rsa.output_size() }; + + enc.overwrite(0, "WellHelloFriendsWellHelloFriendsWellHelloFriendsWellHelloFriends", 64); + + rsa.encrypt(enc, dec); + rsa.decrypt(dec, enc); + + dbg() << "enc size " << enc.size() << " dec size " << dec.size(); + + if (memcmp(enc.data(), "WellHelloFriendsWellHelloFriendsWellHelloFriendsWellHelloFriends", 64) != 0) { + FAIL(Could not encrypt then decrypt); + } else { + PASS; + } +} + +static int tls_tests() +{ + tls_test_client_hello(); + return g_some_test_failed ? 1 : 0; +} + +static void tls_test_client_hello() +{ + I_TEST((TLS | Connect and Data Transfer)); + Core::EventLoop loop; + RefPtr tls = TLS::TLSv12::construct(nullptr); + tls->set_root_certificates(s_root_ca_certificates); + bool sent_request = false; + ByteBuffer contents = ByteBuffer::create_uninitialized(0); + tls->on_tls_ready_to_write = [&](TLS::TLSv12& tls) { + if (sent_request) + return; + sent_request = true; + if (!tls.write("GET / HTTP/1.1\r\nHost: "_b)) { + FAIL(write(0) failed); + loop.quit(0); + } + auto* the_server = server ?: DEFAULT_SERVER; + if (!tls.write(StringView(the_server).bytes())) { + FAIL(write(1) failed); + loop.quit(0); + } + if (!tls.write("\r\nConnection : close\r\n\r\n"_b)) { + FAIL(write(2) failed); + loop.quit(0); + } + }; + tls->on_tls_ready_to_read = [&](TLS::TLSv12& tls) { + auto data = tls.read(); + if (!data.has_value()) { + FAIL(No data received); + loop.quit(1); + } else { + // print_buffer(data.value(), 16); + contents.append(data.value().data(), data.value().size()); + } + }; + tls->on_tls_finished = [&] { + PASS; + loop.quit(0); + }; + tls->on_tls_error = [&](TLS::AlertDescription) { + FAIL(Connection failure); + loop.quit(1); + }; + if (!tls->connect(server ?: DEFAULT_SERVER, port)) { + FAIL(connect() failed); + return; + } + loop.exec(); +} + +static int adler32_tests() +{ + auto do_test = [](ReadonlyBytes input, u32 expected_result) { + I_TEST((CRC32)); + + auto pass = Crypto::Checksum::Adler32(input).digest() == expected_result; + + if (pass) { + PASS; + } else { + FAIL(Incorrect Result); + } + }; + + do_test(String("").bytes(), 0x1); + do_test(String("a").bytes(), 0x00620062); + do_test(String("abc").bytes(), 0x024d0127); + do_test(String("message digest").bytes(), 0x29750586); + do_test(String("abcdefghijklmnopqrstuvwxyz").bytes(), 0x90860b20); + + return g_some_test_failed ? 1 : 0; +} + +static int crc32_tests() +{ + auto do_test = [](ReadonlyBytes input, u32 expected_result) { + I_TEST((Adler32)); + + auto pass = Crypto::Checksum::CRC32(input).digest() == expected_result; + + if (pass) { + PASS; + } else { + FAIL(Incorrect Result); + } + }; + + do_test(String("").bytes(), 0x0); + do_test(String("The quick brown fox jumps over the lazy dog").bytes(), 0x414FA339); + do_test(String("various CRC algorithms input data").bytes(), 0x9BD366AE); + + return g_some_test_failed ? 1 : 0; +} + +static int bigint_tests() +{ + bigint_test_fibo500(); + bigint_addition_edgecases(); + bigint_subtraction(); + bigint_multiplication(); + bigint_division(); + bigint_base10(); + bigint_import_export(); + bigint_bitwise(); + + bigint_test_signed_fibo500(); + bigint_signed_addition_edgecases(); + bigint_signed_subtraction(); + bigint_signed_multiplication(); + bigint_signed_division(); + bigint_signed_base10(); + bigint_signed_import_export(); + bigint_signed_bitwise(); + + return g_some_test_failed ? 1 : 0; +} + +static Crypto::UnsignedBigInteger bigint_fibonacci(size_t n) +{ + Crypto::UnsignedBigInteger num1(0); + Crypto::UnsignedBigInteger num2(1); + for (size_t i = 0; i < n; ++i) { + Crypto::UnsignedBigInteger t = num1.plus(num2); + num2 = num1; + num1 = t; + } + return num1; +} + +static Crypto::SignedBigInteger bigint_signed_fibonacci(size_t n) +{ + Crypto::SignedBigInteger num1(0); + Crypto::SignedBigInteger num2(1); + for (size_t i = 0; i < n; ++i) { + Crypto::SignedBigInteger t = num1.plus(num2); + num2 = num1; + num1 = t; + } + return num1; +} +static void bigint_test_fibo500() +{ + { + I_TEST((BigInteger | Fibonacci500)); + bool pass = (bigint_fibonacci(500).words() == AK::Vector { 315178285, 505575602, 1883328078, 125027121, 3649625763, 347570207, 74535262, 3832543808, 2472133297, 1600064941, 65273441 }); + + if (pass) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_addition_edgecases() +{ + { + I_TEST((BigInteger | Edge Cases)); + Crypto::UnsignedBigInteger num1; + Crypto::UnsignedBigInteger num2(70); + Crypto::UnsignedBigInteger num3 = num1.plus(num2); + bool pass = (num3 == num2); + pass &= (num1 == Crypto::UnsignedBigInteger(0)); + + if (pass) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Borrow with zero)); + Crypto::UnsignedBigInteger num1({ UINT32_MAX - 3, UINT32_MAX }); + Crypto::UnsignedBigInteger num2({ UINT32_MAX - 2, 0 }); + if (num1.plus(num2).words() == Vector { 4294967289, 0, 1 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_subtraction() +{ + { + I_TEST((BigInteger | Simple Subtraction 1)); + Crypto::UnsignedBigInteger num1(80); + Crypto::UnsignedBigInteger num2(70); + + if (num1.minus(num2) == Crypto::UnsignedBigInteger(10)) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Simple Subtraction 2)); + Crypto::UnsignedBigInteger num1(50); + Crypto::UnsignedBigInteger num2(70); + + if (num1.minus(num2).is_invalid()) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Subtraction with borrow)); + Crypto::UnsignedBigInteger num1(UINT32_MAX); + Crypto::UnsignedBigInteger num2(1); + Crypto::UnsignedBigInteger num3 = num1.plus(num2); + Crypto::UnsignedBigInteger result = num3.minus(num2); + if (result == num1) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Subtraction with large numbers)); + Crypto::UnsignedBigInteger num1 = bigint_fibonacci(343); + Crypto::UnsignedBigInteger num2 = bigint_fibonacci(218); + Crypto::UnsignedBigInteger result = num1.minus(num2); + if ((result.plus(num2) == num1) + && (result.words() == Vector { 811430588, 2958904896, 1130908877, 2830569969, 3243275482, 3047460725, 774025231, 7990 })) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Subtraction with large numbers 2)); + Crypto::UnsignedBigInteger num1(Vector { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 }); + Crypto::UnsignedBigInteger num2(Vector { 4196414175, 1117247942, 1123294122, 191895498, 3347106536, 16 }); + Crypto::UnsignedBigInteger result = num1.minus(num2); + // this test only verifies that we don't crash on an assertion + PASS; + } + { + I_TEST((BigInteger | Subtraction Regression 1)); + auto num = Crypto::UnsignedBigInteger { 1 }.shift_left(256); + if (num.minus(1).words() == Vector { 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 0 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_multiplication() +{ + { + I_TEST((BigInteger | Simple Multiplication)); + Crypto::UnsignedBigInteger num1(8); + Crypto::UnsignedBigInteger num2(251); + Crypto::UnsignedBigInteger result = num1.multiplied_by(num2); + if (result.words() == Vector { 2008 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Multiplications with big numbers 1)); + Crypto::UnsignedBigInteger num1 = bigint_fibonacci(200); + Crypto::UnsignedBigInteger num2(12345678); + Crypto::UnsignedBigInteger result = num1.multiplied_by(num2); + if (result.words() == Vector { 669961318, 143970113, 4028714974, 3164551305, 1589380278, 2 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Multiplications with big numbers 2)); + Crypto::UnsignedBigInteger num1 = bigint_fibonacci(200); + Crypto::UnsignedBigInteger num2 = bigint_fibonacci(341); + Crypto::UnsignedBigInteger result = num1.multiplied_by(num2); + if (result.words() == Vector { 3017415433, 2741793511, 1957755698, 3731653885, 3154681877, 785762127, 3200178098, 4260616581, 529754471, 3632684436, 1073347813, 2516430 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} +static void bigint_division() +{ + { + I_TEST((BigInteger | Simple Division)); + Crypto::UnsignedBigInteger num1(27194); + Crypto::UnsignedBigInteger num2(251); + auto result = num1.divided_by(num2); + Crypto::UnsignedDivisionResult expected = { Crypto::UnsignedBigInteger(108), Crypto::UnsignedBigInteger(86) }; + if (result.quotient == expected.quotient && result.remainder == expected.remainder) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Division with big numbers)); + Crypto::UnsignedBigInteger num1 = bigint_fibonacci(386); + Crypto::UnsignedBigInteger num2 = bigint_fibonacci(238); + auto result = num1.divided_by(num2); + Crypto::UnsignedDivisionResult expected = { + Crypto::UnsignedBigInteger(Vector { 2300984486, 2637503534, 2022805584, 107 }), + Crypto::UnsignedBigInteger(Vector { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 }) + }; + if (result.quotient == expected.quotient && result.remainder == expected.remainder) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | Combined test)); + auto num1 = bigint_fibonacci(497); + auto num2 = bigint_fibonacci(238); + auto div_result = num1.divided_by(num2); + if (div_result.quotient.multiplied_by(num2).plus(div_result.remainder) == num1) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_base10() +{ + { + I_TEST((BigInteger | From String)); + auto result = Crypto::UnsignedBigInteger::from_base10("57195071295721390579057195715793"); + if (result.words() == Vector { 3806301393, 954919431, 3879607298, 721 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((BigInteger | To String)); + auto result = Crypto::UnsignedBigInteger { Vector { 3806301393, 954919431, 3879607298, 721 } }.to_base10(); + if (result == "57195071295721390579057195715793") { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_import_export() +{ + { + I_TEST((BigInteger | BigEndian Decode / Encode roundtrip)); + u8 random_bytes[128]; + u8 target_buffer[128]; + AK::fill_with_random(random_bytes, 128); + auto encoded = Crypto::UnsignedBigInteger::import_data(random_bytes, 128); + encoded.export_data({ target_buffer, 128 }); + if (memcmp(target_buffer, random_bytes, 128) != 0) + FAIL(Could not roundtrip); + else + PASS; + } + { + I_TEST((BigInteger | BigEndian Encode / Decode roundtrip)); + u8 target_buffer[128]; + auto encoded = "12345678901234567890"_bigint; + auto size = encoded.export_data({ target_buffer, 128 }); + auto decoded = Crypto::UnsignedBigInteger::import_data(target_buffer, size); + if (encoded != decoded) + FAIL(Could not roundtrip); + else + PASS; + } + { + I_TEST((BigInteger | BigEndian Import)); + auto number = Crypto::UnsignedBigInteger::import_data("hello"); + if (number == "448378203247"_bigint) { + PASS; + } else { + FAIL(Invalid value); + } + } + { + I_TEST((BigInteger | BigEndian Export)); + auto number = "448378203247"_bigint; + char exported[8] { 0 }; + auto exported_length = number.export_data({ exported, 8 }, true); + if (exported_length == 5 && memcmp(exported + 3, "hello", 5) == 0) { + PASS; + } else { + FAIL(Invalid value); + print_buffer({ exported - exported_length + 8, exported_length }, -1); + } + } +} + +static void bigint_bitwise() +{ + { + I_TEST((BigInteger | Basic bitwise or)); + auto num1 = "1234567"_bigint; + auto num2 = "1234567"_bigint; + if (num1.bitwise_or(num2) == num1) { + PASS; + } else { + FAIL(Invalid value); + } + } + { + I_TEST((BigInteger | Bitwise or handles different lengths)); + auto num1 = "1234567"_bigint; + auto num2 = "123456789012345678901234567890"_bigint; + auto expected = "123456789012345678901234622167"_bigint; + auto result = num1.bitwise_or(num2); + if (result == expected) { + PASS; + } else { + FAIL(Invalid value); + } + } + { + I_TEST((BigInteger | Basic bitwise and)); + auto num1 = "1234567"_bigint; + auto num2 = "1234561"_bigint; + if (num1.bitwise_and(num2) == "1234561"_bigint) { + PASS; + } else { + FAIL(Invalid value); + } + } + { + I_TEST((BigInteger | Bitwise and handles different lengths)); + auto num1 = "1234567"_bigint; + auto num2 = "123456789012345678901234567890"_bigint; + if (num1.bitwise_and(num2) == "1180290"_bigint) { + PASS; + } else { + FAIL(Invalid value); + } + } + { + I_TEST((BigInteger | Basic bitwise xor)); + auto num1 = "1234567"_bigint; + auto num2 = "1234561"_bigint; + if (num1.bitwise_xor(num2) == 6) { + PASS; + } else { + FAIL(Invalid value); + } + } + { + I_TEST((BigInteger | Bitwise xor handles different lengths)); + auto num1 = "1234567"_bigint; + auto num2 = "123456789012345678901234567890"_bigint; + if (num1.bitwise_xor(num2) == "123456789012345678901233441877"_bigint) { + PASS; + } else { + FAIL(Invalid value); + } + } +} + +static void bigint_test_signed_fibo500() +{ + { + I_TEST((Signed BigInteger | Fibonacci500)); + bool pass = (bigint_signed_fibonacci(500).unsigned_value().words() == AK::Vector { 315178285, 505575602, 1883328078, 125027121, 3649625763, 347570207, 74535262, 3832543808, 2472133297, 1600064941, 65273441 }); + + if (pass) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_signed_addition_edgecases() +{ + { + I_TEST((Signed BigInteger | Borrow with zero)); + Crypto::SignedBigInteger num1 { Crypto::UnsignedBigInteger { { UINT32_MAX - 3, UINT32_MAX } }, false }; + Crypto::SignedBigInteger num2 { Crypto::UnsignedBigInteger { UINT32_MAX - 2 }, false }; + if (num1.plus(num2).unsigned_value().words() == Vector { 4294967289, 0, 1 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Addition to other sign)); + Crypto::SignedBigInteger num1 = INT32_MAX; + Crypto::SignedBigInteger num2 = num1; + num2.negate(); + if (num1.plus(num2) == Crypto::SignedBigInteger { 0 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_signed_subtraction() +{ + { + I_TEST((Signed BigInteger | Simple Subtraction 1)); + Crypto::SignedBigInteger num1(80); + Crypto::SignedBigInteger num2(70); + + if (num1.minus(num2) == Crypto::SignedBigInteger(10)) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Simple Subtraction 2)); + Crypto::SignedBigInteger num1(50); + Crypto::SignedBigInteger num2(70); + + if (num1.minus(num2) == Crypto::SignedBigInteger { -20 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Subtraction with borrow)); + Crypto::SignedBigInteger num1(Crypto::UnsignedBigInteger { UINT32_MAX }); + Crypto::SignedBigInteger num2(1); + Crypto::SignedBigInteger num3 = num1.plus(num2); + Crypto::SignedBigInteger result = num2.minus(num3); + num1.negate(); + if (result == num1) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Subtraction with large numbers)); + Crypto::SignedBigInteger num1 = bigint_signed_fibonacci(343); + Crypto::SignedBigInteger num2 = bigint_signed_fibonacci(218); + Crypto::SignedBigInteger result = num2.minus(num1); + auto expected = Crypto::UnsignedBigInteger { Vector { 811430588, 2958904896, 1130908877, 2830569969, 3243275482, 3047460725, 774025231, 7990 } }; + if ((result.plus(num1) == num2) + && (result.unsigned_value() == expected)) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Subtraction with large numbers 2)); + Crypto::SignedBigInteger num1(Crypto::UnsignedBigInteger { Vector { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 } }); + Crypto::SignedBigInteger num2(Crypto::UnsignedBigInteger { Vector { 4196414175, 1117247942, 1123294122, 191895498, 3347106536, 16 } }); + Crypto::SignedBigInteger result = num1.minus(num2); + // this test only verifies that we don't crash on an assertion + PASS; + } +} + +static void bigint_signed_multiplication() +{ + { + I_TEST((Signed BigInteger | Simple Multiplication)); + Crypto::SignedBigInteger num1(8); + Crypto::SignedBigInteger num2(-251); + Crypto::SignedBigInteger result = num1.multiplied_by(num2); + if (result == Crypto::SignedBigInteger { -2008 }) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Multiplications with big numbers 1)); + Crypto::SignedBigInteger num1 = bigint_signed_fibonacci(200); + Crypto::SignedBigInteger num2(-12345678); + Crypto::SignedBigInteger result = num1.multiplied_by(num2); + if (result.unsigned_value().words() == Vector { 669961318, 143970113, 4028714974, 3164551305, 1589380278, 2 } && result.is_negative()) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Multiplications with big numbers 2)); + Crypto::SignedBigInteger num1 = bigint_signed_fibonacci(200); + Crypto::SignedBigInteger num2 = bigint_signed_fibonacci(341); + num1.negate(); + Crypto::SignedBigInteger result = num1.multiplied_by(num2); + if (result.unsigned_value().words() == Vector { 3017415433, 2741793511, 1957755698, 3731653885, 3154681877, 785762127, 3200178098, 4260616581, 529754471, 3632684436, 1073347813, 2516430 } && result.is_negative()) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} +static void bigint_signed_division() +{ + { + I_TEST((Signed BigInteger | Simple Division)); + Crypto::SignedBigInteger num1(27194); + Crypto::SignedBigInteger num2(-251); + auto result = num1.divided_by(num2); + Crypto::SignedDivisionResult expected = { Crypto::SignedBigInteger(-108), Crypto::SignedBigInteger(86) }; + if (result.quotient == expected.quotient && result.remainder == expected.remainder) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Division with big numbers)); + Crypto::SignedBigInteger num1 = bigint_signed_fibonacci(386); + Crypto::SignedBigInteger num2 = bigint_signed_fibonacci(238); + num1.negate(); + auto result = num1.divided_by(num2); + Crypto::SignedDivisionResult expected = { + Crypto::SignedBigInteger(Crypto::UnsignedBigInteger { Vector { 2300984486, 2637503534, 2022805584, 107 } }, true), + Crypto::SignedBigInteger(Crypto::UnsignedBigInteger { Vector { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 } }, true) + }; + if (result.quotient == expected.quotient && result.remainder == expected.remainder) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | Combined test)); + auto num1 = bigint_signed_fibonacci(497); + auto num2 = bigint_signed_fibonacci(238); + num1.negate(); + auto div_result = num1.divided_by(num2); + if (div_result.quotient.multiplied_by(num2).plus(div_result.remainder) == num1) { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_signed_base10() +{ + { + I_TEST((Signed BigInteger | From String)); + auto result = Crypto::SignedBigInteger::from_base10("-57195071295721390579057195715793"); + if (result.unsigned_value().words() == Vector { 3806301393, 954919431, 3879607298, 721 } && result.is_negative()) { + PASS; + } else { + FAIL(Incorrect Result); + } + } + { + I_TEST((Signed BigInteger | To String)); + auto result = Crypto::SignedBigInteger { Crypto::UnsignedBigInteger { Vector { 3806301393, 954919431, 3879607298, 721 } }, true }.to_base10(); + if (result == "-57195071295721390579057195715793") { + PASS; + } else { + FAIL(Incorrect Result); + } + } +} + +static void bigint_signed_import_export() +{ + { + I_TEST((Signed BigInteger | BigEndian Decode / Encode roundtrip)); + u8 random_bytes[129]; + u8 target_buffer[129]; + random_bytes[0] = 1; + AK::fill_with_random(random_bytes + 1, 128); + auto encoded = Crypto::SignedBigInteger::import_data(random_bytes, 129); + encoded.export_data({ target_buffer, 129 }); + if (memcmp(target_buffer, random_bytes, 129) != 0) + FAIL(Could not roundtrip); + else + PASS; + } + { + I_TEST((Signed BigInteger | BigEndian Encode / Decode roundtrip)); + u8 target_buffer[128]; + auto encoded = "-12345678901234567890"_sbigint; + auto size = encoded.export_data({ target_buffer, 128 }); + auto decoded = Crypto::SignedBigInteger::import_data(target_buffer, size); + if (encoded != decoded) + FAIL(Could not roundtrip); + else + PASS; + } +} + +static void bigint_signed_bitwise() +{ + { + I_TEST((Signed BigInteger | Bitwise or handles sign)); + auto num1 = "-1234567"_sbigint; + auto num2 = "1234567"_sbigint; + if (num1.bitwise_or(num2) == num1) { + PASS; + } else { + FAIL(Invalid value); + } + } +} diff --git a/Userland/Utilities/test-gfx-font.cpp b/Userland/Utilities/test-gfx-font.cpp new file mode 100644 index 0000000000..e1bf939bb5 --- /dev/null +++ b/Userland/Utilities/test-gfx-font.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +static void test_fontdatabase_get_by_name() +{ + const char* name = "Liza 10 400"; + auto& font_database = Gfx::FontDatabase::the(); + assert(!font_database.get_by_name(name)->name().is_null()); +} + +static void test_fontdatabase_for_each_font() +{ + auto& font_database = Gfx::FontDatabase::the(); + font_database.for_each_font([&](const Gfx::Font& font) { + assert(!font.name().is_null()); + assert(!font.qualified_name().is_null()); + assert(!font.family().is_null()); + assert(font.glyph_count() > 0); + }); +} + +static void test_default_font() +{ + assert(!Gfx::FontDatabase::default_font().name().is_null()); +} + +static void test_default_fixed_width_font() +{ + assert(!Gfx::FontDatabase::default_font().name().is_null()); +} + +static void test_default_bold_fixed_width_font() +{ + assert(!Gfx::FontDatabase::default_font().name().is_null()); +} + +static void test_default_bold_font() +{ + assert(!Gfx::FontDatabase::default_font().name().is_null()); +} + +static void test_clone() +{ + u8 glyph_height = 1; + u8 glyph_width = 1; + auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); + + auto new_font = font->clone(); + assert(!new_font->name().is_null()); + assert(!new_font->qualified_name().is_null()); + assert(!new_font->family().is_null()); + assert(new_font->glyph_count() > 0); +} + +static void test_set_name() +{ + u8 glyph_height = 1; + u8 glyph_width = 1; + auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); + + const char* name = "my newly created font"; + font->set_name(name); + + assert(!font->qualified_name().is_null()); + assert(!font->qualified_name().contains(name)); +} + +static void test_set_type() +{ + u8 glyph_height = 1; + u8 glyph_width = 1; + auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); + + auto type = Gfx::FontTypes::Default; + font->set_type(type); + + assert(font->type() == type); +} + +static void test_width() +{ + u8 glyph_height = 1; + u8 glyph_width = 1; + auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); + + assert(font->width("A") == glyph_width); +} + +static void test_glyph_or_emoji_width() +{ + u8 glyph_height = 1; + u8 glyph_width = 1; + auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); + font->set_type(Gfx::FontTypes::Default); + + assert(font->glyph_or_emoji_width(0)); +} + +static void test_load_from_file() +{ + auto font = Gfx::BitmapFont::load_from_file("/res/fonts/PebbletonBold14.font"); + assert(!font->name().is_null()); +} + +static void test_write_to_file() +{ + u8 glyph_height = 1; + u8 glyph_width = 1; + auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); + + char path[] = "/tmp/new.font.XXXXXX"; + assert(mkstemp(path) != -1); + assert(font->write_to_file(path)); + unlink(path); +} + +int main(int, char**) +{ +#define RUNTEST(x) \ + { \ + printf("Running " #x " ...\n"); \ + x(); \ + printf("Success!\n"); \ + } + RUNTEST(test_fontdatabase_get_by_name); + RUNTEST(test_fontdatabase_for_each_font); + RUNTEST(test_default_font); + RUNTEST(test_default_fixed_width_font); + RUNTEST(test_default_bold_fixed_width_font); + RUNTEST(test_default_bold_font); + RUNTEST(test_clone); + RUNTEST(test_set_name); + RUNTEST(test_set_type); + RUNTEST(test_width); + RUNTEST(test_glyph_or_emoji_width); + RUNTEST(test_load_from_file); + RUNTEST(test_write_to_file); + printf("PASS\n"); + + return 0; +} diff --git a/Userland/Utilities/test-js.cpp b/Userland/Utilities/test-js.cpp new file mode 100644 index 0000000000..9245d06492 --- /dev/null +++ b/Userland/Utilities/test-js.cpp @@ -0,0 +1,765 @@ +/* + * Copyright (c) 2020, Matthew Olsson + * Copyright (c) 2020, Linus Groh + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TOP_LEVEL_TEST_NAME "__$$TOP_LEVEL$$__" + +RefPtr vm; + +static bool collect_on_every_allocation = false; +static String currently_running_test; + +enum class TestResult { + Pass, + Fail, + Skip, +}; + +struct JSTest { + String name; + TestResult result; + String details; +}; + +struct JSSuite { + String name; + // A failed test takes precedence over a skipped test, which both have + // precedence over a passed test + TestResult most_severe_test_result { TestResult::Pass }; + Vector tests {}; +}; + +struct ParserError { + JS::Parser::Error error; + String hint; +}; + +struct JSFileResult { + String name; + Optional error {}; + double time_taken { 0 }; + // A failed test takes precedence over a skipped test, which both have + // precedence over a passed test + TestResult most_severe_test_result { TestResult::Pass }; + Vector suites {}; + Vector logged_messages {}; +}; + +struct JSTestRunnerCounts { + int tests_failed { 0 }; + int tests_passed { 0 }; + int tests_skipped { 0 }; + int suites_failed { 0 }; + int suites_passed { 0 }; + int files_total { 0 }; +}; + +class TestRunnerGlobalObject : public JS::GlobalObject { +public: + TestRunnerGlobalObject(); + virtual ~TestRunnerGlobalObject() override; + + virtual void initialize() override; + +private: + virtual const char* class_name() const override { return "TestRunnerGlobalObject"; } + + JS_DECLARE_NATIVE_FUNCTION(is_strict_mode); + JS_DECLARE_NATIVE_FUNCTION(can_parse_source); +}; + +class TestRunner { +public: + static TestRunner* the() + { + return s_the; + } + + TestRunner(String test_root, bool print_times) + : m_test_root(move(test_root)) + , m_print_times(print_times) + { + ASSERT(!s_the); + s_the = this; + } + + void run(); + + const JSTestRunnerCounts& counts() const { return m_counts; } + +protected: + static TestRunner* s_the; + + virtual Vector get_test_paths() const; + virtual JSFileResult run_file_test(const String& test_path); + void print_file_result(const JSFileResult& file_result) const; + void print_test_results() const; + + String m_test_root; + bool m_print_times; + + double m_total_elapsed_time_in_ms { 0 }; + JSTestRunnerCounts m_counts; + + RefPtr m_test_program; +}; + +TestRunner* TestRunner::s_the = nullptr; + +TestRunnerGlobalObject::TestRunnerGlobalObject() +{ +} + +TestRunnerGlobalObject::~TestRunnerGlobalObject() +{ +} + +void TestRunnerGlobalObject::initialize() +{ + JS::GlobalObject::initialize(); + static FlyString global_property_name { "global" }; + static FlyString is_strict_mode_property_name { "isStrictMode" }; + static FlyString can_parse_source_property_name { "canParseSource" }; + define_property(global_property_name, this, JS::Attribute::Enumerable); + define_native_function(is_strict_mode_property_name, is_strict_mode); + define_native_function(can_parse_source_property_name, can_parse_source); +} + +JS_DEFINE_NATIVE_FUNCTION(TestRunnerGlobalObject::is_strict_mode) +{ + return JS::Value(vm.in_strict_mode()); +} + +JS_DEFINE_NATIVE_FUNCTION(TestRunnerGlobalObject::can_parse_source) +{ + auto source = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + auto parser = JS::Parser(JS::Lexer(source)); + parser.parse_program(); + return JS::Value(!parser.has_errors()); +} + +static void cleanup_and_exit() +{ + // Clear the taskbar progress. +#ifdef __serenity__ + warn("\033]9;-1;\033\\"); +#endif + exit(1); +} + +static void handle_sigabrt(int) +{ + dbgln("test-js: SIGABRT received, cleaning up."); + cleanup_and_exit(); +} + +static double get_time_in_ms() +{ + struct timeval tv1; + auto return_code = gettimeofday(&tv1, nullptr); + ASSERT(return_code >= 0); + return static_cast(tv1.tv_sec) * 1000.0 + static_cast(tv1.tv_usec) / 1000.0; +} + +template +static void iterate_directory_recursively(const String& directory_path, Callback callback) +{ + Core::DirIterator directory_iterator(directory_path, Core::DirIterator::Flags::SkipDots); + + while (directory_iterator.has_next()) { + auto file_path = String::formatted("{}/{}", directory_path, directory_iterator.next_path()); + if (Core::File::is_directory(file_path)) { + iterate_directory_recursively(file_path, callback); + } else { + callback(move(file_path)); + } + } +} + +Vector TestRunner::get_test_paths() const +{ + Vector paths; + iterate_directory_recursively(m_test_root, [&](const String& file_path) { + if (!file_path.ends_with("test-common.js")) + paths.append(file_path); + }); + quick_sort(paths); + return paths; +} + +void TestRunner::run() +{ + size_t progress_counter = 0; + auto test_paths = get_test_paths(); + for (auto& path : test_paths) { + ++progress_counter; + print_file_result(run_file_test(path)); +#ifdef __serenity__ + warn("\033]9;{};{};\033\\", progress_counter, test_paths.size()); +#endif + } + +#ifdef __serenity__ + warn("\033]9;-1;\033\\"); +#endif + + print_test_results(); +} + +static Result, ParserError> parse_file(const String& file_path) +{ + auto file = Core::File::construct(file_path); + auto result = file->open(Core::IODevice::ReadOnly); + if (!result) { + warnln("Failed to open the following file: \"{}\"", file_path); + cleanup_and_exit(); + } + + auto contents = file->read_all(); + String test_file_string(reinterpret_cast(contents.data()), contents.size()); + file->close(); + + auto parser = JS::Parser(JS::Lexer(test_file_string)); + auto program = parser.parse_program(); + + if (parser.has_errors()) { + auto error = parser.errors()[0]; + return Result, ParserError>(ParserError { error, error.source_location_hint(test_file_string) }); + } + + return Result, ParserError>(program); +} + +static Optional get_test_results(JS::Interpreter& interpreter) +{ + auto result = vm->get_variable("__TestResults__", interpreter.global_object()); + auto json_string = JS::JSONObject::stringify_impl(interpreter.global_object(), result, JS::js_undefined(), JS::js_undefined()); + + auto json = JsonValue::from_string(json_string); + if (!json.has_value()) + return {}; + + return json.value(); +} + +JSFileResult TestRunner::run_file_test(const String& test_path) +{ + currently_running_test = test_path; + + double start_time = get_time_in_ms(); + auto interpreter = JS::Interpreter::create(*vm); + + // FIXME: This is a hack while we're refactoring Interpreter/VM stuff. + JS::VM::InterpreterExecutionScope scope(*interpreter); + + interpreter->heap().set_should_collect_on_every_allocation(collect_on_every_allocation); + + if (!m_test_program) { + auto result = parse_file(String::formatted("{}/test-common.js", m_test_root)); + if (result.is_error()) { + warnln("Unable to parse test-common.js"); + warnln("{}", result.error().error.to_string()); + warnln("{}", result.error().hint); + cleanup_and_exit(); + } + m_test_program = result.value(); + } + + interpreter->run(interpreter->global_object(), *m_test_program); + + auto file_program = parse_file(test_path); + if (file_program.is_error()) + return { test_path, file_program.error() }; + interpreter->run(interpreter->global_object(), *file_program.value()); + + auto test_json = get_test_results(*interpreter); + if (!test_json.has_value()) { + warnln("Received malformed JSON from test \"{}\"", test_path); + cleanup_and_exit(); + } + + JSFileResult file_result { test_path.substring(m_test_root.length() + 1, test_path.length() - m_test_root.length() - 1) }; + + // Collect logged messages + auto& arr = interpreter->vm().get_variable("__UserOutput__", interpreter->global_object()).as_array(); + for (auto& entry : arr.indexed_properties()) { + auto message = entry.value_and_attributes(&interpreter->global_object()).value; + file_result.logged_messages.append(message.to_string_without_side_effects()); + } + + test_json.value().as_object().for_each_member([&](const String& suite_name, const JsonValue& suite_value) { + JSSuite suite { suite_name }; + + ASSERT(suite_value.is_object()); + + suite_value.as_object().for_each_member([&](const String& test_name, const JsonValue& test_value) { + JSTest test { test_name, TestResult::Fail, "" }; + + ASSERT(test_value.is_object()); + ASSERT(test_value.as_object().has("result")); + + auto result = test_value.as_object().get("result"); + ASSERT(result.is_string()); + auto result_string = result.as_string(); + if (result_string == "pass") { + test.result = TestResult::Pass; + m_counts.tests_passed++; + } else if (result_string == "fail") { + test.result = TestResult::Fail; + m_counts.tests_failed++; + suite.most_severe_test_result = TestResult::Fail; + ASSERT(test_value.as_object().has("details")); + auto details = test_value.as_object().get("details"); + ASSERT(result.is_string()); + test.details = details.as_string(); + } else { + test.result = TestResult::Skip; + if (suite.most_severe_test_result == TestResult::Pass) + suite.most_severe_test_result = TestResult::Skip; + m_counts.tests_skipped++; + } + + suite.tests.append(test); + }); + + if (suite.most_severe_test_result == TestResult::Fail) { + m_counts.suites_failed++; + file_result.most_severe_test_result = TestResult::Fail; + } else { + if (suite.most_severe_test_result == TestResult::Skip && file_result.most_severe_test_result == TestResult::Pass) + file_result.most_severe_test_result = TestResult::Skip; + m_counts.suites_passed++; + } + + file_result.suites.append(suite); + }); + + m_counts.files_total++; + + file_result.time_taken = get_time_in_ms() - start_time; + m_total_elapsed_time_in_ms += file_result.time_taken; + + return file_result; +} + +enum Modifier { + BG_RED, + BG_GREEN, + FG_RED, + FG_GREEN, + FG_ORANGE, + FG_GRAY, + FG_BLACK, + FG_BOLD, + ITALIC, + CLEAR, +}; + +static void print_modifiers(Vector modifiers) +{ + for (auto& modifier : modifiers) { + auto code = [&] { + switch (modifier) { + case BG_RED: + return "\033[48;2;255;0;102m"; + case BG_GREEN: + return "\033[48;2;102;255;0m"; + case FG_RED: + return "\033[38;2;255;0;102m"; + case FG_GREEN: + return "\033[38;2;102;255;0m"; + case FG_ORANGE: + return "\033[38;2;255;102;0m"; + case FG_GRAY: + return "\033[38;2;135;139;148m"; + case FG_BLACK: + return "\033[30m"; + case FG_BOLD: + return "\033[1m"; + case ITALIC: + return "\033[3m"; + case CLEAR: + return "\033[0m"; + } + ASSERT_NOT_REACHED(); + }(); + out("{}", code); + } +} + +void TestRunner::print_file_result(const JSFileResult& file_result) const +{ + if (file_result.most_severe_test_result == TestResult::Fail || file_result.error.has_value()) { + print_modifiers({ BG_RED, FG_BLACK, FG_BOLD }); + out(" FAIL "); + print_modifiers({ CLEAR }); + } else { + if (m_print_times || file_result.most_severe_test_result != TestResult::Pass) { + print_modifiers({ BG_GREEN, FG_BLACK, FG_BOLD }); + out(" PASS "); + print_modifiers({ CLEAR }); + } else { + return; + } + } + + out(" {}", file_result.name); + + if (m_print_times) { + print_modifiers({ CLEAR, ITALIC, FG_GRAY }); + if (file_result.time_taken < 1000) { + outln(" ({}ms)", static_cast(file_result.time_taken)); + } else { + outln(" ({:3}s)", file_result.time_taken / 1000.0); + } + print_modifiers({ CLEAR }); + } else { + outln(); + } + + if (!file_result.logged_messages.is_empty()) { + print_modifiers({ FG_GRAY, FG_BOLD }); +#ifdef __serenity__ + outln(" ℹ Console output:"); +#else + // This emoji has a second invisible byte after it. The one above does not + outln(" ℹ️ Console output:"); +#endif + print_modifiers({ CLEAR, FG_GRAY }); + for (auto& message : file_result.logged_messages) + outln(" {}", message); + } + + if (file_result.error.has_value()) { + auto test_error = file_result.error.value(); + + print_modifiers({ FG_RED }); +#ifdef __serenity__ + outln(" ❌ The file failed to parse"); +#else + // No invisible byte here, but the spacing still needs to be altered on the host + outln(" ❌ The file failed to parse"); +#endif + outln(); + print_modifiers({ FG_GRAY }); + for (auto& message : test_error.hint.split('\n', true)) { + outln(" {}", message); + } + print_modifiers({ FG_RED }); + outln(" {}", test_error.error.to_string()); + outln(); + return; + } + + if (file_result.most_severe_test_result != TestResult::Pass) { + for (auto& suite : file_result.suites) { + if (suite.most_severe_test_result == TestResult::Pass) + continue; + + bool failed = suite.most_severe_test_result == TestResult::Fail; + + print_modifiers({ FG_GRAY, FG_BOLD }); + + if (failed) { +#ifdef __serenity__ + out(" ❌ Suite: "); +#else + // No invisible byte here, but the spacing still needs to be altered on the host + out(" ❌ Suite: "); +#endif + } else { +#ifdef __serenity__ + out(" ⚠ Suite: "); +#else + // This emoji has a second invisible byte after it. The one above does not + out(" ⚠️ Suite: "); +#endif + } + + print_modifiers({ CLEAR, FG_GRAY }); + + if (suite.name == TOP_LEVEL_TEST_NAME) { + outln(""); + } else { + outln("{}", suite.name); + } + print_modifiers({ CLEAR }); + + for (auto& test : suite.tests) { + if (test.result == TestResult::Pass) + continue; + + print_modifiers({ FG_GRAY, FG_BOLD }); + out(" Test: "); + if (test.result == TestResult::Fail) { + print_modifiers({ CLEAR, FG_RED }); + outln("{} (failed):", test.name); + outln(" {}", test.details); + } else { + print_modifiers({ CLEAR, FG_ORANGE }); + outln("{} (skipped)", test.name); + } + print_modifiers({ CLEAR }); + } + } + } +} + +void TestRunner::print_test_results() const +{ + out("\nTest Suites: "); + if (m_counts.suites_failed) { + print_modifiers({ FG_RED }); + out("{} failed, ", m_counts.suites_failed); + print_modifiers({ CLEAR }); + } + if (m_counts.suites_passed) { + print_modifiers({ FG_GREEN }); + out("{} passed, ", m_counts.suites_passed); + print_modifiers({ CLEAR }); + } + outln("{} total", m_counts.suites_failed + m_counts.suites_passed); + + out("Tests: "); + if (m_counts.tests_failed) { + print_modifiers({ FG_RED }); + out("{} failed, ", m_counts.tests_failed); + print_modifiers({ CLEAR }); + } + if (m_counts.tests_skipped) { + print_modifiers({ FG_ORANGE }); + out("{} skipped, ", m_counts.tests_skipped); + print_modifiers({ CLEAR }); + } + if (m_counts.tests_passed) { + print_modifiers({ FG_GREEN }); + out("{} passed, ", m_counts.tests_passed); + print_modifiers({ CLEAR }); + } + outln("{} total", m_counts.tests_failed + m_counts.tests_skipped + m_counts.tests_passed); + + outln("Files: {} total", m_counts.files_total); + + out("Time: "); + if (m_total_elapsed_time_in_ms < 1000.0) { + outln("{}ms", static_cast(m_total_elapsed_time_in_ms)); + } else { + outln("{:>.3}s", m_total_elapsed_time_in_ms / 1000.0); + } + outln(); +} + +class Test262ParserTestRunner final : public TestRunner { +public: + using TestRunner::TestRunner; + +private: + virtual Vector get_test_paths() const override; + virtual JSFileResult run_file_test(const String& test_path) override; +}; + +Vector Test262ParserTestRunner::get_test_paths() const +{ + Vector paths; + iterate_directory_recursively(m_test_root, [&](const String& file_path) { + auto dirname = LexicalPath(file_path).dirname(); + if (dirname.ends_with("early") || dirname.ends_with("fail") || dirname.ends_with("pass") || dirname.ends_with("pass-explicit")) + paths.append(file_path); + }); + quick_sort(paths); + return paths; +} + +JSFileResult Test262ParserTestRunner::run_file_test(const String& test_path) +{ + currently_running_test = test_path; + + auto dirname = LexicalPath(test_path).dirname(); + bool expecting_file_to_parse; + if (dirname.ends_with("early") || dirname.ends_with("fail")) { + expecting_file_to_parse = false; + } else if (dirname.ends_with("pass") || dirname.ends_with("pass-explicit")) { + expecting_file_to_parse = true; + } else { + ASSERT_NOT_REACHED(); + } + + auto start_time = get_time_in_ms(); + String details = ""; + TestResult test_result; + if (test_path.ends_with(".module.js")) { + test_result = TestResult::Skip; + m_counts.tests_skipped++; + m_counts.suites_passed++; + } else { + auto parse_result = parse_file(test_path); + if (expecting_file_to_parse) { + if (!parse_result.is_error()) { + test_result = TestResult::Pass; + } else { + test_result = TestResult::Fail; + details = parse_result.error().error.to_string(); + } + } else { + if (parse_result.is_error()) { + test_result = TestResult::Pass; + } else { + test_result = TestResult::Fail; + details = "File was expected to produce a parser error but didn't"; + } + } + } + + // test262-parser-tests doesn't have "suites" and "tests" in the usual sense, it just has files + // and an expectation whether they should parse or not. We add one suite with one test nonetheless: + // + // - This makes interpreting skipped test easier as their file is shown as "PASS" + // - That way we can show additional information such as "file parsed but shouldn't have" or + // parser errors for files that should parse respectively + + JSTest test { expecting_file_to_parse ? "file should parse" : "file should not parse", test_result, details }; + JSSuite suite { "Parse file", test_result, { test } }; + JSFileResult file_result { + test_path.substring(m_test_root.length() + 1, test_path.length() - m_test_root.length() - 1), + {}, + get_time_in_ms() - start_time, + test_result, + { suite } + }; + + if (test_result == TestResult::Fail) { + m_counts.tests_failed++; + m_counts.suites_failed++; + } else { + m_counts.tests_passed++; + m_counts.suites_passed++; + } + m_counts.files_total++; + m_total_elapsed_time_in_ms += file_result.time_taken; + + return file_result; +} + +int main(int argc, char** argv) +{ + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_flags = SA_NOCLDWAIT; + act.sa_handler = handle_sigabrt; + int rc = sigaction(SIGABRT, &act, nullptr); + if (rc < 0) { + perror("sigaction"); + return 1; + } + +#ifdef SIGINFO + signal(SIGINFO, [](int) { + static char buffer[4096]; + auto& counts = TestRunner::the()->counts(); + int len = snprintf(buffer, sizeof(buffer), "Pass: %d, Fail: %d, Skip: %d\nCurrent test: %s\n", counts.tests_passed, counts.tests_failed, counts.tests_skipped, currently_running_test.characters()); + write(STDOUT_FILENO, buffer, len); + }); +#endif + + bool print_times = false; + bool test262_parser_tests = false; + const char* specified_test_root = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(print_times, "Show duration of each test", "show-time", 't'); + args_parser.add_option(collect_on_every_allocation, "Collect garbage after every allocation", "collect-often", 'g'); + args_parser.add_option(test262_parser_tests, "Run test262 parser tests", "test262-parser-tests", 0); + args_parser.add_positional_argument(specified_test_root, "Tests root directory", "path", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (test262_parser_tests) { + if (collect_on_every_allocation) { + warnln("--collect-often and --test262-parser-tests options must not be used together"); + return 1; + } + if (!specified_test_root) { + warnln("Test root is required with --test262-parser-tests"); + return 1; + } + } + + if (getenv("DISABLE_DBG_OUTPUT")) { + DebugLogStream::set_enabled(false); + } + + String test_root; + + if (specified_test_root) { + test_root = String { specified_test_root }; + } else { +#ifdef __serenity__ + test_root = "/home/anon/js-tests"; +#else + char* serenity_root = getenv("SERENITY_ROOT"); + if (!serenity_root) { + warnln("No test root given, test-js requires the SERENITY_ROOT environment variable to be set"); + return 1; + } + test_root = String::formatted("{}/Libraries/LibJS/Tests", serenity_root); +#endif + } + if (!Core::File::is_directory(test_root)) { + warnln("Test root is not a directory: {}", test_root); + return 1; + } + + vm = JS::VM::create(); + + if (test262_parser_tests) + Test262ParserTestRunner(test_root, print_times).run(); + else + TestRunner(test_root, print_times).run(); + + vm = nullptr; + + return TestRunner::the()->counts().tests_failed > 0 ? 1 : 0; +} diff --git a/Userland/Utilities/test-pthread.cpp b/Userland/Utilities/test-pthread.cpp new file mode 100644 index 0000000000..36914f6634 --- /dev/null +++ b/Userland/Utilities/test-pthread.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020, Sergey Bugaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +static void test_once() +{ + constexpr size_t threads_count = 10; + + static Vector v; + v.clear(); + pthread_once_t once = PTHREAD_ONCE_INIT; + NonnullRefPtrVector threads; + + for (size_t i = 0; i < threads_count; i++) { + threads.append(LibThread::Thread::construct([&] { + return pthread_once(&once, [] { + v.append(35); + sleep(1); + }); + })); + threads.last().start(); + } + for (auto& thread : threads) + [[maybe_unused]] auto res = thread.join(); + + ASSERT(v.size() == 1); +} + +int main() +{ + test_once(); + return 0; +} diff --git a/Userland/Utilities/test-unveil.cpp b/Userland/Utilities/test-unveil.cpp new file mode 100644 index 0000000000..df632517d1 --- /dev/null +++ b/Userland/Utilities/test-unveil.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + Vector paths_to_test; + const char* permissions = "r"; + bool should_sleep = false; + + Core::ArgsParser parser; + parser.add_option(permissions, "Apply these permissions going forward", "permissions", 'p', "unveil-permissions"); + parser.add_option(should_sleep, "Sleep after processing all arguments", "sleep", 's'); + parser.add_option(Core::ArgsParser::Option { + .requires_argument = true, + .help_string = "Add a path to the unveil list", + .long_name = "unveil", + .short_name = 'u', + .value_name = "path", + .accept_value = [&](auto* s) { + StringView path { s }; + if (path.is_empty()) + return false; + if (unveil(s, permissions) < 0) { + perror("unveil"); + return false; + } + return true; + } }); + parser.add_option(Core::ArgsParser::Option { + .requires_argument = false, + .help_string = "Lock the veil", + .long_name = "lock", + .short_name = 'l', + .accept_value = [&](auto*) { + if (unveil(nullptr, nullptr) < 0) { + perror("unveil(nullptr, nullptr)"); + return false; + } + return true; + } }); + parser.add_positional_argument(Core::ArgsParser::Arg { + .help_string = "Test a path against the veil", + .name = "path", + .min_values = 0, + .max_values = INT_MAX, + .accept_value = [&](auto* s) { + if (access(s, X_OK) == 0) + warnln("'{}' - ok", s); + else + warnln("'{}' - fail: {}", s, strerror(errno)); + return true; + } }); + + parser.parse(argc, argv); + if (should_sleep) + sleep(INT_MAX); + return 0; +} diff --git a/Userland/Utilities/test-web.cpp b/Userland/Utilities/test-web.cpp new file mode 100644 index 0000000000..e4172ddd03 --- /dev/null +++ b/Userland/Utilities/test-web.cpp @@ -0,0 +1,709 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TOP_LEVEL_TEST_NAME "__$$TOP_LEVEL$$__" + +enum class TestResult { + Pass, + Fail, + Skip, +}; + +struct JSTest { + String name; + TestResult result; + String details; +}; + +struct JSSuite { + String name; + // A failed test takes precedence over a skipped test, which both have + // precedence over a passed test + TestResult most_severe_test_result { TestResult::Pass }; + Vector tests {}; +}; + +struct ParserError { + JS::Parser::Error error; + String hint; +}; + +struct JSFileResult { + String name; + Optional error {}; + double time_taken { 0 }; + // A failed test takes precedence over a skipped test, which both have + // precedence over a passed test + TestResult most_severe_test_result { TestResult::Pass }; + Vector suites {}; + Vector logged_messages {}; +}; + +struct JSTestRunnerCounts { + int tests_failed { 0 }; + int tests_passed { 0 }; + int tests_skipped { 0 }; + int suites_failed { 0 }; + int suites_passed { 0 }; + int files_total { 0 }; +}; + +Function g_on_page_change; + +class TestRunnerObject final : public JS::Object { + JS_OBJECT(TestRunnerObject, JS::Object); + +public: + explicit TestRunnerObject(JS::GlobalObject&); + virtual void initialize(JS::GlobalObject&) override; + virtual ~TestRunnerObject() override; + +private: + JS_DECLARE_NATIVE_FUNCTION(change_page); +}; + +TestRunnerObject::TestRunnerObject(JS::GlobalObject& global_object) + : Object(*global_object.object_prototype()) +{ +} + +void TestRunnerObject::initialize(JS::GlobalObject& global_object) +{ + Object::initialize(global_object); + define_native_function("changePage", change_page, 1); +} + +TestRunnerObject::~TestRunnerObject() +{ +} + +JS_DEFINE_NATIVE_FUNCTION(TestRunnerObject::change_page) +{ + auto url = vm.argument(0).to_string(global_object); + if (vm.exception()) + return {}; + + if (g_on_page_change) + g_on_page_change(url); + + return JS::js_undefined(); +} + +class TestRunner { +public: + TestRunner(String web_test_root, String js_test_root, Web::InProcessWebView& page_view, bool print_times) + : m_web_test_root(move(web_test_root)) + , m_js_test_root(move(js_test_root)) + , m_print_times(print_times) + , m_page_view(page_view) + { + } + + void run(); + +private: + JSFileResult run_file_test(const String& test_path); + void print_file_result(const JSFileResult& file_result) const; + void print_test_results() const; + + String m_web_test_root; + String m_js_test_root; + bool m_print_times; + + double m_total_elapsed_time_in_ms { 0 }; + JSTestRunnerCounts m_counts; + + RefPtr m_page_view; + + RefPtr m_js_test_common; + RefPtr m_web_test_common; +}; + +static void cleanup_and_exit() +{ + // Clear the taskbar progress. +#ifdef __serenity__ + fprintf(stderr, "\033]9;-1;\033\\"); +#endif + exit(1); +} + +#if 0 +static void handle_sigabrt(int) +{ + dbgln("test-web: SIGABRT received, cleaning up."); + cleanup_and_exit(); +} +#endif + +static double get_time_in_ms() +{ + struct timeval tv1; + auto return_code = gettimeofday(&tv1, nullptr); + ASSERT(return_code >= 0); + return static_cast(tv1.tv_sec) * 1000.0 + static_cast(tv1.tv_usec) / 1000.0; +} + +template +void iterate_directory_recursively(const String& directory_path, Callback callback) +{ + Core::DirIterator directory_iterator(directory_path, Core::DirIterator::Flags::SkipDots); + + while (directory_iterator.has_next()) { + auto file_path = String::format("%s/%s", directory_path.characters(), directory_iterator.next_path().characters()); + if (Core::File::is_directory(file_path)) { + iterate_directory_recursively(file_path, callback); + } else { + callback(move(file_path)); + } + } +} + +static Vector get_test_paths(const String& test_root) +{ + Vector paths; + + iterate_directory_recursively(test_root, [&](const String& file_path) { + if (!file_path.ends_with("test-common.js") && !file_path.ends_with(".html") && !file_path.ends_with(".ts")) + paths.append(file_path); + }); + + quick_sort(paths); + + return paths; +} + +void TestRunner::run() +{ + size_t progress_counter = 0; + auto test_paths = get_test_paths(m_web_test_root); + + g_on_page_change = [this](auto& page_to_load) { + if (!page_to_load.is_valid()) { + printf("Invalid page URL (%s) on page change", page_to_load.to_string().characters()); + cleanup_and_exit(); + } + + ASSERT(m_page_view->document()); + + // We want to keep the same document since the interpreter is tied to the document, + // and we don't want to lose the test state. So, we just clear the document and + // give a new parser the existing document to work on. + m_page_view->document()->remove_all_children(); + + Web::ResourceLoader::the().load_sync( + page_to_load, + [&](auto data, auto&) { + Web::HTML::HTMLDocumentParser parser(*m_page_view->document(), data, "utf-8"); + parser.run(page_to_load); + }, + [page_to_load](auto error) { + printf("Failed to load test page: %s (%s)", page_to_load.to_string().characters(), error.characters()); + cleanup_and_exit(); + }); + }; + + for (auto& path : test_paths) { + ++progress_counter; + print_file_result(run_file_test(path)); +#ifdef __serenity__ + fprintf(stderr, "\033]9;%zu;%zu;\033\\", progress_counter, test_paths.size()); +#endif + } + +#ifdef __serenity__ + fprintf(stderr, "\033]9;-1;\033\\"); +#endif + + print_test_results(); +} + +static Result, ParserError> parse_file(const String& file_path) +{ + auto file = Core::File::construct(file_path); + auto result = file->open(Core::IODevice::ReadOnly); + if (!result) { + printf("Failed to open the following file: \"%s\"\n", file_path.characters()); + cleanup_and_exit(); + } + + auto contents = file->read_all(); + String test_file_string(reinterpret_cast(contents.data()), contents.size()); + file->close(); + + auto parser = JS::Parser(JS::Lexer(test_file_string)); + auto program = parser.parse_program(); + + if (parser.has_errors()) { + auto error = parser.errors()[0]; + return Result, ParserError>(ParserError { error, error.source_location_hint(test_file_string) }); + } + + return Result, ParserError>(program); +} + +static Optional get_test_results(JS::Interpreter& interpreter) +{ + auto result = interpreter.vm().get_variable("__TestResults__", interpreter.global_object()); + auto json_string = JS::JSONObject::stringify_impl(interpreter.global_object(), result, JS::js_undefined(), JS::js_undefined()); + + auto json = JsonValue::from_string(json_string); + if (!json.has_value()) + return {}; + + return json.value(); +} + +JSFileResult TestRunner::run_file_test(const String& test_path) +{ + double start_time = get_time_in_ms(); + ASSERT(m_page_view->document()); + auto& old_interpreter = m_page_view->document()->interpreter(); + + // FIXME: This is a hack while we're refactoring Interpreter/VM stuff. + JS::VM::InterpreterExecutionScope scope(old_interpreter); + + if (!m_js_test_common) { + auto result = parse_file(String::format("%s/test-common.js", m_js_test_root.characters())); + if (result.is_error()) { + printf("Unable to parse %s/test-common.js\n", m_js_test_root.characters()); + printf("%s\n", result.error().error.to_string().characters()); + printf("%s\n", result.error().hint.characters()); + cleanup_and_exit(); + } + m_js_test_common = result.value(); + } + + if (!m_web_test_common) { + auto result = parse_file(String::format("%s/test-common.js", m_web_test_root.characters())); + if (result.is_error()) { + printf("Unable to parse %s/test-common.js\n", m_web_test_root.characters()); + printf("%s\n", result.error().error.to_string().characters()); + printf("%s\n", result.error().hint.characters()); + cleanup_and_exit(); + } + m_web_test_common = result.value(); + } + + auto file_program = parse_file(test_path); + if (file_program.is_error()) + return { test_path, file_program.error() }; + + // Setup the test on the current page to get "__PageToLoad__". + old_interpreter.run(old_interpreter.global_object(), *m_web_test_common); + old_interpreter.run(old_interpreter.global_object(), *file_program.value()); + auto page_to_load = URL(old_interpreter.vm().get_variable("__PageToLoad__", old_interpreter.global_object()).as_string().string()); + if (!page_to_load.is_valid()) { + printf("Invalid page URL for %s", test_path.characters()); + cleanup_and_exit(); + } + + JSFileResult file_result; + + Web::ResourceLoader::the().load_sync( + page_to_load, + [&](auto data, auto&) { + // Create a new parser and immediately get its document to replace the old interpreter. + auto document = Web::DOM::Document::create(); + Web::HTML::HTMLDocumentParser parser(document, data, "utf-8"); + auto& new_interpreter = parser.document().interpreter(); + + // Setup the test environment and call "__BeforeInitialPageLoad__" + new_interpreter.global_object().define_property( + "libweb_tester", + new_interpreter.heap().allocate(new_interpreter.global_object(), new_interpreter.global_object()), + JS::Attribute::Enumerable | JS::Attribute::Configurable); + new_interpreter.run(new_interpreter.global_object(), *m_js_test_common); + new_interpreter.run(new_interpreter.global_object(), *m_web_test_common); + new_interpreter.run(new_interpreter.global_object(), *file_program.value()); + + auto& before_initial_page_load = new_interpreter.vm().get_variable("__BeforeInitialPageLoad__", new_interpreter.global_object()).as_function(); + [[maybe_unused]] auto rc_before = new_interpreter.vm().call(before_initial_page_load, JS::js_undefined()); + if (new_interpreter.exception()) + new_interpreter.vm().clear_exception(); + + // Now parse the HTML page. + parser.run(page_to_load); + m_page_view->set_document(&parser.document()); + + // Finally run the test by calling "__AfterInitialPageLoad__" + auto& after_initial_page_load = new_interpreter.vm().get_variable("__AfterInitialPageLoad__", new_interpreter.global_object()).as_function(); + [[maybe_unused]] auto rc_after = new_interpreter.vm().call(after_initial_page_load, JS::js_undefined()); + if (new_interpreter.exception()) + new_interpreter.vm().clear_exception(); + + auto test_json = get_test_results(new_interpreter); + if (!test_json.has_value()) { + printf("Received malformed JSON from test \"%s\"\n", test_path.characters()); + cleanup_and_exit(); + } + + file_result = { test_path.substring(m_web_test_root.length() + 1, test_path.length() - m_web_test_root.length() - 1) }; + + // Collect logged messages + auto& arr = new_interpreter.vm().get_variable("__UserOutput__", new_interpreter.global_object()).as_array(); + for (auto& entry : arr.indexed_properties()) { + auto message = entry.value_and_attributes(&new_interpreter.global_object()).value; + file_result.logged_messages.append(message.to_string_without_side_effects()); + } + + test_json.value().as_object().for_each_member([&](const String& suite_name, const JsonValue& suite_value) { + JSSuite suite { suite_name }; + + ASSERT(suite_value.is_object()); + + suite_value.as_object().for_each_member([&](const String& test_name, const JsonValue& test_value) { + JSTest test { test_name, TestResult::Fail, "" }; + + ASSERT(test_value.is_object()); + ASSERT(test_value.as_object().has("result")); + + auto result = test_value.as_object().get("result"); + ASSERT(result.is_string()); + auto result_string = result.as_string(); + if (result_string == "pass") { + test.result = TestResult::Pass; + m_counts.tests_passed++; + } else if (result_string == "fail") { + test.result = TestResult::Fail; + m_counts.tests_failed++; + suite.most_severe_test_result = TestResult::Fail; + ASSERT(test_value.as_object().has("details")); + auto details = test_value.as_object().get("details"); + ASSERT(result.is_string()); + test.details = details.as_string(); + } else { + test.result = TestResult::Skip; + if (suite.most_severe_test_result == TestResult::Pass) + suite.most_severe_test_result = TestResult::Skip; + m_counts.tests_skipped++; + } + + suite.tests.append(test); + }); + + if (suite.most_severe_test_result == TestResult::Fail) { + m_counts.suites_failed++; + file_result.most_severe_test_result = TestResult::Fail; + } else { + if (suite.most_severe_test_result == TestResult::Skip && file_result.most_severe_test_result == TestResult::Pass) + file_result.most_severe_test_result = TestResult::Skip; + m_counts.suites_passed++; + } + + file_result.suites.append(suite); + }); + + m_counts.files_total++; + + file_result.time_taken = get_time_in_ms() - start_time; + m_total_elapsed_time_in_ms += file_result.time_taken; + }, + [page_to_load](auto error) { + printf("Failed to load test page: %s (%s)", page_to_load.to_string().characters(), error.characters()); + cleanup_and_exit(); + }); + + return file_result; +} + +enum Modifier { + BG_RED, + BG_GREEN, + FG_RED, + FG_GREEN, + FG_ORANGE, + FG_GRAY, + FG_BLACK, + FG_BOLD, + ITALIC, + CLEAR, +}; + +static void print_modifiers(Vector modifiers) +{ + for (auto& modifier : modifiers) { + auto code = [&]() -> String { + switch (modifier) { + case BG_RED: + return "\033[48;2;255;0;102m"; + case BG_GREEN: + return "\033[48;2;102;255;0m"; + case FG_RED: + return "\033[38;2;255;0;102m"; + case FG_GREEN: + return "\033[38;2;102;255;0m"; + case FG_ORANGE: + return "\033[38;2;255;102;0m"; + case FG_GRAY: + return "\033[38;2;135;139;148m"; + case FG_BLACK: + return "\033[30m"; + case FG_BOLD: + return "\033[1m"; + case ITALIC: + return "\033[3m"; + case CLEAR: + return "\033[0m"; + } + ASSERT_NOT_REACHED(); + }; + printf("%s", code().characters()); + } +} + +void TestRunner::print_file_result(const JSFileResult& file_result) const +{ + if (file_result.most_severe_test_result == TestResult::Fail || file_result.error.has_value()) { + print_modifiers({ BG_RED, FG_BLACK, FG_BOLD }); + printf(" FAIL "); + print_modifiers({ CLEAR }); + } else { + if (m_print_times || file_result.most_severe_test_result != TestResult::Pass) { + print_modifiers({ BG_GREEN, FG_BLACK, FG_BOLD }); + printf(" PASS "); + print_modifiers({ CLEAR }); + } else { + return; + } + } + + printf(" %s", file_result.name.characters()); + + if (m_print_times) { + print_modifiers({ CLEAR, ITALIC, FG_GRAY }); + if (file_result.time_taken < 1000) { + printf(" (%dms)\n", static_cast(file_result.time_taken)); + } else { + printf(" (%.3fs)\n", file_result.time_taken / 1000.0); + } + print_modifiers({ CLEAR }); + } else { + printf("\n"); + } + + if (!file_result.logged_messages.is_empty()) { + print_modifiers({ FG_GRAY, FG_BOLD }); +#ifdef __serenity__ + printf(" ℹ Console output:\n"); +#else + // This emoji has a second invisible byte after it. The one above does not + printf(" ℹ️ Console output:\n"); +#endif + print_modifiers({ CLEAR, FG_GRAY }); + for (auto& message : file_result.logged_messages) + printf(" %s\n", message.characters()); + } + + if (file_result.error.has_value()) { + auto test_error = file_result.error.value(); + + print_modifiers({ FG_RED }); +#ifdef __serenity__ + printf(" ❌ The file failed to parse\n\n"); +#else + // No invisible byte here, but the spacing still needs to be altered on the host + printf(" ❌ The file failed to parse\n\n"); +#endif + print_modifiers({ FG_GRAY }); + for (auto& message : test_error.hint.split('\n', true)) { + printf(" %s\n", message.characters()); + } + print_modifiers({ FG_RED }); + printf(" %s\n\n", test_error.error.to_string().characters()); + + return; + } + + if (file_result.most_severe_test_result != TestResult::Pass) { + for (auto& suite : file_result.suites) { + if (suite.most_severe_test_result == TestResult::Pass) + continue; + + bool failed = suite.most_severe_test_result == TestResult::Fail; + + print_modifiers({ FG_GRAY, FG_BOLD }); + + if (failed) { +#ifdef __serenity__ + printf(" ❌ Suite: "); +#else + // No invisible byte here, but the spacing still needs to be altered on the host + printf(" ❌ Suite: "); +#endif + } else { +#ifdef __serenity__ + printf(" ⚠ Suite: "); +#else + // This emoji has a second invisible byte after it. The one above does not + printf(" ⚠️ Suite: "); +#endif + } + + print_modifiers({ CLEAR, FG_GRAY }); + + if (suite.name == TOP_LEVEL_TEST_NAME) { + printf("\n"); + } else { + printf("%s\n", suite.name.characters()); + } + print_modifiers({ CLEAR }); + + for (auto& test : suite.tests) { + if (test.result == TestResult::Pass) + continue; + + print_modifiers({ FG_GRAY, FG_BOLD }); + printf(" Test: "); + if (test.result == TestResult::Fail) { + print_modifiers({ CLEAR, FG_RED }); + printf("%s (failed):\n", test.name.characters()); + printf(" %s\n", test.details.characters()); + } else { + print_modifiers({ CLEAR, FG_ORANGE }); + printf("%s (skipped)\n", test.name.characters()); + } + print_modifiers({ CLEAR }); + } + } + } +} + +void TestRunner::print_test_results() const +{ + printf("\nTest Suites: "); + if (m_counts.suites_failed) { + print_modifiers({ FG_RED }); + printf("%d failed, ", m_counts.suites_failed); + print_modifiers({ CLEAR }); + } + if (m_counts.suites_passed) { + print_modifiers({ FG_GREEN }); + printf("%d passed, ", m_counts.suites_passed); + print_modifiers({ CLEAR }); + } + printf("%d total\n", m_counts.suites_failed + m_counts.suites_passed); + + printf("Tests: "); + if (m_counts.tests_failed) { + print_modifiers({ FG_RED }); + printf("%d failed, ", m_counts.tests_failed); + print_modifiers({ CLEAR }); + } + if (m_counts.tests_skipped) { + print_modifiers({ FG_ORANGE }); + printf("%d skipped, ", m_counts.tests_skipped); + print_modifiers({ CLEAR }); + } + if (m_counts.tests_passed) { + print_modifiers({ FG_GREEN }); + printf("%d passed, ", m_counts.tests_passed); + print_modifiers({ CLEAR }); + } + printf("%d total\n", m_counts.tests_failed + m_counts.tests_passed); + + printf("Files: %d total\n", m_counts.files_total); + + printf("Time: "); + if (m_total_elapsed_time_in_ms < 1000.0) { + printf("%dms\n\n", static_cast(m_total_elapsed_time_in_ms)); + } else { + printf("%-.3fs\n\n", m_total_elapsed_time_in_ms / 1000.0); + } +} + +int main(int argc, char** argv) +{ + bool print_times = false; + bool show_window = false; + +#if 0 + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_flags = SA_NOCLDWAIT; + act.sa_handler = handle_sigabrt; + int rc = sigaction(SIGABRT, &act, nullptr); + if (rc < 0) { + perror("sigaction"); + return 1; + } +#endif + + Core::ArgsParser args_parser; + args_parser.add_option(print_times, "Show duration of each test", "show-time", 't'); + args_parser.add_option(show_window, "Show window while running tests", "window", 'w'); + args_parser.parse(argc, argv); + + auto app = GUI::Application::construct(argc, argv); + auto window = GUI::Window::construct(); + auto& main_widget = window->set_main_widget(); + main_widget.set_fill_with_background_color(true); + main_widget.set_layout(); + auto& view = main_widget.add(); + + view.set_document(Web::DOM::Document::create()); + + if (show_window) { + window->set_title("LibWeb Test Window"); + window->resize(640, 480); + window->show(); + } + +#ifdef __serenity__ + TestRunner("/home/anon/web-tests", "/home/anon/js-tests", view, print_times).run(); +#else + char* serenity_root = getenv("SERENITY_ROOT"); + if (!serenity_root) { + printf("test-web requires the SERENITY_ROOT environment variable to be set"); + return 1; + } + TestRunner(String::format("%s/Libraries/LibWeb/Tests", serenity_root), String::format("%s/Libraries/LibJS/Tests", serenity_root), view, print_times).run(); +#endif + return 0; +} diff --git a/Userland/Utilities/test.cpp b/Userland/Utilities/test.cpp new file mode 100644 index 0000000000..a81c84933c --- /dev/null +++ b/Userland/Utilities/test.cpp @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +bool g_there_was_an_error = false; + +[[noreturn]] static void fatal_error(const char* format, ...) +{ + fputs("\033[31m", stderr); + + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + + fputs("\033[0m\n", stderr); + exit(126); +} + +class Condition { +public: + virtual ~Condition() { } + virtual bool check() const = 0; +}; + +class And : public Condition { +public: + And(NonnullOwnPtr lhs, NonnullOwnPtr rhs) + : m_lhs(move(lhs)) + , m_rhs(move(rhs)) + { + } + +private: + virtual bool check() const override + { + return m_lhs->check() && m_rhs->check(); + } + + NonnullOwnPtr m_lhs; + NonnullOwnPtr m_rhs; +}; + +class Or : public Condition { +public: + Or(NonnullOwnPtr lhs, NonnullOwnPtr rhs) + : m_lhs(move(lhs)) + , m_rhs(move(rhs)) + { + } + +private: + virtual bool check() const override + { + return m_lhs->check() || m_rhs->check(); + } + + NonnullOwnPtr m_lhs; + NonnullOwnPtr m_rhs; +}; + +class Not : public Condition { +public: + Not(NonnullOwnPtr cond) + : m_cond(move(cond)) + { + } + +private: + virtual bool check() const override + { + return !m_cond->check(); + } + + NonnullOwnPtr m_cond; +}; + +class FileIsOfKind : public Condition { +public: + enum Kind { + BlockDevice, + CharacterDevice, + Directory, + FIFO, + Regular, + Socket, + SymbolicLink, + }; + FileIsOfKind(StringView path, Kind kind) + : m_path(path) + , m_kind(kind) + { + } + +private: + virtual bool check() const override + { + struct stat statbuf; + int rc; + + if (m_kind == SymbolicLink) + rc = stat(m_path.characters(), &statbuf); + else + rc = lstat(m_path.characters(), &statbuf); + + if (rc < 0) { + if (errno != ENOENT) { + perror(m_path.characters()); + g_there_was_an_error = true; + } + return false; + } + + switch (m_kind) { + case BlockDevice: + return S_ISBLK(statbuf.st_mode); + case CharacterDevice: + return S_ISCHR(statbuf.st_mode); + case Directory: + return S_ISDIR(statbuf.st_mode); + case FIFO: + return S_ISFIFO(statbuf.st_mode); + case Regular: + return S_ISREG(statbuf.st_mode); + case Socket: + return S_ISSOCK(statbuf.st_mode); + case SymbolicLink: + return S_ISLNK(statbuf.st_mode); + default: + ASSERT_NOT_REACHED(); + } + } + + String m_path; + Kind m_kind { Regular }; +}; + +class UserHasPermission : public Condition { +public: + enum Permission { + Any, + Read, + Write, + Execute, + }; + UserHasPermission(StringView path, Permission kind) + : m_path(path) + , m_kind(kind) + { + } + +private: + virtual bool check() const override + { + switch (m_kind) { + case Read: + return access(m_path.characters(), R_OK) == 0; + case Write: + return access(m_path.characters(), W_OK) == 0; + case Execute: + return access(m_path.characters(), X_OK) == 0; + case Any: + return access(m_path.characters(), F_OK) == 0; + default: + ASSERT_NOT_REACHED(); + } + } + + String m_path; + Permission m_kind { Read }; +}; + +class StringCompare : public Condition { +public: + enum Mode { + Equal, + NotEqual, + }; + + StringCompare(StringView lhs, StringView rhs, Mode mode) + : m_lhs(move(lhs)) + , m_rhs(move(rhs)) + , m_mode(mode) + { + } + +private: + virtual bool check() const override + { + if (m_mode == Equal) + return m_lhs == m_rhs; + return m_lhs != m_rhs; + } + + StringView m_lhs; + StringView m_rhs; + Mode m_mode { Equal }; +}; + +class NumericCompare : public Condition { +public: + enum Mode { + Equal, + Greater, + GreaterOrEqual, + Less, + LessOrEqual, + NotEqual, + }; + + NumericCompare(String lhs, String rhs, Mode mode) + : m_mode(mode) + { + auto lhs_option = lhs.trim_whitespace().to_int(); + auto rhs_option = rhs.trim_whitespace().to_int(); + + if (!lhs_option.has_value()) + fatal_error("expected integer expression: '%s'", lhs.characters()); + + if (!rhs_option.has_value()) + fatal_error("expected integer expression: '%s'", rhs.characters()); + + m_lhs = lhs_option.value(); + m_rhs = rhs_option.value(); + } + +private: + virtual bool check() const override + { + switch (m_mode) { + case Equal: + return m_lhs == m_rhs; + case Greater: + return m_lhs > m_rhs; + case GreaterOrEqual: + return m_lhs >= m_rhs; + case Less: + return m_lhs < m_rhs; + case LessOrEqual: + return m_lhs <= m_rhs; + case NotEqual: + return m_lhs != m_rhs; + default: + ASSERT_NOT_REACHED(); + } + } + + int m_lhs { 0 }; + int m_rhs { 0 }; + Mode m_mode { Equal }; +}; + +class FileCompare : public Condition { +public: + enum Mode { + Same, + ModificationTimestampGreater, + ModificationTimestampLess, + }; + + FileCompare(String lhs, String rhs, Mode mode) + : m_lhs(move(lhs)) + , m_rhs(move(rhs)) + , m_mode(mode) + { + } + +private: + virtual bool check() const override + { + struct stat statbuf_l; + int rc = stat(m_lhs.characters(), &statbuf_l); + + if (rc < 0) { + perror(m_lhs.characters()); + g_there_was_an_error = true; + return false; + } + + struct stat statbuf_r; + rc = stat(m_rhs.characters(), &statbuf_r); + + if (rc < 0) { + perror(m_rhs.characters()); + g_there_was_an_error = true; + return false; + } + + switch (m_mode) { + case Same: + return statbuf_l.st_dev == statbuf_r.st_dev && statbuf_l.st_ino == statbuf_r.st_ino; + case ModificationTimestampLess: + return statbuf_l.st_mtime < statbuf_r.st_mtime; + case ModificationTimestampGreater: + return statbuf_l.st_mtime > statbuf_r.st_mtime; + default: + ASSERT_NOT_REACHED(); + } + } + + String m_lhs; + String m_rhs; + Mode m_mode { Same }; +}; + +static OwnPtr parse_complex_expression(char* argv[]); + +static bool should_treat_expression_as_single_string(const StringView& arg_after) +{ + return arg_after.is_null() || arg_after == "-a" || arg_after == "-o"; +} + +static OwnPtr parse_simple_expression(char* argv[]) +{ + StringView arg = argv[optind]; + if (arg.is_null()) { + return {}; + } + + if (arg == "(") { + optind++; + auto command = parse_complex_expression(argv); + if (command && argv[optind] && StringView(argv[++optind]) == ")") + return command; + fatal_error("Unmatched \033[1m("); + } + + if (arg == "!") { + if (should_treat_expression_as_single_string(argv[optind])) + return make(move(arg), "", StringCompare::NotEqual); + auto command = parse_complex_expression(argv); + if (!command) + fatal_error("Expected an expression after \033[1m!"); + return make(command.release_nonnull()); + } + + // Try to read a unary op. + if (arg.starts_with('-') && arg.length() == 2) { + optind++; + if (should_treat_expression_as_single_string(argv[optind])) { + --optind; + return make(move(arg), "", StringCompare::NotEqual); + } + + StringView value = argv[optind]; + switch (arg[1]) { + case 'b': + return make(value, FileIsOfKind::BlockDevice); + case 'c': + return make(value, FileIsOfKind::CharacterDevice); + case 'd': + return make(value, FileIsOfKind::Directory); + case 'f': + return make(value, FileIsOfKind::Regular); + case 'h': + case 'L': + return make(value, FileIsOfKind::SymbolicLink); + case 'p': + return make(value, FileIsOfKind::FIFO); + case 'S': + return make(value, FileIsOfKind::Socket); + case 'r': + return make(value, UserHasPermission::Read); + case 'w': + return make(value, UserHasPermission::Write); + case 'x': + return make(value, UserHasPermission::Execute); + case 'e': + return make(value, UserHasPermission::Any); + case 'o': + case 'a': + // '-a' and '-o' are boolean ops, which are part of a complex expression + // so we have nothing to parse, simply return to caller. + --optind; + return {}; + case 'n': + return make("", value, StringCompare::NotEqual); + case 'z': + return make("", value, StringCompare::Equal); + case 'g': + case 'G': + case 'k': + case 'N': + case 'O': + case 's': + fatal_error("Unsupported operator \033[1m%s", argv[optind]); + default: + --optind; + break; + } + } + + // Try to read a binary op, this is either a op , op , or op . + auto lhs = arg; + arg = argv[++optind]; + + if (arg == "=") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, StringCompare::Equal); + } else if (arg == "!=") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, StringCompare::NotEqual); + } else if (arg == "-eq") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, NumericCompare::Equal); + } else if (arg == "-ge") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, NumericCompare::GreaterOrEqual); + } else if (arg == "-gt") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, NumericCompare::Greater); + } else if (arg == "-le") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, NumericCompare::LessOrEqual); + } else if (arg == "-lt") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, NumericCompare::Less); + } else if (arg == "-ne") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, NumericCompare::NotEqual); + } else if (arg == "-ef") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, FileCompare::Same); + } else if (arg == "-nt") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, FileCompare::ModificationTimestampGreater); + } else if (arg == "-ot") { + StringView rhs = argv[++optind]; + return make(lhs, rhs, FileCompare::ModificationTimestampLess); + } else if (arg == "-o" || arg == "-a") { + // '-a' and '-o' are boolean ops, which are part of a complex expression + // put them back and return with lhs as string compare. + --optind; + return make("", lhs, StringCompare::NotEqual); + } else { + --optind; + return make("", lhs, StringCompare::NotEqual); + } +} + +static OwnPtr parse_complex_expression(char* argv[]) +{ + auto command = parse_simple_expression(argv); + + while (argv[optind] && argv[optind + 1]) { + if (!command && argv[optind]) + fatal_error("expected an expression"); + + StringView arg = argv[++optind]; + + enum { + AndOp, + OrOp, + } binary_operation { AndOp }; + + if (arg == "-a") { + optind++; + binary_operation = AndOp; + } else if (arg == "-o") { + optind++; + binary_operation = OrOp; + } else { + // Ooops, looked too far. + optind--; + return command; + } + auto rhs = parse_complex_expression(argv); + if (!rhs) + fatal_error("Missing right-hand side"); + + if (binary_operation == AndOp) + command = make(command.release_nonnull(), rhs.release_nonnull()); + else + command = make(command.release_nonnull(), rhs.release_nonnull()); + } + + return command; +} + +int main(int argc, char* argv[]) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 126; + } + + if (LexicalPath { argv[0] }.basename() == "[") { + --argc; + if (StringView { argv[argc] } != "]") + fatal_error("test invoked as '[' requires a closing bracket ']'"); + argv[argc] = nullptr; + } + + // Exit false when no arguments are given. + if (argc == 1) + return 1; + + auto condition = parse_complex_expression(argv); + if (optind != argc - 1) + fatal_error("Too many arguments"); + auto result = condition ? condition->check() : false; + + if (g_there_was_an_error) + return 126; + return result ? 0 : 1; +} diff --git a/Userland/Utilities/test_efault.cpp b/Userland/Utilities/test_efault.cpp new file mode 100644 index 0000000000..b063fcfb78 --- /dev/null +++ b/Userland/Utilities/test_efault.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +#define EXPECT_OK(syscall, address, size) \ + do { \ + rc = syscall(fd, (void*)(address), (size_t)(size)); \ + if (rc < 0) { \ + fprintf(stderr, "Expected success: " #syscall "(%p, %zu), got rc=%d, errno=%d\n", (void*)(address), (size_t)(size), rc, errno); \ + } \ + } while (0) + +#define EXPECT_EFAULT(syscall, address, size) \ + do { \ + rc = syscall(fd, (void*)(address), (size_t)(size)); \ + if (rc >= 0 || errno != EFAULT) { \ + fprintf(stderr, "Expected EFAULT: " #syscall "(%p, %zu), got rc=%d, errno=%d\n", (void*)(address), (size_t)(size), rc, errno); \ + } \ + } while (0) + +#define EXPECT_EFAULT_NO_FD(syscall, address, size) \ + do { \ + rc = syscall((address), (size_t)(size)); \ + if (rc >= 0 || errno != EFAULT) { \ + fprintf(stderr, "Expected EFAULT: " #syscall "(%p, %zu), got rc=%d, errno=%d\n", (void*)(address), (size_t)(size), rc, errno); \ + } \ + } while (0) + +int main(int, char**) +{ + int fd = open("/dev/zero", O_RDONLY); + int rc; + + // Test a one-page mapping (4KB) + u8* one_page = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + ASSERT(one_page); + EXPECT_OK(read, one_page, 4096); + EXPECT_EFAULT(read, one_page, 4097); + EXPECT_EFAULT(read, one_page - 1, 4096); + + // Test a two-page mapping (8KB) + u8* two_page = (u8*)mmap(nullptr, 8192, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + ASSERT(two_page); + + EXPECT_OK(read, two_page, 4096); + EXPECT_OK(read, two_page + 4096, 4096); + EXPECT_OK(read, two_page, 8192); + EXPECT_OK(read, two_page + 4095, 4097); + EXPECT_OK(read, two_page + 1, 8191); + EXPECT_EFAULT(read, two_page, 8193); + EXPECT_EFAULT(read, two_page - 1, 1); + + // Check validation of pages between the first and last address. + ptrdiff_t distance = two_page - one_page; + EXPECT_EFAULT(read, one_page, (u32)distance + 1024); + + // Test every kernel page just because. + for (u64 kernel_address = 0xc0000000; kernel_address <= 0xffffffff; kernel_address += PAGE_SIZE) { + EXPECT_EFAULT(read, (void*)kernel_address, 1); + } + + char buffer[4096]; + EXPECT_EFAULT_NO_FD(dbgputstr, buffer, 0xffffff00); + + // Test the page just below where the kernel VM begins. + u8* jerk_page = (u8*)mmap((void*)(0xc0000000 - PAGE_SIZE), PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0); + ASSERT(jerk_page == (void*)(0xc0000000 - PAGE_SIZE)); + + EXPECT_OK(read, jerk_page, 4096); + EXPECT_EFAULT(read, jerk_page, 4097); + + // Test something that would wrap around the 2^32 mark. + EXPECT_EFAULT(read, jerk_page, 0x50000000); + + return 0; +} diff --git a/Userland/Utilities/test_env.cpp b/Userland/Utilities/test_env.cpp new file mode 100644 index 0000000000..ce5467ecf9 --- /dev/null +++ b/Userland/Utilities/test_env.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018-2020, Ben Wiederhake + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +static void assert_env(const char* name, const char* value) +{ + char* result = getenv(name); + if (!result) { + perror("getenv"); + printf("(When reading value for '%s'; we expected '%s'.)\n", name, value); + ASSERT(false); + } + if (strcmp(result, value) != 0) { + printf("Expected '%s', got '%s' instead.\n", value, result); + ASSERT(false); + } +} + +static void test_getenv_preexisting() +{ + assert_env("HOME", "/home/anon"); +} + +static void test_puttenv() +{ + char* to_put = strdup("PUTENVTEST=HELLOPUTENV"); + int rc = putenv(to_put); + if (rc) { + perror("putenv"); + ASSERT(false); + } + assert_env("PUTENVTEST", "HELLOPUTENV"); + // Do not free `to_put`! +} + +static void test_settenv() +{ + int rc = setenv("SETENVTEST", "HELLO SETENV!", 0); + if (rc) { + perror("setenv"); + ASSERT(false); + } + // This used to trigger a very silly bug! :) + assert_env("SETENVTEST", "HELLO SETENV!"); + + rc = setenv("SETENVTEST", "How are you today?", 0); + if (rc) { + perror("setenv"); + ASSERT(false); + } + assert_env("SETENVTEST", "HELLO SETENV!"); + + rc = setenv("SETENVTEST", "Goodbye, friend!", 1); + if (rc) { + perror("setenv"); + ASSERT(false); + } + assert_env("SETENVTEST", "Goodbye, friend!"); +} + +static void test_settenv_overwrite_empty() +{ + int rc = setenv("EMPTYTEST", "Forcefully overwrite non-existing envvar", 1); + if (rc) { + perror("setenv"); + ASSERT(false); + } + assert_env("EMPTYTEST", "Forcefully overwrite non-existing envvar"); +} + +int main(int, char**) +{ +#define RUNTEST(x) \ + { \ + printf("Running " #x " ...\n"); \ + x(); \ + printf("Success!\n"); \ + } + RUNTEST(test_getenv_preexisting); + RUNTEST(test_puttenv); + RUNTEST(test_settenv); + RUNTEST(test_settenv_overwrite_empty); + printf("PASS\n"); + + return 0; +} diff --git a/Userland/Utilities/test_io.cpp b/Userland/Utilities/test_io.cpp new file mode 100644 index 0000000000..316840a8e5 --- /dev/null +++ b/Userland/Utilities/test_io.cpp @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define EXPECT_ERROR_2(err, syscall, arg1, arg2) \ + do { \ + rc = syscall(arg1, arg2); \ + if (rc >= 0 || errno != err) { \ + fprintf(stderr, __FILE__ ":%d: Expected " #err ": " #syscall "(%p, %p), got rc=%d, errno=%d\n", __LINE__, (const void*)(arg1), (const void*)arg2, rc, errno); \ + } \ + } while (0) + +#define EXPECT_ERROR_3(err, syscall, arg1, arg2, arg3) \ + do { \ + rc = syscall(arg1, arg2, arg3); \ + if (rc >= 0 || errno != err) { \ + fprintf(stderr, __FILE__ ":%d: Expected " #err ": " #syscall "(%p, %p, %p), got rc=%d, errno=%d\n", __LINE__, (const void*)(arg1), (const void*)(arg2), (const void*)(arg3), rc, errno); \ + } \ + } while (0) + +static void test_read_from_directory() +{ + char buffer[BUFSIZ]; + int fd = open("/", O_DIRECTORY | O_RDONLY); + ASSERT(fd >= 0); + int rc; + EXPECT_ERROR_3(EISDIR, read, fd, buffer, sizeof(buffer)); + rc = close(fd); + ASSERT(rc == 0); +} + +static void test_write_to_directory() +{ + char str[] = "oh frick"; + int fd = open("/", O_DIRECTORY | O_RDONLY); + if (fd < 0) + perror("open"); + ASSERT(fd >= 0); + int rc; + EXPECT_ERROR_3(EBADF, write, fd, str, sizeof(str)); + rc = close(fd); + ASSERT(rc == 0); +} + +static void test_read_from_writeonly() +{ + char buffer[BUFSIZ]; + int fd = open("/tmp/xxxx123", O_CREAT | O_WRONLY); + ASSERT(fd >= 0); + int rc; + EXPECT_ERROR_3(EBADF, read, fd, buffer, sizeof(buffer)); + rc = close(fd); + ASSERT(rc == 0); +} + +static void test_write_to_readonly() +{ + char str[] = "hello"; + int fd = open("/tmp/abcd123", O_CREAT | O_RDONLY); + ASSERT(fd >= 0); + int rc; + EXPECT_ERROR_3(EBADF, write, fd, str, sizeof(str)); + rc = close(fd); + ASSERT(rc == 0); +} + +static void test_read_past_eof() +{ + char buffer[BUFSIZ]; + int fd = open("/home/anon/myfile.txt", O_RDONLY); + if (fd < 0) + perror("open"); + ASSERT(fd >= 0); + int rc; + rc = lseek(fd, 9999, SEEK_SET); + if (rc < 0) + perror("lseek"); + rc = read(fd, buffer, sizeof(buffer)); + if (rc < 0) + perror("read"); + if (rc > 0) + fprintf(stderr, "read %d bytes past EOF\n", rc); + rc = close(fd); + ASSERT(rc == 0); +} + +static void test_ftruncate_readonly() +{ + int fd = open("/tmp/trunctest", O_RDONLY | O_CREAT, 0666); + ASSERT(fd >= 0); + int rc; + EXPECT_ERROR_2(EBADF, ftruncate, fd, 0); + close(fd); +} + +static void test_ftruncate_negative() +{ + int fd = open("/tmp/trunctest", O_RDWR | O_CREAT, 0666); + ASSERT(fd >= 0); + int rc; + EXPECT_ERROR_2(EINVAL, ftruncate, fd, -1); + close(fd); +} + +static void test_mmap_directory() +{ + int fd = open("/tmp", O_RDONLY | O_DIRECTORY); + ASSERT(fd >= 0); + auto* ptr = mmap(nullptr, 4096, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0); + if (ptr != MAP_FAILED) { + fprintf(stderr, "Boo! mmap() of a directory succeeded!\n"); + return; + } + if (errno != ENODEV) { + fprintf(stderr, "Boo! mmap() of a directory gave errno=%d instead of ENODEV!\n", errno); + return; + } + close(fd); +} + +static void test_tmpfs_read_past_end() +{ + int fd = open("/tmp/x", O_RDWR | O_CREAT | O_TRUNC, 0600); + ASSERT(fd >= 0); + + int rc = ftruncate(fd, 1); + ASSERT(rc == 0); + + rc = lseek(fd, 4096, SEEK_SET); + ASSERT(rc == 4096); + + char buffer[16]; + int nread = read(fd, buffer, sizeof(buffer)); + if (nread != 0) { + fprintf(stderr, "Expected 0-length read past end of file in /tmp\n"); + } + close(fd); +} + +static void test_procfs_read_past_end() +{ + int fd = open("/proc/uptime", O_RDONLY); + ASSERT(fd >= 0); + + int rc = lseek(fd, 4096, SEEK_SET); + ASSERT(rc == 4096); + + char buffer[16]; + int nread = read(fd, buffer, sizeof(buffer)); + if (nread != 0) { + fprintf(stderr, "Expected 0-length read past end of file in /proc\n"); + } + close(fd); +} + +static void test_open_create_device() +{ + int fd = open("/tmp/fakedevice", (O_RDWR | O_CREAT), (S_IFCHR | 0600)); + ASSERT(fd >= 0); + + struct stat st; + if (fstat(fd, &st) < 0) { + perror("stat"); + ASSERT_NOT_REACHED(); + } + + if (st.st_mode != 0100600) { + fprintf(stderr, "Expected mode 0100600 after attempt to create a device node with open(O_CREAT), mode=%o\n", st.st_mode); + } + unlink("/tmp/fakedevice"); + close(fd); +} + +static void test_unlink_symlink() +{ + int rc = symlink("/proc/2/foo", "/tmp/linky"); + if (rc < 0) { + perror("symlink"); + ASSERT_NOT_REACHED(); + } + + auto target = Core::File::read_link("/tmp/linky"); + ASSERT(target == "/proc/2/foo"); + + rc = unlink("/tmp/linky"); + if (rc < 0) { + perror("unlink"); + fprintf(stderr, "Expected unlink() of a symlink into an unreadable directory to succeed!\n"); + } +} + +static void test_eoverflow() +{ + int fd = open("/tmp/x", O_RDWR); + ASSERT(fd >= 0); + + int rc = lseek(fd, INT32_MAX, SEEK_SET); + ASSERT(rc == INT32_MAX); + + char buffer[16]; + rc = read(fd, buffer, sizeof(buffer)); + if (rc >= 0 || errno != EOVERFLOW) { + fprintf(stderr, "Expected EOVERFLOW when trying to read past INT32_MAX\n"); + } + rc = write(fd, buffer, sizeof(buffer)); + if (rc >= 0 || errno != EOVERFLOW) { + fprintf(stderr, "Expected EOVERFLOW when trying to write past INT32_MAX\n"); + } + close(fd); +} + +static void test_rmdir_while_inside_dir() +{ + int rc = mkdir("/home/anon/testdir", 0700); + ASSERT(rc == 0); + + rc = chdir("/home/anon/testdir"); + ASSERT(rc == 0); + + rc = rmdir("/home/anon/testdir"); + ASSERT(rc == 0); + + int fd = open("x", O_CREAT | O_RDWR, 0600); + if (fd >= 0 || errno != ENOENT) { + fprintf(stderr, "Expected ENOENT when trying to create a file inside a deleted directory. Got %d with errno=%d\n", fd, errno); + } + + rc = chdir("/home/anon"); + ASSERT(rc == 0); +} + +static void test_writev() +{ + int pipefds[2]; + pipe(pipefds); + + iovec iov[2]; + iov[0].iov_base = const_cast((const void*)"Hello"); + iov[0].iov_len = 5; + iov[1].iov_base = const_cast((const void*)"Friends"); + iov[1].iov_len = 7; + int nwritten = writev(pipefds[1], iov, 2); + if (nwritten < 0) { + perror("writev"); + ASSERT_NOT_REACHED(); + } + if (nwritten != 12) { + fprintf(stderr, "Didn't write 12 bytes to pipe with writev\n"); + ASSERT_NOT_REACHED(); + } + + char buffer[32]; + int nread = read(pipefds[0], buffer, sizeof(buffer)); + if (nread != 12 || memcmp(buffer, "HelloFriends", 12)) { + fprintf(stderr, "Didn't read the expected data from pipe after writev\n"); + ASSERT_NOT_REACHED(); + } + + close(pipefds[0]); + close(pipefds[1]); +} + +static void test_rmdir_root() +{ + int rc = rmdir("/"); + if (rc != -1 || errno != EBUSY) { + warnln("rmdir(/) didn't fail with EBUSY"); + ASSERT_NOT_REACHED(); + } +} + +int main() +{ + int rc; + EXPECT_ERROR_2(ENOTDIR, open, "/dev/zero", (O_DIRECTORY | O_RDONLY)); + EXPECT_ERROR_2(EINVAL, open, "/dev/zero", (O_DIRECTORY | O_CREAT | O_RDWR)); + EXPECT_ERROR_2(EEXIST, open, "/dev/zero", (O_CREAT | O_EXCL | O_RDWR)); + EXPECT_ERROR_2(EINVAL, open, "/tmp/abcdef", (O_DIRECTORY | O_CREAT | O_RDWR)); + EXPECT_ERROR_2(EACCES, open, "/proc/all", (O_RDWR)); + EXPECT_ERROR_2(ENOENT, open, "/boof/baaf/nonexistent", (O_CREAT | O_RDWR)); + EXPECT_ERROR_2(EISDIR, open, "/tmp", (O_DIRECTORY | O_RDWR)); + + test_read_from_directory(); + test_write_to_directory(); + test_read_from_writeonly(); + test_write_to_readonly(); + test_read_past_eof(); + test_ftruncate_readonly(); + test_ftruncate_negative(); + test_mmap_directory(); + test_tmpfs_read_past_end(); + test_procfs_read_past_end(); + test_open_create_device(); + test_unlink_symlink(); + test_eoverflow(); + test_rmdir_while_inside_dir(); + test_writev(); + test_rmdir_root(); + + EXPECT_ERROR_2(EPERM, link, "/", "/home/anon/lolroot"); + + return 0; +} diff --git a/Userland/Utilities/top.cpp b/Userland/Utilities/top.cpp new file mode 100644 index 0000000000..c53dfb88f0 --- /dev/null +++ b/Userland/Utilities/top.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ThreadData { + int tid; + pid_t pid; + pid_t pgid; + pid_t pgp; + pid_t sid; + uid_t uid; + gid_t gid; + pid_t ppid; + unsigned nfds; + String name; + String tty; + size_t amount_virtual; + size_t amount_resident; + size_t amount_shared; + unsigned syscall_count; + unsigned inode_faults; + unsigned zero_faults; + unsigned cow_faults; + unsigned times_scheduled; + + unsigned times_scheduled_since_prev { 0 }; + unsigned cpu_percent { 0 }; + unsigned cpu_percent_decimal { 0 }; + + u32 priority; + String username; + String state; +}; + +struct PidAndTid { + bool operator==(const PidAndTid& other) const + { + return pid == other.pid && tid == other.tid; + } + pid_t pid; + int tid; +}; + +namespace AK { +template<> +struct Traits : public GenericTraits { + static unsigned hash(const PidAndTid& value) { return pair_int_hash(value.pid, value.tid); } +}; +} + +struct Snapshot { + HashMap map; + u32 sum_times_scheduled { 0 }; +}; + +static Snapshot get_snapshot() +{ + auto all_processes = Core::ProcessStatisticsReader::get_all(); + if (!all_processes.has_value()) + return {}; + + Snapshot snapshot; + for (auto& it : all_processes.value()) { + auto& stats = it.value; + for (auto& thread : stats.threads) { + snapshot.sum_times_scheduled += thread.times_scheduled; + ThreadData thread_data; + thread_data.tid = thread.tid; + thread_data.pid = stats.pid; + thread_data.pgid = stats.pgid; + thread_data.pgp = stats.pgp; + thread_data.sid = stats.sid; + thread_data.uid = stats.uid; + thread_data.gid = stats.gid; + thread_data.ppid = stats.ppid; + thread_data.nfds = stats.nfds; + thread_data.name = stats.name; + thread_data.tty = stats.tty; + thread_data.amount_virtual = stats.amount_virtual; + thread_data.amount_resident = stats.amount_resident; + thread_data.amount_shared = stats.amount_shared; + thread_data.syscall_count = thread.syscall_count; + thread_data.inode_faults = thread.inode_faults; + thread_data.zero_faults = thread.zero_faults; + thread_data.cow_faults = thread.cow_faults; + thread_data.times_scheduled = thread.times_scheduled; + thread_data.priority = thread.priority; + thread_data.state = thread.state; + thread_data.username = stats.username; + + snapshot.map.set({ stats.pid, thread.tid }, move(thread_data)); + } + } + + return snapshot; +} + +static bool g_window_size_changed = true; +static struct winsize g_window_size; + +int main(int, char**) +{ + if (pledge("stdio rpath tty sigaction ", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/proc/all", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/passwd", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + signal(SIGWINCH, [](int) { + g_window_size_changed = true; + }); + + if (pledge("stdio rpath tty", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector threads; + auto prev = get_snapshot(); + usleep(10000); + for (;;) { + if (g_window_size_changed) { + int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &g_window_size); + if (rc < 0) { + perror("ioctl(TIOCGWINSZ)"); + return 1; + } + g_window_size_changed = false; + } + + auto current = get_snapshot(); + auto sum_diff = current.sum_times_scheduled - prev.sum_times_scheduled; + + printf("\033[3J\033[H\033[2J"); + printf("\033[47;30m%6s %3s %3s %-9s %-10s %6s %6s %4s %s\033[K\033[0m\n", + "PID", + "TID", + "PRI", + "USER", + "STATE", + "VIRT", + "PHYS", + "%CPU", + "NAME"); + for (auto& it : current.map) { + auto pid_and_tid = it.key; + if (pid_and_tid.pid == 0) + continue; + u32 times_scheduled_now = it.value.times_scheduled; + auto jt = prev.map.find(pid_and_tid); + if (jt == prev.map.end()) + continue; + u32 times_scheduled_before = (*jt).value.times_scheduled; + u32 times_scheduled_diff = times_scheduled_now - times_scheduled_before; + it.value.times_scheduled_since_prev = times_scheduled_diff; + it.value.cpu_percent = ((times_scheduled_diff * 100) / sum_diff); + it.value.cpu_percent_decimal = (((times_scheduled_diff * 1000) / sum_diff) % 10); + threads.append(&it.value); + } + + quick_sort(threads, [](auto* p1, auto* p2) { + return p2->times_scheduled_since_prev < p1->times_scheduled_since_prev; + }); + + int row = 0; + for (auto* thread : threads) { + int nprinted = printf("%6d %3d %2u %-9s %-10s %6zu %6zu %2u.%1u ", + thread->pid, + thread->tid, + thread->priority, + thread->username.characters(), + thread->state.characters(), + thread->amount_virtual / 1024, + thread->amount_resident / 1024, + thread->cpu_percent, + thread->cpu_percent_decimal); + + int remaining = g_window_size.ws_col - nprinted; + fwrite(thread->name.characters(), 1, max(0, min(remaining, (int)thread->name.length())), stdout); + putchar('\n'); + + if (++row >= (g_window_size.ws_row - 2)) + break; + } + threads.clear_with_capacity(); + prev = move(current); + sleep(1); + } + return 0; +} diff --git a/Userland/Utilities/touch.cpp b/Userland/Utilities/touch.cpp new file mode 100644 index 0000000000..ef6c16162c --- /dev/null +++ b/Userland/Utilities/touch.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool file_exists(const char* path) +{ + struct stat st; + int rc = stat(path, &st); + if (rc < 0) { + if (errno == ENOENT) + return false; + } + if (rc == 0) { + return true; + } + perror("stat"); + exit(1); +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath cpath fattr", nullptr)) { + perror("pledge"); + return 1; + } + + Vector paths; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Create a file, or update its mtime (time of last modification)."); + args_parser.add_positional_argument(paths, "Files to touch", "path", Core::ArgsParser::Required::Yes); + args_parser.parse(argc, argv); + + for (auto path : paths) { + if (file_exists(path)) { + int rc = utime(path, nullptr); + if (rc < 0) + perror("utime"); + } else { + int fd = open(path, O_CREAT, 0100644); + if (fd < 0) { + perror("open"); + return 1; + } + int rc = close(fd); + if (rc < 0) { + perror("close"); + return 1; + } + } + } + return 0; +} diff --git a/Userland/Utilities/tr.cpp b/Userland/Utilities/tr.cpp new file mode 100644 index 0000000000..8096598e9a --- /dev/null +++ b/Userland/Utilities/tr.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + const char* from_chars = nullptr; + const char* to_chars = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(from_chars, "Character to translate from", "from"); + args_parser.add_positional_argument(to_chars, "Character to translate to", "to"); + args_parser.parse(argc, argv); + + // TODO: Support multiple characters to translate from and to + auto from = from_chars[0]; + auto to = to_chars[0]; + + for (;;) { + char ch = fgetc(stdin); + if (feof(stdin)) + break; + if (ch == from) + putchar(to); + else + putchar(ch); + } + + return 0; +} diff --git a/Userland/Utilities/tree.cpp b/Userland/Utilities/tree.cpp new file mode 100644 index 0000000000..5f6dd334a5 --- /dev/null +++ b/Userland/Utilities/tree.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020, Stijn De Ridder + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool flag_show_hidden_files = false; +static bool flag_show_only_directories = false; +static int max_depth = INT_MAX; + +static int g_directories_seen = 0; +static int g_files_seen = 0; + +static void print_directory_tree(const String& root_path, int depth, const String& indent_string) +{ + if (depth > 0) { + String root_indent_string; + if (depth > 1) { + root_indent_string = indent_string.substring(0, (depth - 1) * 4); + } else { + root_indent_string = ""; + } + out("{}|-- ", root_indent_string); + } + + String root_dir_name = AK::LexicalPath(root_path).basename(); + out("\033[34;1m{}\033[0m\n", root_dir_name); + + if (depth >= max_depth) { + return; + } + + Core::DirIterator di(root_path, flag_show_hidden_files ? Core::DirIterator::SkipParentAndBaseDir : Core::DirIterator::SkipDots); + if (di.has_error()) { + warnln("{}: {}", root_path, di.error_string()); + return; + } + + Vector names; + while (di.has_next()) { + String name = di.next_path(); + if (di.has_error()) { + warnln("{}: {}", root_path, di.error_string()); + continue; + } + names.append(name); + } + + quick_sort(names); + + for (size_t i = 0; i < names.size(); i++) { + String name = names[i]; + + StringBuilder builder; + builder.append(root_path); + if (!root_path.ends_with('/')) { + builder.append('/'); + } + builder.append(name); + String full_path = builder.to_string(); + + struct stat st; + int rc = lstat(full_path.characters(), &st); + if (rc == -1) { + warnln("lstat({}) failed: {}", full_path, strerror(errno)); + continue; + } + + if (S_ISDIR(st.st_mode)) { + g_directories_seen++; + + bool at_last_entry = i == names.size() - 1; + String new_indent_string; + if (at_last_entry) { + new_indent_string = String::formatted("{} ", indent_string); + } else { + new_indent_string = String::formatted("{}| ", indent_string); + } + + print_directory_tree(full_path.characters(), depth + 1, new_indent_string); + } else if (!flag_show_only_directories) { + g_files_seen++; + + outln("{}|-- {}", indent_string, name); + } + } +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath tty", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector directories; + + Core::ArgsParser args_parser; + args_parser.add_option(flag_show_hidden_files, "Show hidden files", "all", 'a'); + args_parser.add_option(flag_show_only_directories, "Show only directories", "only-directories", 'd'); + args_parser.add_option(max_depth, "Maximum depth of the tree", "maximum-depth", 'L', "level"); + args_parser.add_positional_argument(directories, "Directories to print", "directories", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (max_depth < 1) { + warnln("{}: Invalid level, must be greater than 0.", argv[0]); + return 1; + } + + if (directories.is_empty()) { + print_directory_tree(".", 0, ""); + puts(""); + } else { + for (const char* directory : directories) { + print_directory_tree(directory, 0, ""); + puts(""); + } + } + + outln("{} directories, {} files", g_directories_seen, g_files_seen); + + return 0; +} diff --git a/Userland/Utilities/true.cpp b/Userland/Utilities/true.cpp new file mode 100644 index 0000000000..3e3d125d4c --- /dev/null +++ b/Userland/Utilities/true.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +int main(int, char**) +{ + return 0; +} diff --git a/Userland/Utilities/truncate.cpp b/Userland/Utilities/truncate.cpp new file mode 100644 index 0000000000..ad0cbd531d --- /dev/null +++ b/Userland/Utilities/truncate.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +enum TruncateOperation { + OP_Set, + OP_Grow, + OP_Shrink, +}; + +int main(int argc, char** argv) +{ + const char* resize = nullptr; + const char* reference = nullptr; + const char* file = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(resize, "Resize the target file to (or by) this size. Prefix with + or - to expand or shrink the file, or a bare number to set the size exactly", "size", 's', "size"); + args_parser.add_option(reference, "Resize the target file to match the size of this one", "reference", 'r', "file"); + args_parser.add_positional_argument(file, "File path", "file"); + args_parser.parse(argc, argv); + + if (!resize && !reference) { + args_parser.print_usage(stderr, argv[0]); + return 1; + } + + if (resize && reference) { + args_parser.print_usage(stderr, argv[0]); + return 1; + } + + auto op = OP_Set; + int size = 0; + + if (resize) { + String str = resize; + + switch (str[0]) { + case '+': + op = OP_Grow; + str = str.substring(1, str.length() - 1); + break; + case '-': + op = OP_Shrink; + str = str.substring(1, str.length() - 1); + break; + } + + auto size_opt = str.to_int(); + if (!size_opt.has_value()) { + args_parser.print_usage(stderr, argv[0]); + return 1; + } + size = size_opt.value(); + } + + if (reference) { + struct stat st; + int rc = stat(reference, &st); + if (rc < 0) { + perror("stat"); + return 1; + } + + size = st.st_size; + } + + int fd = open(file, O_RDWR | O_CREAT, 0666); + if (fd < 0) { + perror("open"); + return 1; + } + + struct stat st; + if (fstat(fd, &st) < 0) { + perror("fstat"); + return 1; + } + + switch (op) { + case OP_Set: + break; + case OP_Grow: + size = st.st_size + size; + break; + case OP_Shrink: + size = st.st_size - size; + break; + } + + if (ftruncate(fd, size) < 0) { + perror("ftruncate"); + return 1; + } + + if (close(fd) < 0) { + perror("close"); + return 1; + } + + return 0; +} diff --git a/Userland/Utilities/tt.cpp b/Userland/Utilities/tt.cpp new file mode 100644 index 0000000000..b5f4730ee9 --- /dev/null +++ b/Userland/Utilities/tt.cpp @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +static int mutex_test(); +static int detached_test(); +static int priority_test(); +static int stack_size_test(); +static int staying_alive_test(); +static int set_stack_test(); + +int main(int argc, char** argv) +{ + const char* test_name = "n"; + + Core::ArgsParser args_parser; + args_parser.set_general_help( + "Exercise error-handling and edge-case paths of the execution environment " + "(i.e., Kernel or UE) by doing unusual thread-related things."); + args_parser.add_positional_argument(test_name, "Test to run (m = mutex, d = detached, p = priority, s = stack size, t = simple thread test, x = set stack, nothing = join race)", "test-name", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (*test_name == 'm') + return mutex_test(); + if (*test_name == 'd') + return detached_test(); + if (*test_name == 'p') + return priority_test(); + if (*test_name == 's') + return stack_size_test(); + if (*test_name == 't') + return staying_alive_test(); + if (*test_name == 'x') + return set_stack_test(); + if (*test_name != 'n') { + args_parser.print_usage(stdout, argv[0]); + return 1; + } + + printf("Hello from the first thread!\n"); + pthread_t thread_id; + int rc = pthread_create( + &thread_id, nullptr, [](void*) -> void* { + printf("Hi there, from the second thread!\n"); + pthread_exit((void*)0xDEADBEEF); + return nullptr; + }, + nullptr); + if (rc < 0) { + perror("pthread_create"); + return 1; + } + void* retval; + rc = pthread_join(thread_id, &retval); + if (rc < 0) { + perror("pthread_join"); + return 1; + } + printf("Okay, joined and got retval=%p\n", retval); + return 0; +} + +static pthread_mutex_t mutex; + +int mutex_test() +{ + int rc = pthread_mutex_init(&mutex, nullptr); + if (rc < 0) { + perror("pthread_mutex_init"); + return 1; + } + pthread_t thread_id; + rc = pthread_create( + &thread_id, nullptr, [](void*) -> void* { + printf("I'm the secondary thread :^)\n"); + for (;;) { + pthread_mutex_lock(&mutex); + printf("Second thread stole mutex\n"); + sleep(1); + printf("Second thread giving back mutex\n"); + pthread_mutex_unlock(&mutex); + sleep(1); + } + pthread_exit((void*)0xDEADBEEF); + return nullptr; + }, + nullptr); + if (rc < 0) { + perror("pthread_create"); + return 1; + } + for (;;) { + pthread_mutex_lock(&mutex); + printf("Obnoxious spam!\n"); + pthread_mutex_unlock(&mutex); + usleep(10000); + } + return 0; +} + +int detached_test() +{ + pthread_attr_t attributes; + int rc = pthread_attr_init(&attributes); + if (rc != 0) { + printf("pthread_attr_init: %s\n", strerror(rc)); + return 1; + } + + int detach_state = 99; // clearly invalid + rc = pthread_attr_getdetachstate(&attributes, &detach_state); + if (rc != 0) { + printf("pthread_attr_getdetachstate: %s\n", strerror(rc)); + return 2; + } + printf("Default detach state: %s\n", detach_state == PTHREAD_CREATE_JOINABLE ? "joinable" : "detached"); + + detach_state = PTHREAD_CREATE_DETACHED; + rc = pthread_attr_setdetachstate(&attributes, detach_state); + if (rc != 0) { + printf("pthread_attr_setdetachstate: %s\n", strerror(rc)); + return 3; + } + printf("Set detach state on new thread to detached\n"); + + pthread_t thread_id; + rc = pthread_create( + &thread_id, &attributes, [](void*) -> void* { + printf("I'm the secondary thread :^)\n"); + sleep(1); + pthread_exit((void*)0xDEADBEEF); + return nullptr; + }, + nullptr); + if (rc != 0) { + printf("pthread_create: %s\n", strerror(rc)); + return 4; + } + + void* ret_val; + rc = pthread_join(thread_id, &ret_val); + if (rc != 0 && rc != EINVAL) { + printf("pthread_join: %s\n", strerror(rc)); + return 5; + } + if (rc != EINVAL) { + printf("Expected EINVAL! Thread was joinable?\n"); + return 6; + } + + sleep(2); + printf("Thread was created detached. I sure hope it exited on its own.\n"); + + rc = pthread_attr_destroy(&attributes); + if (rc != 0) { + printf("pthread_attr_destroy: %s\n", strerror(rc)); + return 7; + } + + return 0; +} + +int priority_test() +{ + pthread_attr_t attributes; + int rc = pthread_attr_init(&attributes); + if (rc != 0) { + printf("pthread_attr_init: %s\n", strerror(rc)); + return 1; + } + + struct sched_param sched_params; + rc = pthread_attr_getschedparam(&attributes, &sched_params); + if (rc != 0) { + printf("pthread_attr_getschedparam: %s\n", strerror(rc)); + return 2; + } + printf("Default priority: %d\n", sched_params.sched_priority); + + sched_params.sched_priority = 3; + rc = pthread_attr_setschedparam(&attributes, &sched_params); + if (rc != 0) { + printf("pthread_attr_setschedparam: %s\n", strerror(rc)); + return 3; + } + printf("Set thread priority to 3\n"); + + pthread_t thread_id; + rc = pthread_create( + &thread_id, &attributes, [](void*) -> void* { + printf("I'm the secondary thread :^)\n"); + sleep(1); + pthread_exit((void*)0xDEADBEEF); + return nullptr; + }, + nullptr); + if (rc < 0) { + perror("pthread_create"); + return 4; + } + + rc = pthread_join(thread_id, nullptr); + if (rc < 0) { + perror("pthread_join"); + return 5; + } + + rc = pthread_attr_destroy(&attributes); + if (rc != 0) { + printf("pthread_attr_destroy: %s\n", strerror(rc)); + return 6; + } + + return 0; +} + +int stack_size_test() +{ + pthread_attr_t attributes; + int rc = pthread_attr_init(&attributes); + if (rc != 0) { + printf("pthread_attr_init: %s\n", strerror(rc)); + return 1; + } + + size_t stack_size; + rc = pthread_attr_getstacksize(&attributes, &stack_size); + if (rc != 0) { + printf("pthread_attr_getstacksize: %s\n", strerror(rc)); + return 2; + } + printf("Default stack size: %zu\n", stack_size); + + stack_size = 8 * 1024 * 1024; + rc = pthread_attr_setstacksize(&attributes, stack_size); + if (rc != 0) { + printf("pthread_attr_setstacksize: %s\n", strerror(rc)); + return 3; + } + printf("Set thread stack size to 8 MiB\n"); + + pthread_t thread_id; + rc = pthread_create( + &thread_id, &attributes, [](void*) -> void* { + printf("I'm the secondary thread :^)\n"); + sleep(1); + pthread_exit((void*)0xDEADBEEF); + return nullptr; + }, + nullptr); + if (rc < 0) { + perror("pthread_create"); + return 4; + } + + rc = pthread_join(thread_id, nullptr); + if (rc < 0) { + perror("pthread_join"); + return 5; + } + + rc = pthread_attr_destroy(&attributes); + if (rc != 0) { + printf("pthread_attr_destroy: %s\n", strerror(rc)); + return 6; + } + + return 0; +} + +int staying_alive_test() +{ + pthread_t thread_id; + int rc = pthread_create( + &thread_id, nullptr, [](void*) -> void* { + printf("I'm the secondary thread :^)\n"); + sleep(20); + printf("Secondary thread is still alive\n"); + sleep(3520); + printf("Secondary thread exiting\n"); + pthread_exit((void*)0xDEADBEEF); + return nullptr; + }, + nullptr); + if (rc < 0) { + perror("pthread_create"); + return 1; + } + + sleep(1); + printf("I'm the main thread :^)\n"); + sleep(3600); + + printf("Main thread exiting\n"); + return 0; +} + +int set_stack_test() +{ + pthread_attr_t attributes; + int rc = pthread_attr_init(&attributes); + if (rc < 0) { + printf("pthread_attr_init: %s\n", strerror(rc)); + return 1; + } + + size_t stack_size = 8 * 1024 * 1024; + void* stack_addr = mmap_with_name(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, 0, 0, "Cool stack"); + + if (!stack_addr) { + perror("mmap_with_name"); + return -1; + } + + rc = pthread_attr_setstack(&attributes, stack_addr, stack_size); + if (rc != 0) { + printf("pthread_attr_setstack: %s\n", strerror(rc)); + return 2; + } + printf("Set thread stack to %p, size %zu\n", stack_addr, stack_size); + + size_t stack_size_verify; + void* stack_addr_verify; + + rc = pthread_attr_getstack(&attributes, &stack_addr_verify, &stack_size_verify); + if (rc != 0) { + printf("pthread_attr_getstack: %s\n", strerror(rc)); + return 3; + } + + if (stack_addr != stack_addr_verify || stack_size != stack_size_verify) { + printf("Stack address and size don't match! addr: %p %p, size: %zu %zu\n", stack_addr, stack_addr_verify, stack_size, stack_size_verify); + return 4; + } + + pthread_t thread_id; + rc = pthread_create( + &thread_id, &attributes, [](void*) -> void* { + printf("I'm the secondary thread :^)\n"); + sleep(1); + pthread_exit((void*)0xDEADBEEF); + return nullptr; + }, + nullptr); + if (rc < 0) { + perror("pthread_create"); + return 5; + } + + rc = pthread_join(thread_id, nullptr); + if (rc < 0) { + perror("pthread_join"); + return 6; + } + + rc = pthread_attr_destroy(&attributes); + if (rc != 0) { + printf("pthread_attr_destroy: %s\n", strerror(rc)); + return 7; + } + + return 0; +} diff --git a/Userland/Utilities/tty.cpp b/Userland/Utilities/tty.cpp new file mode 100644 index 0000000000..509eb522de --- /dev/null +++ b/Userland/Utilities/tty.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +int main(int, char**) +{ + char* tty = ttyname(0); + if (!tty) { + perror("Error"); + return 1; + } + printf("%s\n", tty); + return 0; +} diff --git a/Userland/Utilities/umount.cpp b/Userland/Utilities/umount.cpp new file mode 100644 index 0000000000..63b20cf344 --- /dev/null +++ b/Userland/Utilities/umount.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + const char* mount_point = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(mount_point, "Mount point", "mountpoint"); + args_parser.parse(argc, argv); + + if (umount(mount_point) < 0) { + perror("umount"); + return 1; + } + return 0; +} diff --git a/Userland/Utilities/uname.cpp b/Userland/Utilities/uname.cpp new file mode 100644 index 0000000000..99b0606009 --- /dev/null +++ b/Userland/Utilities/uname.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + bool flag_system = false; + bool flag_node = false; + bool flag_release = false; + bool flag_machine = false; + bool flag_all = false; + + Core::ArgsParser args_parser; + args_parser.add_option(flag_system, "Print the system name (default)", nullptr, 's'); + args_parser.add_option(flag_node, "Print the node name", nullptr, 'n'); + args_parser.add_option(flag_release, "Print the system release", nullptr, 'r'); + args_parser.add_option(flag_machine, "Print the machine hardware name", nullptr, 'm'); + args_parser.add_option(flag_all, "Print all information (same as -snrm)", nullptr, 'a'); + args_parser.parse(argc, argv); + + if (flag_all) + flag_system = flag_node = flag_release = flag_machine = true; + + if (!flag_system && !flag_node && !flag_release && !flag_machine) + flag_system = true; + + utsname uts; + int rc = uname(&uts); + if (rc < 0) { + perror("uname() failed"); + return 0; + } + + Vector parts; + if (flag_system) + parts.append(uts.sysname); + if (flag_node) + parts.append(uts.nodename); + if (flag_release) + parts.append(uts.release); + if (flag_machine) + parts.append(uts.machine); + StringBuilder builder; + builder.join(' ', parts); + puts(builder.to_string().characters()); + return 0; +} diff --git a/Userland/Utilities/uniq.cpp b/Userland/Utilities/uniq.cpp new file mode 100644 index 0000000000..0034c1cb62 --- /dev/null +++ b/Userland/Utilities/uniq.cpp @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020, Matthew L. Curry + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +struct linebuf { + char* buf = NULL; + size_t len = 0; +}; + +static FILE* get_stream(const char* filepath, const char* perms) +{ + FILE* ret; + + if (filepath == nullptr) { + if (perms[0] == 'r') + return stdin; + return stdout; + } + + ret = fopen(filepath, perms); + if (ret == nullptr) { + perror("fopen"); + exit(1); + } + + return ret; +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath wpath cpath", nullptr) > 0) { + perror("pledge"); + return 1; + } + + const char* inpath = nullptr; + const char* outpath = nullptr; + Core::ArgsParser args_parser; + args_parser.add_positional_argument(inpath, "Input file", "input", Core::ArgsParser::Required::No); + args_parser.add_positional_argument(outpath, "Output file", "output", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + FILE* infile = get_stream(inpath, "r"); + FILE* outfile = get_stream(outpath, "w"); + + struct linebuf buffers[2]; + struct linebuf* previous = &(buffers[0]); + struct linebuf* current = &(buffers[1]); + bool first_run = true; + for (;;) { + errno = 0; + ssize_t rc = getline(&(current->buf), &(current->len), infile); + if (rc < 0 && errno != 0) { + perror("getline"); + exit(1); + } + if (rc < 0) + break; + if (!first_run && strcmp(current->buf, previous->buf) == 0) + continue; + + fputs(current->buf, outfile); + AK::swap(current, previous); + first_run = false; + } + + fclose(infile); + fclose(outfile); + + return 0; +} diff --git a/Userland/Utilities/unzip.cpp b/Userland/Utilities/unzip.cpp new file mode 100644 index 0000000000..d21228c822 --- /dev/null +++ b/Userland/Utilities/unzip.cpp @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2020, Andrés Vieira + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +static const u8 central_directory_file_header_sig[] = "\x50\x4b\x01\x02"; + +static bool seek_and_read(u8* buffer, const MappedFile& file, off_t seek_to, size_t bytes_to_read) +{ + if (!buffer) + return false; + + if ((size_t)seek_to >= file.size()) + return false; + + memcpy(buffer, (const char*)file.data() + seek_to, bytes_to_read); + + return true; +} + +static bool find_next_central_directory(off_t file_size, const MappedFile& file, off_t current_index, off_t& return_index) +{ + off_t start_index = current_index == 0 ? current_index : current_index + 1; + for (off_t index = start_index; index < file_size - 4; index++) { + u8 buffer[4]; + if (!seek_and_read(buffer, file, index, 4)) + return false; + + if (!memcmp(buffer, central_directory_file_header_sig, 4)) { + return_index = index; + return true; + } + } + return false; +} + +static bool unpack_file_for_central_directory_index(off_t central_directory_index, const MappedFile& file) +{ + enum CentralFileDirectoryHeaderOffsets { + CFDHCompressionMethodOffset = 10, + CFDHLocalFileHeaderIndexOffset = 42, + }; + enum LocalFileHeaderOffsets { + LFHCompressionMethodOffset = 8, + LFHCompressedSizeOffset = 18, + LFHFileNameLengthOffset = 26, + LFHExtraFieldLengthOffset = 28, + LFHFileNameBaseOffset = 30, + }; + enum CompressionMethod { + None = 0, + Shrunk = 1, + Factor1 = 2, + Factor2 = 3, + Factor3 = 4, + Factor4 = 5, + Implode = 6, + Deflate = 8, + EnhancedDeflate = 9, + PKWareDCLImplode = 10, + BZIP2 = 12, + LZMA = 14, + TERSE = 18, + LZ77 = 19, + }; + + u8 buffer[4]; + if (!seek_and_read(buffer, file, central_directory_index + CFDHLocalFileHeaderIndexOffset, 4)) + return false; + off_t local_file_header_index = buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; + + if (!seek_and_read(buffer, file, local_file_header_index + LFHCompressionMethodOffset, 2)) + return false; + auto compression_method = buffer[1] << 8 | buffer[0]; + // FIXME: Remove once any decompression is supported. + ASSERT(compression_method == None); + + if (!seek_and_read(buffer, file, local_file_header_index + LFHCompressedSizeOffset, 4)) + return false; + off_t compressed_file_size = buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; + + if (!seek_and_read(buffer, file, local_file_header_index + LFHFileNameLengthOffset, 2)) + return false; + off_t file_name_length = buffer[1] << 8 | buffer[0]; + + if (!seek_and_read(buffer, file, local_file_header_index + LFHExtraFieldLengthOffset, 2)) + return false; + off_t extra_field_length = buffer[1] << 8 | buffer[0]; + + char file_name[file_name_length + 1]; + if (!seek_and_read((u8*)file_name, file, local_file_header_index + LFHFileNameBaseOffset, file_name_length)) + return false; + file_name[file_name_length] = '\0'; + + if (file_name[file_name_length - 1] == '/') { + if (mkdir(file_name, 0755) < 0) { + perror("mkdir"); + return false; + } + } else { + auto new_file = Core::File::construct(String { file_name }); + if (!new_file->open(Core::IODevice::WriteOnly)) { + fprintf(stderr, "Can't write file %s: %s\n", file_name, new_file->error_string()); + return false; + } + + printf(" extracting: %s\n", file_name); + u8 raw_file_contents[compressed_file_size]; + if (!seek_and_read(raw_file_contents, file, local_file_header_index + LFHFileNameBaseOffset + file_name_length + extra_field_length, compressed_file_size)) + return false; + + // FIXME: Try to uncompress data here. We're just ignoring it as no decompression methods are implemented yet. + if (!new_file->write(raw_file_contents, compressed_file_size)) { + fprintf(stderr, "Can't write file contents in %s: %s\n", file_name, new_file->error_string()); + return false; + } + + if (!new_file->close()) { + fprintf(stderr, "Can't close file %s: %s\n", file_name, new_file->error_string()); + return false; + } + } + + return true; +} + +int main(int argc, char** argv) +{ + const char* path; + int map_size_limit = 32 * MiB; + + Core::ArgsParser args_parser; + args_parser.add_option(map_size_limit, "Maximum chunk size to map", "map-size-limit", 0, "size"); + args_parser.add_positional_argument(path, "File to unzip", "path", Core::ArgsParser::Required::Yes); + args_parser.parse(argc, argv); + + String zip_file_path { path }; + + struct stat st; + int rc = stat(zip_file_path.characters(), &st); + if (rc < 0) { + perror("stat"); + return 1; + } + + // FIXME: Map file chunk-by-chunk once we have mmap() with offset. + // This will require mapping some parts then unmapping them repeatedly, + // but it would be significantly faster and less syscall heavy than seek()/read() at every read. + if (st.st_size >= map_size_limit) { + fprintf(stderr, "unzip warning: Refusing to map file since it is larger than %s, pass '--map-size-limit %d' to get around this\n", + human_readable_size(map_size_limit).characters(), + round_up_to_power_of_two(st.st_size, 16)); + return 1; + } + + auto file_or_error = MappedFile ::map(zip_file_path); + if (file_or_error.is_error()) { + warnln("Failed to open {}: {}", zip_file_path, file_or_error.error()); + return 1; + } + auto& mapped_file = *file_or_error.value(); + + printf("Archive: %s\n", zip_file_path.characters()); + + off_t index = 0; + while (find_next_central_directory(st.st_size, mapped_file, index, index)) { + bool success = unpack_file_for_central_directory_index(index, mapped_file); + if (!success) { + printf("Could not find local file header for a file.\n"); + return 4; + } + } + + return 0; +} diff --git a/Userland/Utilities/uptime.cpp b/Userland/Utilities/uptime.cpp new file mode 100644 index 0000000000..c8b21ae368 --- /dev/null +++ b/Userland/Utilities/uptime.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +int main(int, char**) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + FILE* fp = fopen("/proc/uptime", "r"); + if (!fp) { + perror("fopen(/proc/uptime)"); + return 1; + } + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + char buffer[BUFSIZ]; + auto* p = fgets(buffer, sizeof(buffer), fp); + if (!p) { + perror("fgets"); + return 1; + } + + unsigned seconds; + sscanf(buffer, "%u", &seconds); + + printf("Up "); + + if (seconds / 86400 > 0) { + printf("%d day%s, ", seconds / 86400, (seconds / 86400) == 1 ? "" : "s"); + seconds %= 86400; + } + + if (seconds / 3600 > 0) { + printf("%d hour%s, ", seconds / 3600, (seconds / 3600) == 1 ? "" : "s"); + seconds %= 3600; + } + + if (seconds / 60 > 0) { + printf("%d minute%s, ", seconds / 60, (seconds / 60) == 1 ? "" : "s"); + seconds %= 60; + } + + printf("%d second%s\n", seconds, seconds == 1 ? "" : "s"); + + fclose(fp); + return 0; +} diff --git a/Userland/Utilities/useradd.cpp b/Userland/Utilities/useradd.cpp new file mode 100644 index 0000000000..627e1209f2 --- /dev/null +++ b/Userland/Utilities/useradd.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2019-2020, Jesse Buhagiar + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr uid_t BASE_UID = 1000; +constexpr gid_t USERS_GID = 100; +constexpr const char* DEFAULT_SHELL = "/bin/Shell"; + +int main(int argc, char** argv) +{ + if (pledge("stdio wpath rpath cpath chown", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* home_path = nullptr; + int uid = 0; + int gid = USERS_GID; + bool create_home_dir = false; + const char* password = ""; + const char* shell = DEFAULT_SHELL; + const char* gecos = ""; + const char* username = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(home_path, "Home directory for the new user", "home-dir", 'd', "path"); + args_parser.add_option(uid, "User ID (uid) for the new user", "uid", 'u', "uid"); + args_parser.add_option(gid, "Group ID (gid) for the new user", "gid", 'g', "gid"); + args_parser.add_option(password, "Encrypted password of the new user", "password", 'p', "password"); + args_parser.add_option(create_home_dir, "Create home directory if it does not exist", "create-home", 'm'); + args_parser.add_option(shell, "Path to the default shell binary for the new user", "shell", 's', "path-to-shell"); + args_parser.add_option(gecos, "GECOS name of the new user", "gecos", 'n', "general-info"); + args_parser.add_positional_argument(username, "Login user identity (username)", "login"); + + args_parser.parse(argc, argv); + + // Let's run a quick sanity check on username + if (strpbrk(username, "\\/!@#$%^&*()~+=`:\n")) { + fprintf(stderr, "invalid character in username, %s\n", username); + return 1; + } + + // Disallow names starting with _ and - + if (username[0] == '_' || username[0] == '-' || !isalpha(username[0])) { + fprintf(stderr, "invalid username, %s\n", username); + return 1; + } + + if (uid < 0) { + fprintf(stderr, "invalid uid %d!\n", uid); + return 3; + } + + // First, let's sort out the uid for the user + if (uid > 0) { + if (getpwuid(static_cast(uid))) { + fprintf(stderr, "uid %u already exists!\n", uid); + return 4; + } + + } else { + for (uid = BASE_UID; getpwuid(static_cast(uid)); uid++) { + } + } + + if (gid < 0) { + fprintf(stderr, "invalid gid %d\n", gid); + return 3; + } + + FILE* pwfile = fopen("/etc/passwd", "a"); + if (!pwfile) { + perror("failed to open /etc/passwd"); + return 1; + } + + String home; + if (!home_path) + home = String::format("/home/%s", username); + else + home = home_path; + + if (create_home_dir) { + if (mkdir(home.characters(), 0700) < 0) { + perror(String::format("failed to create directory %s", home.characters()).characters()); + return 12; + } + + if (chown(home.characters(), static_cast(uid), static_cast(gid)) < 0) { + perror(String::format("failed to chown %s to %u:%u", home.characters(), uid, gid).characters()); + + if (rmdir(home.characters()) < 0) { + perror(String::format("failed to rmdir %s", home.characters()).characters()); + return 12; + } + return 12; + } + } + + struct passwd p; + p.pw_name = const_cast(username); + p.pw_passwd = const_cast(password); + p.pw_dir = const_cast(home.characters()); + p.pw_uid = static_cast(uid); + p.pw_gid = static_cast(gid); + p.pw_shell = const_cast(shell); + p.pw_gecos = const_cast(gecos); + + if (putpwent(&p, pwfile) < 0) { + perror("putpwent"); + return 1; + } + + fclose(pwfile); + + return 0; +} diff --git a/Userland/Utilities/userdel.cpp b/Userland/Utilities/userdel.cpp new file mode 100644 index 0000000000..818b73f0b4 --- /dev/null +++ b/Userland/Utilities/userdel.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020, Fei Wu + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio wpath rpath cpath fattr proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/etc/", "rwc") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/bin/rm", "x") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + const char* username = nullptr; + bool remove_home = false; + + Core::ArgsParser args_parser; + args_parser.add_option(remove_home, "Remove home directory", "remove", 'r'); + args_parser.add_positional_argument(username, "Login user identity (username)", "login"); + args_parser.parse(argc, argv); + + if (!remove_home) { + if (pledge("stdio wpath rpath cpath fattr", nullptr) < 0) { + perror("pledge"); + return 1; + } + } + + char temp_filename[] = "/etc/passwd.XXXXXX"; + auto fd = mkstemp(temp_filename); + if (fd == -1) { + perror("failed to create temporary file"); + return 1; + } + + FILE* temp_file = fdopen(fd, "w"); + if (!temp_file) { + perror("fdopen"); + if (unlink(temp_filename) < 0) { + perror("unlink"); + } + + return 1; + } + + bool user_exists = false; + String home_directory; + + int rc = 0; + setpwent(); + for (auto* pw = getpwent(); pw; pw = getpwent()) { + if (strcmp(pw->pw_name, username)) { + if (putpwent(pw, temp_file) != 0) { + perror("failed to put an entry in the temporary passwd file"); + rc = 1; + break; + } + } else { + user_exists = true; + if (remove_home) + home_directory = pw->pw_dir; + } + } + endpwent(); + + if (fclose(temp_file)) { + perror("fclose"); + if (!rc) + rc = 1; + } + + if (rc == 0 && !user_exists) { + fprintf(stderr, "specified user doesn't exist\n"); + rc = 6; + } + + if (rc == 0 && chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) { + perror("chmod"); + rc = 1; + } + + if (rc == 0 && rename(temp_filename, "/etc/passwd") < 0) { + perror("failed to rename the temporary passwd file"); + rc = 1; + } + + if (rc) { + if (unlink(temp_filename) < 0) { + perror("unlink"); + } + return rc; + } + + if (remove_home) { + if (access(home_directory.characters(), F_OK) == -1) + return 0; + + String real_path = Core::File::real_path_for(home_directory); + + if (real_path == "/") { + fprintf(stderr, "home directory is /, not deleted!\n"); + return 12; + } + + pid_t child; + const char* argv[] = { "rm", "-r", home_directory.characters(), nullptr }; + if ((errno = posix_spawn(&child, "/bin/rm", nullptr, nullptr, const_cast(argv), environ))) { + perror("posix_spawn"); + return 12; + } + int wstatus; + if (waitpid(child, &wstatus, 0) < 0) { + perror("waitpid"); + return 12; + } + if (WEXITSTATUS(wstatus)) { + fprintf(stderr, "failed to remove the home directory\n"); + return 12; + } + } + + return 0; +} diff --git a/Userland/Utilities/utmpupdate.cpp b/Userland/Utilities/utmpupdate.cpp new file mode 100644 index 0000000000..c055559b7e --- /dev/null +++ b/Userland/Utilities/utmpupdate.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio wpath cpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/var/run/utmp", "rwc") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + pid_t pid = 0; + bool flag_create = false; + bool flag_delete = false; + const char* tty_name = nullptr; + const char* from = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_option(flag_create, "Create entry", "create", 'c'); + args_parser.add_option(flag_delete, "Delete entry", "delete", 'd'); + args_parser.add_option(pid, "PID", "PID", 'p', "PID"); + args_parser.add_option(from, "From", "from", 'f', "From"); + args_parser.add_positional_argument(tty_name, "TTY name", "tty"); + + args_parser.parse(argc, argv); + + if (flag_create && flag_delete) { + warnln("-c and -d are mutually exclusive"); + return 1; + } + + dbgln("Updating utmp from UID={} GID={} EGID={} PID={}", getuid(), getgid(), getegid(), pid); + + auto file_or_error = Core::File::open("/var/run/utmp", Core::IODevice::ReadWrite); + if (file_or_error.is_error()) { + dbgln("Error: {}", file_or_error.error()); + return 1; + } + + auto& file = *file_or_error.value(); + + auto file_contents = file.read_all(); + auto previous_json = JsonValue::from_string(file_contents); + + JsonObject json; + + if (!previous_json.has_value() || !previous_json.value().is_object()) { + dbgln("Error: Could not parse JSON"); + } else { + json = previous_json.value().as_object(); + } + + if (flag_create) { + JsonObject entry; + entry.set("pid", pid); + entry.set("uid", getuid()); + entry.set("from", from); + entry.set("login_at", time(nullptr)); + json.set(tty_name, move(entry)); + } else { + ASSERT(flag_delete); + dbgln("Removing {} from utmp", tty_name); + json.remove(tty_name); + } + + if (!file.seek(0)) { + dbgln("Seek failed"); + return 1; + } + + if (!file.truncate(0)) { + dbgln("Truncation failed"); + return 1; + } + + if (!file.write(json.to_string())) { + dbgln("Write failed"); + return 1; + } + + return 0; +} diff --git a/Userland/Utilities/w.cpp b/Userland/Utilities/w.cpp new file mode 100644 index 0000000000..4c9dbc106b --- /dev/null +++ b/Userland/Utilities/w.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/dev", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/etc/passwd", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/var/run/utmp", "r") < 0) { + perror("unveil"); + return 1; + } + + if (unveil("/proc", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + auto file_or_error = Core::File::open("/var/run/utmp", Core::IODevice::ReadOnly); + if (file_or_error.is_error()) { + warnln("Error: {}", file_or_error.error()); + return 1; + } + auto& file = *file_or_error.value(); + auto json = JsonValue::from_string(file.read_all()); + if (!json.has_value() || !json.value().is_object()) { + warnln("Error: Could not parse /var/run/utmp"); + return 1; + } + + auto process_statistics = Core::ProcessStatisticsReader::get_all(); + if (!process_statistics.has_value()) { + warnln("Error: Could not get process statistics"); + return 1; + } + + auto now = time(nullptr); + + printf("\033[1m%-10s %-12s %-16s %-6s %s\033[0m\n", + "USER", "TTY", "LOGIN@", "IDLE", "WHAT"); + json.value().as_object().for_each_member([&](auto& tty, auto& value) { + const JsonObject& entry = value.as_object(); + auto uid = entry.get("uid").to_u32(); + [[maybe_unused]] auto pid = entry.get("pid").to_i32(); + + auto login_time = Core::DateTime::from_timestamp(entry.get("login_at").to_number()); + auto login_at = login_time.to_string("%b%d %H:%M:%S"); + + auto* pw = getpwuid(uid); + String username; + if (pw) + username = pw->pw_name; + else + username = String::number(uid); + + StringBuilder builder; + String idle_string = "n/a"; + struct stat st; + if (stat(tty.characters(), &st) == 0) { + auto idle_time = now - st.st_mtime; + if (idle_time >= 0) { + builder.appendf("%llds", idle_time); + idle_string = builder.to_string(); + } + } + + String what = "n/a"; + + for (auto& it : process_statistics.value()) { + if (it.value.tty == tty && it.value.pid == it.value.pgid) + what = it.value.name; + } + + printf("%-10s %-12s %-16s %-6s %s\n", + username.characters(), + tty.characters(), + login_at.characters(), + idle_string.characters(), + what.characters()); + }); + return 0; +} diff --git a/Userland/Utilities/watch.cpp b/Userland/Utilities/watch.cpp new file mode 100644 index 0000000000..1377a7667c --- /dev/null +++ b/Userland/Utilities/watch.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2020, Sahan Fernando + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int opt_interval = 2; +static bool flag_noheader = false; +static bool flag_beep_on_fail = false; +static volatile int exit_code = 0; +static volatile pid_t child_pid = -1; + +static String build_header_string(const Vector& command, const struct timeval& interval) +{ + StringBuilder builder; + builder.appendff("Every {}", interval.tv_sec); + builder.appendf(".%ds: \x1b[1m", interval.tv_usec / 100000); + builder.join(' ', command); + builder.append("\x1b[0m"); + return builder.build(); +} + +static struct timeval get_current_time() +{ + struct timespec ts; + struct timeval tv; + clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); + timespec_to_timeval(ts, tv); + return tv; +} + +static int64_t usecs_from(const struct timeval& start, const struct timeval& end) +{ + struct timeval diff; + timeval_sub(end, start, diff); + return 1000000 * diff.tv_sec + diff.tv_usec; +} + +static void handle_signal(int signal) +{ + if (child_pid > 0) { + if (kill(child_pid, signal) < 0) { + perror("kill"); + } + int status; + if (waitpid(child_pid, &status, 0) < 0) { + perror("waitpid"); + } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + exit_code = 1; + } + } + exit(exit_code); +} + +static int run_command(const Vector& command) +{ + if ((errno = posix_spawnp(const_cast(&child_pid), command[0], nullptr, nullptr, const_cast(command.data()), environ))) { + exit_code = 1; + perror("posix_spawn"); + return errno; + } + + // Wait for the child to terminate, then return its exit code. + int status; + pid_t exited_pid; + do { + exited_pid = waitpid(child_pid, &status, 0); + } while (exited_pid < 0 && errno == EINTR); + ASSERT(exited_pid == child_pid); + child_pid = -1; + if (exited_pid < 0) { + perror("waitpid"); + return 1; + } + if (WIFEXITED(status)) { + return WEXITSTATUS(status); + } else { + return 1; + } +} + +int main(int argc, char** argv) +{ + signal(SIGINT, handle_signal); + if (pledge("stdio proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector command; + Core::ArgsParser args_parser; + args_parser.set_general_help("Execute a command repeatedly, and watch its output over time."); + args_parser.add_option(opt_interval, "Amount of time between updates", "interval", 'n', "seconds"); + args_parser.add_option(flag_noheader, "Turn off the header describing the command and interval", "no-title", 't'); + args_parser.add_option(flag_beep_on_fail, "Beep if the command has a non-zero exit code", "beep", 'b'); + args_parser.add_positional_argument(command, "Command to run", "command"); + args_parser.parse(argc, argv); + + struct timeval interval; + if (opt_interval <= 0) { + interval = { 0, 100000 }; + } else { + interval = { opt_interval, 0 }; + } + + auto header = build_header_string(command, interval); + command.append(nullptr); + + auto now = get_current_time(); + auto next_run_time = now; + while (true) { + int usecs_to_sleep = usecs_from(now, next_run_time); + while (usecs_to_sleep > 0) { + usleep(usecs_to_sleep); + now = get_current_time(); + usecs_to_sleep = usecs_from(now, next_run_time); + } + // Clear the screen, then reset the cursor position to the top left. + fprintf(stderr, "\033[H\033[2J"); + // Print the header. + if (!flag_noheader) { + fprintf(stderr, "%s\n\n", header.characters()); + } else { + fflush(stderr); + } + if (run_command(command) != 0) { + exit_code = 1; + if (flag_beep_on_fail) { + fprintf(stderr, "\a"); + fflush(stderr); + } + } + now = get_current_time(); + timeval_add(next_run_time, interval, next_run_time); + if (usecs_from(now, next_run_time) < 0) { + // The next execution is overdue, so we set next_run_time to now to prevent drift. + next_run_time = now; + } + } +} diff --git a/Userland/Utilities/wc.cpp b/Userland/Utilities/wc.cpp new file mode 100644 index 0000000000..cf9fa62d35 --- /dev/null +++ b/Userland/Utilities/wc.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * Copyright (c) 2021, Emanuele Torre + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include + +struct Count { + String name; + bool exists { true }; + unsigned lines { 0 }; + unsigned characters { 0 }; + unsigned words { 0 }; + size_t bytes { 0 }; +}; + +bool g_output_line = false; +bool g_output_byte = false; +bool g_output_word = false; + +static void wc_out(const Count& count) +{ + if (g_output_line) + out("{:7} ", count.lines); + if (g_output_word) + out("{:7} ", count.words); + if (g_output_byte) + out("{:7} ", count.bytes); + + outln("{:>14}", count.name); +} + +static Count get_count(const String& file_specifier) +{ + Count count; + FILE* file_pointer = nullptr; + if (file_specifier == "-") { + count.name = ""; + file_pointer = stdin; + } else { + count.name = file_specifier; + if ((file_pointer = fopen(file_specifier.characters(), "r")) == nullptr) { + warnln("wc: unable to open {}", file_specifier); + count.exists = false; + return count; + } + } + + bool start_a_new_word = true; + for (int ch = fgetc(file_pointer); ch != EOF; ch = fgetc(file_pointer)) { + count.bytes++; + if (isspace(ch)) { + start_a_new_word = true; + if (ch == '\n') + count.lines++; + } else if (start_a_new_word) { + start_a_new_word = false; + count.words++; + } + } + + if (file_pointer != stdin) + fclose(file_pointer); + + return count; +} + +static Count get_total_count(const Vector& counts) +{ + Count total_count { "total" }; + for (auto& count : counts) { + total_count.lines += count.lines; + total_count.words += count.words; + total_count.characters += count.characters; + total_count.bytes += count.bytes; + } + return total_count; +} + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + Vector file_specifiers; + + Core::ArgsParser args_parser; + args_parser.add_option(g_output_line, "Output line count", "lines", 'l'); + args_parser.add_option(g_output_byte, "Output byte count", "bytes", 'c'); + args_parser.add_option(g_output_word, "Output word count", "words", 'w'); + args_parser.add_positional_argument(file_specifiers, "File to process", "file", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + if (!g_output_line && !g_output_byte && !g_output_word) + g_output_line = g_output_byte = g_output_word = true; + + Vector counts; + for (const auto& file_specifier : file_specifiers) + counts.append(get_count(file_specifier)); + + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (file_specifiers.is_empty()) + counts.append(get_count("-")); + else if (file_specifiers.size() > 1) + counts.append(get_total_count(counts)); + + for (const auto& count : counts) { + if (count.exists) + wc_out(count); + } + + return 0; +} diff --git a/Userland/Utilities/which.cpp b/Userland/Utilities/which.cpp new file mode 100644 index 0000000000..e902fae2e7 --- /dev/null +++ b/Userland/Utilities/which.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* filename = nullptr; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(filename, "Name of executable", "executable"); + args_parser.parse(argc, argv); + + auto fullpath = Core::find_executable_in_path(filename); + if (fullpath.is_null()) { + printf("no '%s' in path\n", filename); + return 1; + } + + printf("%s\n", fullpath.characters()); + return 0; +} diff --git a/Userland/Utilities/whoami.cpp b/Userland/Utilities/whoami.cpp new file mode 100644 index 0000000000..830d1796ba --- /dev/null +++ b/Userland/Utilities/whoami.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +int main(int, char**) +{ + if (pledge("stdio rpath", nullptr) < 0) { + perror("pledge"); + return 1; + } + + if (unveil("/etc/passwd", "r") < 0) { + perror("unveil"); + return 1; + } + + unveil(nullptr, nullptr); + + puts(getlogin()); + return 0; +} diff --git a/Userland/Utilities/xargs.cpp b/Userland/Utilities/xargs.cpp new file mode 100644 index 0000000000..68e4eb54e3 --- /dev/null +++ b/Userland/Utilities/xargs.cpp @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool run_command(Vector&& child_argv, bool verbose, bool is_stdin, int devnull_fd); + +enum Decision { + Unget, + Continue, + Stop, +}; +bool read_items(FILE* fp, char entry_separator, Function); + +class ParsedInitialArguments { +public: + ParsedInitialArguments(Vector&, const StringView& placeholder); + + void for_each_joined_argument(const StringView&, Function) const; + + size_t size() const { return m_all_parts.size(); } + +private: + Vector> m_all_parts; +}; + +int main(int argc, char** argv) +{ + if (pledge("stdio rpath proc exec", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* placeholder = nullptr; + bool split_with_nulls = false; + const char* specified_delimiter = "\n"; + Vector arguments; + bool verbose = false; + const char* file_to_read = "-"; + int max_lines_for_one_command = 0; + int max_bytes_for_one_command = ARG_MAX; + + Core::ArgsParser args_parser; + args_parser.set_general_help("Read arguments from stdin and interpret them as command-line arguments for another program. See also: 'man xargs'."); + args_parser.add_option(placeholder, "Placeholder string to be replaced in arguments", "replace", 'I', "placeholder"); + args_parser.add_option(split_with_nulls, "Split input items with the null character instead of newline", "null", '0'); + args_parser.add_option(specified_delimiter, "Split the input items with the specified character", "delimiter", 'd', "delim"); + args_parser.add_option(verbose, "Display each command before executing it", "verbose", 'v'); + args_parser.add_option(file_to_read, "Read arguments from the specified file instead of stdin", "arg-file", 'a', "file"); + args_parser.add_option(max_lines_for_one_command, "Use at most max-lines lines to create a command", "line-limit", 'L', "max-lines"); + args_parser.add_option(max_bytes_for_one_command, "Use at most max-chars characters to create a command", "char-limit", 's', "max-chars"); + args_parser.add_positional_argument(arguments, "Command and any initial arguments for it", "command", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + size_t max_bytes = min(ARG_MAX, max_bytes_for_one_command); + size_t max_lines = max(max_lines_for_one_command, 0); + + if (!split_with_nulls && strlen(specified_delimiter) > 1) { + fprintf(stderr, "xargs: the delimiter must be a single byte\n"); + return 1; + } + + char entry_separator = split_with_nulls ? '\0' : *specified_delimiter; + + StringView placeholder_view { placeholder }; + + if (!placeholder_view.is_empty()) + max_lines = 1; + + if (arguments.is_empty()) + arguments.append("echo"); + + ParsedInitialArguments initial_arguments(arguments, placeholder_view); + + FILE* fp = stdin; + bool is_stdin = true; + + if (StringView { "-" } != file_to_read) { + // A file was specified, try to open it. + fp = fopen(file_to_read, "re"); + if (!fp) { + perror("fopen"); + return 1; + } + is_stdin = false; + } + + StringBuilder builder; + Vector child_argv; + + int devnull_fd = 0; + + if (is_stdin) { + devnull_fd = open("/dev/null", O_RDONLY | O_CLOEXEC); + if (devnull_fd < 0) { + perror("open"); + return 1; + } + } + + size_t total_command_length = 0; + size_t items_used_for_this_command = 0; + + auto fail = read_items(fp, entry_separator, [&](StringView item) { + if (item.ends_with('\n')) + item = item.substring_view(0, item.length() - 1); + + if (item.is_empty()) + return Continue; + + // The first item is processed differently, as all the initial-arguments are processed _with_ that item + // as their substitution target (assuming that substitution is enabled). + // Note that if substitution is not enabled, we manually insert a substitution target at the end of initial-arguments, + // so this item has a place to go. + if (items_used_for_this_command == 0) { + child_argv.ensure_capacity(initial_arguments.size()); + + initial_arguments.for_each_joined_argument(item, [&](const String& string) { + total_command_length += string.length(); + child_argv.append(strdup(string.characters())); + }); + + ++items_used_for_this_command; + } else { + if ((max_lines > 0 && items_used_for_this_command + 1 > max_lines) || total_command_length + item.length() + 1 >= max_bytes) { + // Note: This `move' does not actually move-construct a new Vector at the callsite, it only allows perfect-forwarding + // and does not invalidate `child_argv' in this scope. + // The same applies for the one below. + if (!run_command(move(child_argv), verbose, is_stdin, devnull_fd)) + return Stop; + items_used_for_this_command = 0; + total_command_length = 0; + return Unget; + } else { + child_argv.append(strndup(item.characters_without_null_termination(), item.length())); + total_command_length += item.length(); + ++items_used_for_this_command; + } + } + + return Continue; + }); + + if (!fail && !child_argv.is_empty()) + fail = !run_command(move(child_argv), verbose, is_stdin, devnull_fd); + + if (!is_stdin) + fclose(fp); + + return fail ? 1 : 0; +} + +bool read_items(FILE* fp, char entry_separator, Function callback) +{ + bool fail = false; + + for (;;) { + char* item = nullptr; + size_t buffer_size = 0; + + auto item_size = getdelim(&item, &buffer_size, entry_separator, fp); + + if (item_size < 0) { + // getdelim() will return -1 and set errno to 0 on EOF. + if (errno != 0) { + perror("getdelim"); + fail = true; + } + break; + } + + Decision decision; + do { + decision = callback(item); + if (decision == Stop) { + free(item); + return true; + } + } while (decision == Unget); + + free(item); + } + + return fail; +} + +bool run_command(Vector&& child_argv, bool verbose, bool is_stdin, int devnull_fd) +{ + child_argv.append(nullptr); + + if (verbose) { + StringBuilder builder; + builder.join(" ", child_argv); + fprintf(stderr, "xargs: %s\n", builder.to_string().characters()); + fflush(stderr); + } + + auto pid = fork(); + if (pid < 0) { + perror("fork"); + return false; + } + + if (pid == 0) { + if (is_stdin) + dup2(devnull_fd, STDIN_FILENO); + + execvp(child_argv[0], child_argv.data()); + exit(1); + } + + for (auto* ptr : child_argv) + free(ptr); + + child_argv.clear_with_capacity(); + + int wstatus = 0; + if (waitpid(pid, &wstatus, 0) < 0) { + perror("waitpid"); + return false; + } + + if (WIFEXITED(wstatus)) { + if (WEXITSTATUS(wstatus) != 0) + return false; + } else { + return false; + } + + return true; +} + +ParsedInitialArguments::ParsedInitialArguments(Vector& arguments, const StringView& placeholder) +{ + m_all_parts.ensure_capacity(arguments.size()); + bool some_argument_has_placeholder = false; + + for (auto argument : arguments) { + StringView arg { argument }; + + if (placeholder.is_empty()) { + m_all_parts.append({ arg }); + } else { + auto parts = arg.split_view(placeholder, true); + some_argument_has_placeholder = some_argument_has_placeholder || parts.size() > 1; + m_all_parts.append(move(parts)); + } + } + + // Append an implicit placeholder at the end if no argument has any placeholders. + if (!some_argument_has_placeholder) { + Vector parts; + parts.append(""); + parts.append(""); + m_all_parts.append(move(parts)); + } +} + +void ParsedInitialArguments::for_each_joined_argument(const StringView& separator, Function callback) const +{ + StringBuilder builder; + for (auto& parts : m_all_parts) { + builder.clear(); + builder.join(separator, parts); + callback(builder.to_string()); + } +} diff --git a/Userland/Utilities/yes.cpp b/Userland/Utilities/yes.cpp new file mode 100644 index 0000000000..31b715ef8f --- /dev/null +++ b/Userland/Utilities/yes.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018-2020, Andreas Kling + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +int main(int argc, char** argv) +{ + if (pledge("stdio", nullptr) < 0) { + perror("pledge"); + return 1; + } + + const char* string = "yes"; + + Core::ArgsParser args_parser; + args_parser.add_positional_argument(string, "String to output (defaults to 'yes')", "string", Core::ArgsParser::Required::No); + args_parser.parse(argc, argv); + + for (;;) + puts(string); + return 0; +} diff --git a/Userland/adjtime.cpp b/Userland/adjtime.cpp deleted file mode 100644 index 810421bc98..0000000000 --- a/Userland/adjtime.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2020, Nico Weber - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ -#ifdef __serenity__ - if (pledge("stdio settime", nullptr) < 0) { - perror("pledge"); - return 1; - } -#endif - - Core::ArgsParser args_parser; - double delta = __builtin_nan(""); - args_parser.add_option(delta, "Adjust system time by this many seconds", "set", 's', "delta_seconds"); - args_parser.parse(argc, argv); - - if (__builtin_isnan(delta)) { -#ifdef __serenity__ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } -#endif - } else { - long delta_us = static_cast(round(delta * 1'000'000)); - timeval delta_timeval; - delta_timeval.tv_sec = delta_us / 1'000'000; - delta_timeval.tv_usec = delta_us % 1'000'000; - if (delta_timeval.tv_usec < 0) { - delta_timeval.tv_sec--; - delta_timeval.tv_usec += 1'000'000; - } - if (adjtime(&delta_timeval, nullptr) < 0) { - perror("adjtime set"); - return 1; - } - } - - timeval remaining_delta_timeval; - if (adjtime(nullptr, &remaining_delta_timeval) < 0) { - perror("adjtime get"); - return 1; - } - double remaining_delta = remaining_delta_timeval.tv_sec + remaining_delta_timeval.tv_usec / 1'000'000.0; - printf("%f\n", remaining_delta); - - return 0; -} diff --git a/Userland/allocate.cpp b/Userland/allocate.cpp deleted file mode 100644 index 9e906121a4..0000000000 --- a/Userland/allocate.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -static void usage() -{ - printf("usage: allocate [number [unit (B/KiB/MiB)]]\n"); - exit(1); -} - -enum class Unit { - Bytes, - KiB, - MiB, -}; - -int main(int argc, char** argv) -{ - int count = 50; - auto unit = Unit::MiB; - - if (argc >= 2) { - auto number = String(argv[1]).to_uint(); - if (!number.has_value()) { - usage(); - } - count = number.value(); - } - - if (argc >= 3) { - if (strcmp(argv[2], "B") == 0) - unit = Unit::Bytes; - else if (strcmp(argv[2], "KiB") == 0) - unit = Unit::KiB; - else if (strcmp(argv[2], "MiB") == 0) - unit = Unit::MiB; - else - usage(); - } - - switch (unit) { - case Unit::Bytes: - break; - case Unit::KiB: - count *= KiB; - break; - case Unit::MiB: - count *= MiB; - break; - } - - Core::ElapsedTimer timer; - - printf("allocating memory (%d bytes)...\n", count); - timer.start(); - char* ptr = (char*)malloc(count); - if (!ptr) { - printf("failed.\n"); - return 1; - } - printf("done in %dms\n", timer.elapsed()); - - auto pages = count / PAGE_SIZE; - auto step = pages / 10; - - Core::ElapsedTimer timer2; - - printf("writing one byte to each page of allocated memory...\n"); - timer.start(); - timer2.start(); - for (int i = 0; i < pages; ++i) { - ptr[i * PAGE_SIZE] = 1; - - if (i != 0 && (i % step) == 0) { - auto ms = timer2.elapsed(); - if (ms == 0) - ms = 1; - - auto bps = double(step * PAGE_SIZE) / (double(ms) / 1000); - - printf("step took %dms (%fMiB/s)\n", ms, bps / MiB); - - timer2.start(); - } - } - printf("done in %dms\n", timer.elapsed()); - - printf("sleeping for ten seconds...\n"); - for (int i = 0; i < 10; i++) { - printf("%d\n", i); - sleep(1); - } - printf("done.\n"); - - printf("freeing memory...\n"); - timer.start(); - free(ptr); - printf("done in %dms\n", timer.elapsed()); - - return 0; -} diff --git a/Userland/aplay.cpp b/Userland/aplay.cpp deleted file mode 100644 index 5c5d221bf5..0000000000 --- a/Userland/aplay.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - const char* path = nullptr; - bool should_loop = false; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(path, "Path to audio file", "path"); - args_parser.add_option(should_loop, "Loop playback", "loop", 'l'); - args_parser.parse(argc, argv); - - Core::EventLoop loop; - - auto audio_client = Audio::ClientConnection::construct(); - audio_client->handshake(); - NonnullRefPtr loader = Audio::Loader::create(path); - if (loader->has_error()) { - fprintf(stderr, "Failed to load audio file: %s\n", loader->error_string()); - return 1; - } - - printf("\033[34;1m Playing\033[0m: %s\n", path); - printf("\033[34;1m Format\033[0m: %u Hz, %u-bit, %s\n", - loader->sample_rate(), - loader->bits_per_sample(), - loader->num_channels() == 1 ? "Mono" : "Stereo"); - printf("\033[34;1mProgress\033[0m: \033[s"); - for (;;) { - auto samples = loader->get_more_samples(); - if (samples) { - printf("\033[u"); - printf("%d/%d", loader->loaded_samples(), loader->total_samples()); - fflush(stdout); - audio_client->enqueue(*samples); - } else if (should_loop) { - loader->reset(); - } else if (audio_client->get_remaining_samples()) { - sleep(1); - } else { - break; - } - } - printf("\n"); - return 0; -} diff --git a/Userland/arp.cpp b/Userland/arp.cpp deleted file mode 100644 index 2be5ce7a26..0000000000 --- a/Userland/arp.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main() -{ - auto file = Core::File::construct("/proc/net/arp"); - if (!file->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Error: %s\n", file->error_string()); - return 1; - } - - printf("Address HWaddress\n"); - auto file_contents = file->read_all(); - auto json = JsonValue::from_string(file_contents); - ASSERT(json.has_value()); - json.value().as_array().for_each([](auto& value) { - auto if_object = value.as_object(); - - auto ip_address = if_object.get("ip_address").to_string(); - auto mac_address = if_object.get("mac_address").to_string(); - - printf("%-15s ", ip_address.characters()); - printf("%-17s ", mac_address.characters()); - printf("\n"); - }); - - return 0; -} diff --git a/Userland/avol.cpp b/Userland/avol.cpp deleted file mode 100644 index cc38a6b0dc..0000000000 --- a/Userland/avol.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - Core::EventLoop loop; - auto audio_client = Audio::ClientConnection::construct(); - audio_client->handshake(); - - bool mute = false; - bool unmute = false; - // FIXME: What is a good way to have an optional int argument? - const char* volume = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(mute, "Mute volume", "mute", 'm'); - args_parser.add_option(unmute, "Unmute volume", "unmute", 'M'); - args_parser.add_positional_argument(volume, "Volume to set", "volume", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (!mute && !unmute && !volume) { - auto volume = audio_client->get_main_mix_volume(); - printf("Volume: %d\n", volume); - return 0; - } - if (!(mute ^ unmute ^ (volume != nullptr))) { - fprintf(stderr, "Only one of mute, unmute or volume must be used\n"); - return 1; - } - if (mute) { - audio_client->set_muted(true); - printf("Muted.\n"); - } else if (unmute) { - audio_client->set_muted(false); - printf("Unmuted.\n"); - } else { - auto new_volume = atoi(volume); - audio_client->set_main_mix_volume(new_volume); - } - return 0; -} diff --git a/Userland/base64.cpp b/Userland/base64.cpp deleted file mode 100644 index f8e0fc6ff9..0000000000 --- a/Userland/base64.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2020, Tom Lebreux - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool decode = false; - const char* filepath = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(decode, "Decode data", "decode", 'd'); - args_parser.add_positional_argument(filepath, "", "file", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - ByteBuffer buffer; - if (filepath == nullptr || strcmp(filepath, "-") == 0) { - auto file = Core::File::construct(); - bool success = file->open( - STDIN_FILENO, - Core::IODevice::OpenMode::ReadOnly, - Core::File::ShouldCloseFileDescriptor::Yes); - ASSERT(success); - buffer = file->read_all(); - } else { - auto result = Core::File::open(filepath, Core::IODevice::OpenMode::ReadOnly); - ASSERT(!result.is_error()); - auto file = result.value(); - buffer = file->read_all(); - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (decode) { - auto decoded = decode_base64(StringView(buffer)); - fwrite(decoded.data(), sizeof(u8), decoded.size(), stdout); - return 0; - } - - auto encoded = encode_base64(buffer); - printf("%s\n", encoded.characters()); -} diff --git a/Userland/basename.cpp b/Userland/basename.cpp deleted file mode 100644 index 65862f1072..0000000000 --- a/Userland/basename.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* path = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(path, "Path to get basename from", "path"); - args_parser.parse(argc, argv); - - printf("%s\n", LexicalPath(path).basename().characters()); - return 0; -} diff --git a/Userland/beep.cpp b/Userland/beep.cpp deleted file mode 100644 index 7f183151cc..0000000000 --- a/Userland/beep.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include - -int main() -{ - sysbeep(); - return 0; -} diff --git a/Userland/cal.cpp b/Userland/cal.cpp deleted file mode 100644 index c5b436eb9c..0000000000 --- a/Userland/cal.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -const int line_width = 70; -const int line_count = 8; -const int column_width = 22; - -char print_buffer[line_width * line_count]; -char temp_buffer[line_width * 8]; - -int target_year; -int target_month; -int target_day; - -int current_year; -int current_month; - -static void append_to_print(char* buffer, int row, int column, char* text) -{ - int starting_point = (line_width * row) + (column * column_width); - for (int i = 0; text[i] != '\0'; i++) { - buffer[starting_point + i] = text[i]; - } -} - -static void insert_month_to_print(int column, int month, int year) -{ - int printing_column = column; - int printing_row = 0; - - // FIXME: Both the month name and month header text should be provided by a locale - sprintf(temp_buffer, " %02u - %04u ", month, year); - append_to_print(print_buffer, printing_row, printing_column, temp_buffer); - printing_row++; - - sprintf(temp_buffer, "Su Mo Tu We Th Fr Sa"); - append_to_print(print_buffer, printing_row, printing_column, temp_buffer); - printing_row++; - int day_to_print = 1; - auto date_time = Core::DateTime::create(year, month, 1); - int first_day_of_week_for_month = date_time.weekday(); - int days_in_the_month = date_time.days_in_month(); - int last_written_chars = 0; - for (int i = 1; day_to_print <= days_in_the_month; ++i) { - if (i - 1 < first_day_of_week_for_month) { - last_written_chars += sprintf(temp_buffer + last_written_chars, " "); - } else { - if (year == current_year && month == current_month && target_day == day_to_print) { - // FIXME: To replicate Unix cal it would be better to use "\x1b[30;47m%2d\x1b[0m " in here instead of *. - // However, doing that messes up the layout. - last_written_chars += sprintf(temp_buffer + last_written_chars, "%2d*", day_to_print); - } else { - last_written_chars += sprintf(temp_buffer + last_written_chars, "%2d ", day_to_print); - } - day_to_print++; - } - - append_to_print(print_buffer, printing_row, printing_column, temp_buffer); - - if (i % 7 == 0) { - printing_row++; - memset(temp_buffer, ' ', line_width * 8); - temp_buffer[line_width * 8 - 1] = '\0'; - last_written_chars = 0; - } - } -} - -static void clean_buffers() -{ - for (int i = 1; i < line_width * line_count; ++i) { - print_buffer[i - 1] = i % line_width == 0 ? '\n' : ' '; - } - print_buffer[line_width * line_count - 1] = '\0'; - - for (int i = 0; i < line_width; ++i) { - temp_buffer[i] = ' '; - } - temp_buffer[line_width - 1] = '\0'; -} - -int main(int argc, char** argv) -{ - int day = 0; - int month = 0; - int year = 0; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Display a nice overview of a month or year, defaulting to the current month."); - // FIXME: This should ensure two values get parsed as month + year - args_parser.add_positional_argument(day, "Day of year", "day", Core::ArgsParser::Required::No); - args_parser.add_positional_argument(month, "Month", "month", Core::ArgsParser::Required::No); - args_parser.add_positional_argument(year, "Year", "year", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - time_t now = time(nullptr); - auto* tm = localtime(&now); - - // Hack: workaround two values parsing as day + month. - if (day && month && !year) { - year = month; - month = day; - day = 0; - } - - bool year_mode = !day && !month && year; - - if (!year) - year = tm->tm_year + 1900; - if (!month) - month = tm->tm_mon + 1; - if (!day) - day = tm->tm_mday; - - current_year = year; - current_month = month; - - clean_buffers(); - - if (year_mode) { - printf(" "); - printf("Year %4d", year); - printf(" \n\n"); - - for (int i = 1; i < 12; ++i) { - insert_month_to_print(0, i++, year); - insert_month_to_print(1, i++, year); - insert_month_to_print(2, i, year); - printf("%s\n", print_buffer); - clean_buffers(); - } - } else { - insert_month_to_print(0, month, year); - printf("%s\n\n", print_buffer); - clean_buffers(); - } - - return 0; -} diff --git a/Userland/cat.cpp b/Userland/cat.cpp deleted file mode 100644 index 496eb890bc..0000000000 --- a/Userland/cat.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector paths; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Concatenate files or pipes to stdout."); - args_parser.add_positional_argument(paths, "File path", "path", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - Vector fds; - if (!paths.is_empty()) { - for (const char* path : paths) { - int fd; - if (StringView { path } == "-") { - fd = 0; - } else if ((fd = open(path, O_RDONLY)) == -1) { - fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); - continue; - } - fds.append(fd); - } - } else { - fds.append(0); - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - for (auto& fd : fds) { - for (;;) { - char buf[32768]; - ssize_t nread = read(fd, buf, sizeof(buf)); - if (nread == 0) - break; - if (nread < 0) { - perror("read"); - return 2; - } - size_t already_written = 0; - while (already_written < (size_t)nread) { - ssize_t nwritten = write(1, buf + already_written, nread - already_written); - if (nwritten < 0) { - perror("write"); - return 3; - } - already_written += nwritten; - } - } - close(fd); - } - - return 0; -} diff --git a/Userland/checksum.cpp b/Userland/checksum.cpp deleted file mode 100644 index d2c6e59e5b..0000000000 --- a/Userland/checksum.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2020, Linus Groh - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - auto program_name = StringView { argv[0] }; - auto hash_kind { Crypto::Hash::HashKind::None }; - - if (program_name == "md5sum") - hash_kind = Crypto::Hash::HashKind::MD5; - else if (program_name == "sha1sum") - hash_kind = Crypto::Hash::HashKind::SHA1; - else if (program_name == "sha256sum") - hash_kind = Crypto::Hash::HashKind::SHA256; - else if (program_name == "sha512sum") - hash_kind = Crypto::Hash::HashKind::SHA512; - - if (hash_kind == Crypto::Hash::HashKind::None) { - fprintf(stderr, "Error: program must be executed as 'md5sum', 'sha1sum', 'sha256sum' or 'sha512sum'; got '%s'\n", argv[0]); - exit(1); - } - - auto hash_name = program_name.substring_view(0, program_name.length() - 3).to_string().to_uppercase(); - auto paths_help_string = String::format("File(s) to print %s checksum of", hash_name.characters()); - - Vector paths; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(paths, paths_help_string.characters(), "path", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (paths.is_empty()) - paths.append("-"); - - Crypto::Hash::Manager hash; - hash.initialize(hash_kind); - - bool success; - auto has_error = false; - auto file = Core::File::construct(); - - for (auto path : paths) { - if (StringView { path } == "-") { - success = file->open(STDIN_FILENO, Core::IODevice::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No); - } else { - file->set_filename(path); - success = file->open(Core::IODevice::OpenMode::ReadOnly); - } - if (!success) { - fprintf(stderr, "%s: %s: %s\n", argv[0], path, file->error_string()); - has_error = true; - continue; - } - hash.update(file->read_all()); - auto digest = hash.digest(); - auto digest_data = digest.immutable_data(); - StringBuilder builder; - for (size_t i = 0; i < hash.digest_size(); ++i) - builder.appendf("%02x", digest_data[i]); - auto hash_sum_hex = builder.build(); - printf("%s %s\n", hash_sum_hex.characters(), path); - } - return has_error ? 1 : 0; -} diff --git a/Userland/chgrp.cpp b/Userland/chgrp.cpp deleted file mode 100644 index 18cb0475ab..0000000000 --- a/Userland/chgrp.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath chown", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* gid_arg = nullptr; - const char* path = nullptr; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Change the owning group for a file or directory."); - args_parser.add_positional_argument(gid_arg, "Group ID", "gid"); - args_parser.add_positional_argument(path, "Path to file", "path"); - args_parser.parse(argc, argv); - - gid_t new_gid = -1; - - if (String(gid_arg).is_empty()) { - fprintf(stderr, "Empty gid option\n"); - return 1; - } - - auto number = String(gid_arg).to_uint(); - if (number.has_value()) { - new_gid = number.value(); - } else { - auto* group = getgrnam(gid_arg); - if (!group) { - fprintf(stderr, "Unknown group '%s'\n", gid_arg); - return 1; - } - new_gid = group->gr_gid; - } - - int rc = chown(path, -1, new_gid); - if (rc < 0) { - perror("chgrp"); - return 1; - } - - return 0; -} diff --git a/Userland/chmod.cpp b/Userland/chmod.cpp deleted file mode 100644 index 4b3dd20089..0000000000 --- a/Userland/chmod.cpp +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -/* the new mode will be computed using the boolean function(for each bit): - - |current mode|removal mask|applying mask|result | - | 0 | 0 | 0 | 0 | - | 0 | 0 | 1 | 1 | - | 0 | 1 | 0 | 0 | - | 0 | 1 | 1 | 1 | ---> find the CNF --> find the minimal CNF - | 1 | 0 | 0 | 1 | - | 1 | 0 | 1 | 1 | - | 1 | 1 | 0 | 0 | - | 1 | 1 | 1 | 1 | -*/ - -class Mask { -private: - mode_t removal_mask; // the bits that will be removed - mode_t applying_mask; // the bits that will be set - -public: - Mask() - : removal_mask(0) - , applying_mask(0) - { - } - - Mask& operator|=(const Mask& other) - { - removal_mask |= other.removal_mask; - applying_mask |= other.applying_mask; - - return *this; - } - - mode_t& get_removal_mask() { return removal_mask; } - mode_t& get_applying_mask() { return applying_mask; } -}; - -Optional string_to_mode(char access_scope, const char*& access_string); -Optional apply_permission(char access_scope, char permission, char operation); - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath fattr", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (argc < 3) { - printf("usage: chmod \n" - " chmod [[ugoa][+-=][rwx...],...] \n"); - return 1; - } - - Mask mask; - - /* compute a mask */ - if (argv[1][0] >= '0' && argv[1][0] <= '7') { - if (sscanf(argv[1], "%ho", &mask.get_applying_mask()) != 1) { - perror("sscanf"); - return 1; - } - mask.get_removal_mask() = ~mask.get_applying_mask(); - } else { - const char* access_string = argv[1]; - - while (*access_string != '\0') { - Optional tmp_mask; - switch (*access_string) { - case 'u': - tmp_mask = string_to_mode('u', ++access_string); - break; - case 'g': - tmp_mask = string_to_mode('g', ++access_string); - break; - case 'o': - tmp_mask = string_to_mode('o', ++access_string); - break; - case 'a': - tmp_mask = string_to_mode('a', ++access_string); - break; - case '=': - case '+': - case '-': - tmp_mask = string_to_mode('a', access_string); - break; - case ',': - ++access_string; - continue; - } - if (!tmp_mask.has_value()) { - fprintf(stderr, "chmod: invalid mode: %s\n", argv[1]); - return 1; - } - mask |= tmp_mask.value(); - } - } - - /* set the mask for each file's permissions */ - struct stat current_access; - int i = 2; - while (i < argc) { - if (stat(argv[i], ¤t_access) != 0) { - perror("stat"); - return 1; - } - /* found the minimal CNF by The Quine–McCluskey algorithm and use it */ - mode_t mode = mask.get_applying_mask() - | (current_access.st_mode & ~mask.get_removal_mask()); - if (chmod(argv[i++], mode) != 0) { - perror("chmod"); - } - } - - return 0; -} - -Optional string_to_mode(char access_scope, const char*& access_string) -{ - char operation = *access_string; - - if (operation != '+' && operation != '-' && operation != '=') { - return {}; - } - - Mask mask; - if (operation == '=') { - switch (access_scope) { - case 'u': - mask.get_removal_mask() = (S_IRUSR | S_IWUSR | S_IXUSR); - break; - case 'g': - mask.get_removal_mask() = (S_IRGRP | S_IWGRP | S_IXGRP); - break; - case 'o': - mask.get_removal_mask() = (S_IROTH | S_IWOTH | S_IXOTH); - break; - case 'a': - mask.get_removal_mask() = (S_IRUSR | S_IWUSR | S_IXUSR - | S_IRGRP | S_IWGRP | S_IXGRP - | S_IROTH | S_IWOTH | S_IXOTH); - break; - } - operation = '+'; - } - - access_string++; - while (*access_string != '\0' && *access_string != ',') { - Optional tmp_mask; - tmp_mask = apply_permission(access_scope, *access_string, operation); - if (!tmp_mask.has_value()) { - return {}; - } - mask |= tmp_mask.value(); - access_string++; - } - - return mask; -} - -Optional apply_permission(char access_scope, char permission, char operation) -{ - if (permission != 'r' && permission != 'w' && permission != 'x') { - return {}; - } - - Mask mask; - mode_t tmp_mask = 0; - switch (access_scope) { - case 'u': - switch (permission) { - case 'r': - tmp_mask = S_IRUSR; - break; - case 'w': - tmp_mask = S_IWUSR; - break; - case 'x': - tmp_mask = S_IXUSR; - break; - } - break; - case 'g': - switch (permission) { - case 'r': - tmp_mask = S_IRGRP; - break; - case 'w': - tmp_mask = S_IWGRP; - break; - case 'x': - tmp_mask = S_IXGRP; - break; - } - break; - case 'o': - switch (permission) { - case 'r': - tmp_mask = S_IROTH; - break; - case 'w': - tmp_mask = S_IWOTH; - break; - case 'x': - tmp_mask = S_IXOTH; - break; - } - break; - case 'a': - mask |= apply_permission('u', permission, operation).value(); - mask |= apply_permission('g', permission, operation).value(); - mask |= apply_permission('o', permission, operation).value(); - break; - } - - if (operation == '+') { - mask.get_applying_mask() |= tmp_mask; - } else { - mask.get_removal_mask() |= tmp_mask; - } - - return mask; -} diff --git a/Userland/chown.cpp b/Userland/chown.cpp deleted file mode 100644 index 6e5040380b..0000000000 --- a/Userland/chown.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath chown", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (argc < 3) { - printf("usage: chown \n"); - return 0; - } - - uid_t new_uid = -1; - gid_t new_gid = -1; - - auto parts = String(argv[1]).split(':', true); - if (parts.is_empty()) { - fprintf(stderr, "Empty uid/gid spec\n"); - return 1; - } - if (parts[0].is_empty() || (parts.size() == 2 && parts[1].is_empty()) || parts.size() > 2) { - fprintf(stderr, "Invalid uid/gid spec\n"); - return 1; - } - - auto number = parts[0].to_uint(); - if (number.has_value()) { - new_uid = number.value(); - } else { - auto* passwd = getpwnam(parts[0].characters()); - if (!passwd) { - fprintf(stderr, "Unknown user '%s'\n", parts[0].characters()); - return 1; - } - new_uid = passwd->pw_uid; - } - - if (parts.size() == 2) { - auto number = parts[1].to_uint(); - if (number.has_value()) { - new_gid = number.value(); - } else { - auto* group = getgrnam(parts[1].characters()); - if (!group) { - fprintf(stderr, "Unknown group '%s'\n", parts[1].characters()); - return 1; - } - new_gid = group->gr_gid; - } - } - - int rc = chown(argv[2], new_uid, new_gid); - if (rc < 0) { - perror("chown"); - return 1; - } - - return 0; -} diff --git a/Userland/chroot.cpp b/Userland/chroot.cpp deleted file mode 100644 index 07c774ecda..0000000000 --- a/Userland/chroot.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - int flags = -1; - uid_t chroot_user = 0; - gid_t chroot_group = 0; - const char* path = nullptr; - const char* program = "/bin/Shell"; - const char* userspec = "0:0"; - - Core::ArgsParser args_parser; - args_parser.set_general_help( - "Run a program in a chroot sandbox. During execution, the program " - "sees the given path as '/', and cannot access files outside of it."); - args_parser.add_positional_argument(path, "New root directory", "path"); - args_parser.add_positional_argument(program, "Program to run", "program", Core::ArgsParser::Required::No); - - Core::ArgsParser::Option userspec_option { - true, - "The uid:gid to use", - "userspec", - 'u', - "userpec", - [&userspec](const char* s) { - Vector parts = StringView(s).split_view(':', true); - if (parts.size() != 2) - return false; - userspec = s; - return true; - } - }; - args_parser.add_option(move(userspec_option)); - - Core::ArgsParser::Option mount_options { - true, - "Mount options", - "options", - 'o', - "options", - [&flags](const char* s) { - flags = 0; - Vector parts = StringView(s).split_view(','); - for (auto& part : parts) { - if (part == "defaults") - continue; - else if (part == "nodev") - flags |= MS_NODEV; - else if (part == "noexec") - flags |= MS_NOEXEC; - else if (part == "nosuid") - flags |= MS_NOSUID; - else if (part == "ro") - flags |= MS_RDONLY; - else if (part == "remount") - flags |= MS_REMOUNT; - else if (part == "bind") - fprintf(stderr, "Ignoring -o bind, as it doesn't make sense for chroot\n"); - else - return false; - } - return true; - } - }; - args_parser.add_option(move(mount_options)); - args_parser.parse(argc, argv); - - if (chroot_with_mount_flags(path, flags) < 0) { - perror("chroot"); - return 1; - } - - if (chdir("/") < 0) { - perror("chdir(/)"); - return 1; - } - - // Failed parsing will silently fail open (uid=0; gid=0); - // 0:0 is also the default when no --userspec argument is provided. - auto parts = String(userspec).split(':', true); - chroot_user = (uid_t)strtol(parts[0].characters(), nullptr, 10); - chroot_group = (uid_t)strtol(parts[1].characters(), nullptr, 10); - - if (setresgid(chroot_group, chroot_group, chroot_group)) { - perror("setgid"); - return 1; - } - - if (setresuid(chroot_user, chroot_user, chroot_user)) { - perror("setuid"); - return 1; - } - - execl(program, program, nullptr); - perror("execl"); - return 1; -} diff --git a/Userland/clear.cpp b/Userland/clear.cpp deleted file mode 100644 index fd3f0506a4..0000000000 --- a/Userland/clear.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include - -int main(int, char**) -{ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - printf("\033[3J\033[H\033[2J"); - fflush(stdout); - return 0; -} diff --git a/Userland/copy.cpp b/Userland/copy.cpp deleted file mode 100644 index 90d2e04430..0000000000 --- a/Userland/copy.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct Options { - String data; - StringView type; -}; - -static Options parse_options(int argc, char* argv[]) -{ - const char* type = "text/plain"; - Vector text; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Copy text from stdin or the command-line to the clipboard."); - args_parser.add_option(type, "Pick a type", "type", 't', "type"); - args_parser.add_positional_argument(text, "Text to copy", "text", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - Options options; - options.type = type; - - if (text.is_empty()) { - // Copy our stdin. - auto c_stdin = Core::File::construct(); - bool success = c_stdin->open( - STDIN_FILENO, - Core::IODevice::OpenMode::ReadOnly, - Core::File::ShouldCloseFileDescriptor::No); - ASSERT(success); - auto buffer = c_stdin->read_all(); - dbgln("Read size {}", buffer.size()); - options.data = String((char*)buffer.data(), buffer.size()); - } else { - // Copy the rest of our command-line args. - StringBuilder builder; - builder.join(' ', text); - options.data = builder.to_string(); - } - - return options; -} - -int main(int argc, char* argv[]) -{ - auto app = GUI::Application::construct(argc, argv); - - Options options = parse_options(argc, argv); - - auto& clipboard = GUI::Clipboard::the(); - clipboard.set_data(options.data.bytes(), options.type); - - return 0; -} diff --git a/Userland/cp.cpp b/Userland/cp.cpp deleted file mode 100644 index ee640f6f3d..0000000000 --- a/Userland/cp.cpp +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -bool copy_file_or_directory(String, String, bool, bool); -bool copy_file(String, String, const struct stat&, int); -bool copy_directory(String, String, bool); - -static mode_t my_umask = 0; - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath wpath cpath fattr", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool link = false; - bool recursion_allowed = false; - bool verbose = false; - Vector sources; - const char* destination = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(link, "Link files instead of copying", "link", 'l'); - args_parser.add_option(recursion_allowed, "Copy directories recursively", "recursive", 'R'); - args_parser.add_option(recursion_allowed, "Same as -R", nullptr, 'r'); - args_parser.add_option(verbose, "Verbose", "verbose", 'v'); - args_parser.add_positional_argument(sources, "Source file path", "source"); - args_parser.add_positional_argument(destination, "Destination file path", "destination"); - args_parser.parse(argc, argv); - - auto my_umask = umask(0); - umask(my_umask); - - for (auto& source : sources) { - bool ok = copy_file_or_directory(source, destination, recursion_allowed, link); - if (!ok) - return 1; - if (verbose) - printf("'%s' -> '%s'\n", source, destination); - } - return 0; -} - -/** - * Copy a file or directory to a new location. Returns true if successful, false - * otherwise. If there is an error, its description is output to stderr. - * - * Directories should only be copied if recursion_allowed is set. - */ -bool copy_file_or_directory(String src_path, String dst_path, bool recursion_allowed, bool link) -{ - int src_fd = open(src_path.characters(), O_RDONLY); - if (src_fd < 0) { - perror("open src"); - return false; - } - - struct stat src_stat; - int rc = fstat(src_fd, &src_stat); - if (rc < 0) { - perror("stat src"); - return false; - } - - if (S_ISDIR(src_stat.st_mode)) { - if (!recursion_allowed) { - fprintf(stderr, "cp: -R not specified; omitting directory '%s'\n", src_path.characters()); - return false; - } - return copy_directory(src_path, dst_path, link); - } - if (link) { - if (::link(src_path.characters(), dst_path.characters()) < 0) { - perror("link"); - return false; - } - return true; - } - - return copy_file(src_path, dst_path, src_stat, src_fd); -} - -/** - * Copy a source file to a destination file. Returns true if successful, false - * otherwise. If there is an error, its description is output to stderr. - * - * To avoid repeated work, the source file's stat and file descriptor are required. - */ -bool copy_file(String src_path, String dst_path, const struct stat& src_stat, int src_fd) -{ - // NOTE: We don't copy the set-uid and set-gid bits. - mode_t mode = (src_stat.st_mode & ~my_umask) & ~06000; - - int dst_fd = creat(dst_path.characters(), mode); - if (dst_fd < 0) { - if (errno != EISDIR) { - perror("open dst"); - return false; - } - StringBuilder builder; - builder.append(dst_path); - builder.append('/'); - builder.append(LexicalPath(src_path).basename()); - dst_path = builder.to_string(); - dst_fd = creat(dst_path.characters(), 0666); - if (dst_fd < 0) { - perror("open dst"); - return false; - } - } - - if (src_stat.st_size > 0) { - if (ftruncate(dst_fd, src_stat.st_size) < 0) { - perror("cp: ftruncate"); - return false; - } - } - - for (;;) { - char buffer[32768]; - ssize_t nread = read(src_fd, buffer, sizeof(buffer)); - if (nread < 0) { - perror("read src"); - return false; - } - if (nread == 0) - break; - ssize_t remaining_to_write = nread; - char* bufptr = buffer; - while (remaining_to_write) { - ssize_t nwritten = write(dst_fd, bufptr, remaining_to_write); - if (nwritten < 0) { - perror("write dst"); - return false; - } - assert(nwritten > 0); - remaining_to_write -= nwritten; - bufptr += nwritten; - } - } - - close(src_fd); - close(dst_fd); - return true; -} - -/** - * Copy the contents of a source directory into a destination directory. - */ -bool copy_directory(String src_path, String dst_path, bool link) -{ - int rc = mkdir(dst_path.characters(), 0755); - if (rc < 0) { - perror("cp: mkdir"); - return false; - } - - String src_rp = Core::File::real_path_for(src_path); - src_rp = String::format("%s/", src_rp.characters()); - String dst_rp = Core::File::real_path_for(dst_path); - dst_rp = String::format("%s/", dst_rp.characters()); - - if (!dst_rp.is_empty() && dst_rp.starts_with(src_rp)) { - fprintf(stderr, "cp: Cannot copy %s into itself (%s)\n", - src_path.characters(), dst_path.characters()); - return false; - } - - Core::DirIterator di(src_path, Core::DirIterator::SkipDots); - if (di.has_error()) { - fprintf(stderr, "cp: DirIterator: %s\n", di.error_string()); - return false; - } - while (di.has_next()) { - String filename = di.next_path(); - bool is_copied = copy_file_or_directory( - String::format("%s/%s", src_path.characters(), filename.characters()), - String::format("%s/%s", dst_path.characters(), filename.characters()), - true, link); - if (!is_copied) { - return false; - } - } - return true; -} diff --git a/Userland/crash.cpp b/Userland/crash.cpp deleted file mode 100644 index 5f4c763a4f..0000000000 --- a/Userland/crash.cpp +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2019-2020, Shannon Booth - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#pragma GCC optimize("O0") - -class Crash { -public: - enum class RunType { - UsingChildProcess, - UsingCurrentProcess, - }; - - enum class Failure { - DidNotCrash, - UnexpectedError, - }; - - Crash(String test_type, Function crash_function) - : m_type(test_type) - , m_crash_function(move(crash_function)) - { - } - - void run(RunType run_type) - { - printf("\x1B[33mTesting\x1B[0m: \"%s\"\n", m_type.characters()); - - auto run_crash_and_print_if_error = [this]() { - auto failure = m_crash_function(); - - // If we got here something went wrong - printf("\x1B[31mFAIL\x1B[0m: "); - switch (failure) { - case Failure::DidNotCrash: - printf("Did not crash!\n"); - break; - case Failure::UnexpectedError: - printf("Unexpected error!\n"); - break; - default: - ASSERT_NOT_REACHED(); - } - }; - - if (run_type == RunType::UsingCurrentProcess) { - run_crash_and_print_if_error(); - } else { - - // Run the test in a child process so that we do not crash the crash program :^) - pid_t pid = fork(); - if (pid < 0) { - perror("fork"); - ASSERT_NOT_REACHED(); - } else if (pid == 0) { - run_crash_and_print_if_error(); - exit(0); - } - - int status; - waitpid(pid, &status, 0); - if (WIFSIGNALED(status)) - printf("\x1B[32mPASS\x1B[0m: Terminated with signal %d\n", WTERMSIG(status)); - } - } - -private: - String m_type; - Function m_crash_function; -}; - -int main(int argc, char** argv) -{ - bool do_all_crash_types = false; - bool do_segmentation_violation = false; - bool do_division_by_zero = false; - bool do_illegal_instruction = false; - bool do_abort = false; - bool do_write_to_uninitialized_malloc_memory = false; - bool do_write_to_freed_memory = false; - bool do_write_to_read_only_memory = false; - bool do_read_from_uninitialized_malloc_memory = false; - bool do_read_from_freed_memory = false; - bool do_invalid_stack_pointer_on_syscall = false; - bool do_invalid_stack_pointer_on_page_fault = false; - bool do_syscall_from_writeable_memory = false; - bool do_execute_non_executable_memory = false; - bool do_trigger_user_mode_instruction_prevention = false; - bool do_use_io_instruction = false; - bool do_read_cpu_counter = false; - - auto args_parser = Core::ArgsParser(); - args_parser.set_general_help( - "Exercise error-handling paths of the execution environment " - "(i.e., Kernel or UE) by crashing in many different ways."); - args_parser.add_option(do_all_crash_types, "Test that all of the following crash types crash as expected", nullptr, 'A'); - args_parser.add_option(do_segmentation_violation, "Perform a segmentation violation by dereferencing an invalid pointer", nullptr, 's'); - args_parser.add_option(do_division_by_zero, "Perform a division by zero", nullptr, 'd'); - args_parser.add_option(do_illegal_instruction, "Execute an illegal CPU instruction", nullptr, 'i'); - args_parser.add_option(do_abort, "Call `abort()`", nullptr, 'a'); - args_parser.add_option(do_read_from_uninitialized_malloc_memory, "Read a pointer from uninitialized malloc memory, then read from it", nullptr, 'm'); - args_parser.add_option(do_read_from_freed_memory, "Read a pointer from memory freed using `free()`, then read from it", nullptr, 'f'); - args_parser.add_option(do_write_to_uninitialized_malloc_memory, "Read a pointer from uninitialized malloc memory, then write to it", nullptr, 'M'); - args_parser.add_option(do_write_to_freed_memory, "Read a pointer from memory freed using `free()`, then write to it", nullptr, 'F'); - args_parser.add_option(do_write_to_read_only_memory, "Write to read-only memory", nullptr, 'r'); - args_parser.add_option(do_invalid_stack_pointer_on_syscall, "Make a syscall while using an invalid stack pointer", nullptr, 'T'); - args_parser.add_option(do_invalid_stack_pointer_on_page_fault, "Trigger a page fault while using an invalid stack pointer", nullptr, 't'); - args_parser.add_option(do_syscall_from_writeable_memory, "Make a syscall from writeable memory", nullptr, 'S'); - args_parser.add_option(do_execute_non_executable_memory, "Attempt to execute non-executable memory (not mapped with PROT_EXEC)", nullptr, 'X'); - args_parser.add_option(do_trigger_user_mode_instruction_prevention, "Attempt to trigger an x86 User Mode Instruction Prevention fault", nullptr, 'U'); - args_parser.add_option(do_use_io_instruction, "Use an x86 I/O instruction in userspace", nullptr, 'I'); - args_parser.add_option(do_read_cpu_counter, "Read the x86 TSC (Time Stamp Counter) directly", nullptr, 'c'); - - if (argc != 2) { - args_parser.print_usage(stderr, argv[0]); - exit(1); - } - - args_parser.parse(argc, argv); - - Crash::RunType run_type = do_all_crash_types ? Crash::RunType::UsingChildProcess - : Crash::RunType::UsingCurrentProcess; - - if (do_segmentation_violation || do_all_crash_types) { - Crash("Segmentation violation", []() { - volatile int* crashme = nullptr; - *crashme = 0xbeef; - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_division_by_zero || do_all_crash_types) { - Crash("Division by zero", []() { - volatile int lala = 10; - volatile int zero = 0; - [[maybe_unused]] volatile int test = lala / zero; - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_illegal_instruction || do_all_crash_types) { - Crash("Illegal instruction", []() { - asm volatile("ud2"); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_abort || do_all_crash_types) { - Crash("Abort", []() { - abort(); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_read_from_uninitialized_malloc_memory || do_all_crash_types) { - Crash("Read from uninitialized malloc memory", []() { - auto* uninitialized_memory = (volatile u32**)malloc(1024); - if (!uninitialized_memory) - return Crash::Failure::UnexpectedError; - - [[maybe_unused]] volatile auto x = uninitialized_memory[0][0]; - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_read_from_uninitialized_malloc_memory || do_all_crash_types) { - Crash("Read from freed memory", []() { - auto* uninitialized_memory = (volatile u32**)malloc(1024); - if (!uninitialized_memory) - return Crash::Failure::UnexpectedError; - - free(uninitialized_memory); - [[maybe_unused]] volatile auto x = uninitialized_memory[4][0]; - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_write_to_uninitialized_malloc_memory || do_all_crash_types) { - Crash("Write to uninitialized malloc memory", []() { - auto* uninitialized_memory = (volatile u32**)malloc(1024); - if (!uninitialized_memory) - return Crash::Failure::UnexpectedError; - - uninitialized_memory[4][0] = 1; - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_write_to_freed_memory || do_all_crash_types) { - Crash("Write to freed memory", []() { - auto* uninitialized_memory = (volatile u32**)malloc(1024); - if (!uninitialized_memory) - return Crash::Failure::UnexpectedError; - - free(uninitialized_memory); - uninitialized_memory[4][0] = 1; - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_write_to_read_only_memory || do_all_crash_types) { - Crash("Write to read only memory", []() { - auto* ptr = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANON, 0, 0); - if (ptr != MAP_FAILED) - return Crash::Failure::UnexpectedError; - - *ptr = 'x'; // This should work fine. - int rc = mprotect(ptr, 4096, PROT_READ); - if (rc != 0 || *ptr != 'x') - return Crash::Failure::UnexpectedError; - - *ptr = 'y'; // This should crash! - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_invalid_stack_pointer_on_syscall || do_all_crash_types) { - Crash("Invalid stack pointer on syscall", []() { - u8* makeshift_stack = (u8*)mmap(nullptr, 0, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK, 0, 0); - if (!makeshift_stack) - return Crash::Failure::UnexpectedError; - - u8* makeshift_esp = makeshift_stack + 2048; - asm volatile("mov %%eax, %%esp" ::"a"(makeshift_esp)); - getuid(); - dbgln("Survived syscall with MAP_STACK stack"); - - u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - if (!bad_stack) - return Crash::Failure::UnexpectedError; - - u8* bad_esp = bad_stack + 2048; - asm volatile("mov %%eax, %%esp" ::"a"(bad_esp)); - getuid(); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_invalid_stack_pointer_on_page_fault || do_all_crash_types) { - Crash("Invalid stack pointer on page fault", []() { - u8* bad_stack = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - if (!bad_stack) - return Crash::Failure::UnexpectedError; - - u8* bad_esp = bad_stack + 2048; - asm volatile("mov %%eax, %%esp" ::"a"(bad_esp)); - asm volatile("pushl $0"); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_syscall_from_writeable_memory || do_all_crash_types) { - Crash("Syscall from writable memory", []() { - u8 buffer[] = { 0xb8, Syscall::SC_getuid, 0, 0, 0, 0xcd, 0x82 }; - ((void (*)())buffer)(); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_execute_non_executable_memory || do_all_crash_types) { - Crash("Execute non executable memory", []() { - auto* ptr = (u8*)mmap(nullptr, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - if (ptr == MAP_FAILED) - return Crash::Failure::UnexpectedError; - - ptr[0] = 0xc3; // ret - typedef void* (*CrashyFunctionPtr)(); - ((CrashyFunctionPtr)ptr)(); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_trigger_user_mode_instruction_prevention || do_all_crash_types) { - Crash("Trigger x86 User Mode Instruction Prevention", []() { - asm volatile("str %eax"); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_use_io_instruction || do_all_crash_types) { - Crash("Attempt to use an I/O instruction", [] { - u8 keyboard_status = IO::in8(0x64); - printf("Keyboard status: %#02x\n", keyboard_status); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - if (do_read_cpu_counter || do_all_crash_types) { - Crash("Read the CPU timestamp counter", [] { - asm volatile("rdtsc"); - return Crash::Failure::DidNotCrash; - }).run(run_type); - } - - return 0; -} diff --git a/Userland/cut.cpp b/Userland/cut.cpp deleted file mode 100644 index f3eee67e1c..0000000000 --- a/Userland/cut.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2019-2020, Marios Prokopakis - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -struct Index { - enum class Type { - SingleIndex, - SliceIndex, - RangedIndex - }; - ssize_t m_from { -1 }; - ssize_t m_to { -1 }; - Type m_type { Type::SingleIndex }; - - bool intersects(const Index& other) - { - if (m_type != Type::RangedIndex) - return m_from == other.m_from; - - return !(other.m_from > m_to || other.m_to < m_from); - } -}; - -static void print_usage_and_exit(int ret) -{ - printf("Usage: cut -b list [File]\n"); - exit(ret); -} - -static void add_if_not_exists(Vector& indexes, Index data) -{ - bool append_to_vector = true; - for (auto& index : indexes) { - if (index.intersects(data)) { - if (index.m_type == Index::Type::RangedIndex) { - index.m_from = AK::min(index.m_from, data.m_from); - index.m_to = AK::max(index.m_to, data.m_to); - } - append_to_vector = false; - } - } - - if (append_to_vector) { - indexes.append(data); - } -} - -static void expand_list(Vector& tokens, Vector& indexes) -{ - for (auto& token : tokens) { - if (token.length() == 0) { - fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); - print_usage_and_exit(1); - } - - if (token == "-") { - fprintf(stderr, "cut: invalid range with no endpoint: %s\n", token.characters()); - print_usage_and_exit(1); - } - - if (token[0] == '-') { - auto index = token.substring(1, token.length() - 1).to_int(); - if (!index.has_value()) { - fprintf(stderr, "cut: invalid byte/character position '%s'\n", token.characters()); - print_usage_and_exit(1); - } - - if (index.value() == 0) { - fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); - print_usage_and_exit(1); - } - - Index tmp = { 1, index.value(), Index::Type::RangedIndex }; - add_if_not_exists(indexes, tmp); - } else if (token[token.length() - 1] == '-') { - auto index = token.substring(0, token.length() - 1).to_int(); - if (!index.has_value()) { - fprintf(stderr, "cut: invalid byte/character position '%s'\n", token.characters()); - print_usage_and_exit(1); - } - - if (index.value() == 0) { - fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); - print_usage_and_exit(1); - } - Index tmp = { index.value(), -1, Index::Type::SliceIndex }; - add_if_not_exists(indexes, tmp); - } else { - auto range = token.split('-'); - if (range.size() == 2) { - auto index1 = range[0].to_int(); - if (!index1.has_value()) { - fprintf(stderr, "cut: invalid byte/character position '%s'\n", range[0].characters()); - print_usage_and_exit(1); - } - - auto index2 = range[1].to_int(); - if (!index2.has_value()) { - fprintf(stderr, "cut: invalid byte/character position '%s'\n", range[1].characters()); - print_usage_and_exit(1); - } - - if (index1.value() > index2.value()) { - fprintf(stderr, "cut: invalid decreasing range\n"); - print_usage_and_exit(1); - } else if (index1.value() == 0 || index2.value() == 0) { - fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); - print_usage_and_exit(1); - } - - Index tmp = { index1.value(), index2.value(), Index::Type::RangedIndex }; - add_if_not_exists(indexes, tmp); - } else if (range.size() == 1) { - auto index = range[0].to_int(); - if (!index.has_value()) { - fprintf(stderr, "cut: invalid byte/character position '%s'\n", range[0].characters()); - print_usage_and_exit(1); - } - - if (index.value() == 0) { - fprintf(stderr, "cut: byte/character positions are numbered from 1\n"); - print_usage_and_exit(1); - } - - Index tmp = { index.value(), index.value(), Index::Type::SingleIndex }; - add_if_not_exists(indexes, tmp); - } else { - fprintf(stderr, "cut: invalid byte or character range\n"); - print_usage_and_exit(1); - } - } - } -} - -static void cut_file(const String& file, const Vector& byte_vector) -{ - FILE* fp = stdin; - if (!file.is_null()) { - fp = fopen(file.characters(), "r"); - if (!fp) { - fprintf(stderr, "cut: Could not open file '%s'\n", file.characters()); - return; - } - } - - char* line = nullptr; - ssize_t line_length = 0; - size_t line_capacity = 0; - while ((line_length = getline(&line, &line_capacity, fp)) != -1) { - line[line_length - 1] = '\0'; - line_length--; - for (auto& i : byte_vector) { - if (i.m_type == Index::Type::SliceIndex && i.m_from < line_length) - printf("%s", line + i.m_from - 1); - else if (i.m_type == Index::Type::SingleIndex && i.m_from <= line_length) - printf("%c", line[i.m_from - 1]); - else if (i.m_type == Index::Type::RangedIndex && i.m_from <= line_length) { - auto to = i.m_to > line_length ? line_length : i.m_to; - auto sub_string = String(line).substring(i.m_from - 1, to - i.m_from + 1); - printf("%s", sub_string.characters()); - } else - break; - } - printf("\n"); - } - - if (line) - free(line); - - if (!file.is_null()) - fclose(fp); -} - -int main(int argc, char** argv) -{ - String byte_list = ""; - Vector tokens; - Vector files; - if (argc == 1) { - print_usage_and_exit(1); - } - - for (int i = 1; i < argc;) { - if (!strcmp(argv[i], "-b")) { - /* The next argument should be a list of bytes. */ - byte_list = (i + 1 < argc) ? argv[i + 1] : ""; - - if (byte_list == "") { - print_usage_and_exit(1); - } - tokens = byte_list.split(','); - i += 2; - } else if (!strcmp(argv[i], "--help") || !strcmp(argv[i], "-h")) { - print_usage_and_exit(1); - } else if (argv[i][0] != '-') { - files.append(argv[i++]); - } else { - fprintf(stderr, "cut: invalid argument %s\n", argv[i]); - print_usage_and_exit(1); - } - } - - if (byte_list == "") - print_usage_and_exit(1); - - Vector byte_vector; - expand_list(tokens, byte_vector); - quick_sort(byte_vector, [](auto& a, auto& b) { return a.m_from < b.m_from; }); - - if (files.is_empty()) - files.append(String()); - - /* Process each file */ - for (auto& file : files) - cut_file(file, byte_vector); - - return 0; -} diff --git a/Userland/date.cpp b/Userland/date.cpp deleted file mode 100644 index 057112a4c3..0000000000 --- a/Userland/date.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio settime", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool print_unix_date = false; - bool print_iso_8601 = false; - bool print_rfc_3339 = false; - bool print_rfc_5322 = false; - const char* set_date = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(set_date, "Set system date and time", "set", 's', "date"); - args_parser.add_option(print_unix_date, "Print date as Unix timestamp", "unix", 'u'); - args_parser.add_option(print_iso_8601, "Print date in ISO 8601 format", "iso-8601", 'i'); - args_parser.add_option(print_rfc_3339, "Print date in RFC 3339 format", "rfc-3339", 'r'); - args_parser.add_option(print_rfc_5322, "Print date in RFC 5322 format", "rfc-5322", 'R'); - args_parser.parse(argc, argv); - - if (set_date != nullptr) { - auto number = String(set_date).to_uint(); - - if (!number.has_value()) { - fprintf(stderr, "date: Invalid timestamp value"); - return 1; - } - - timespec ts = { number.value(), 0 }; - if (clock_settime(CLOCK_REALTIME, &ts) < 0) { - perror("clock_settime"); - return 1; - } - - return 0; - } - - // FIXME: this should be improved and will need to be cleaned up - // when additional output formats and formatting is supported - if (print_unix_date && print_iso_8601 && print_rfc_3339 && print_rfc_5322) { - fprintf(stderr, "date: multiple output formats specified\n"); - return 1; - } - - time_t now = time(nullptr); - auto date = Core::DateTime::from_timestamp(now); - - if (print_unix_date) { - printf("%lld\n", (long long)now); - return 0; - } else if (print_iso_8601) { - printf("%s\n", date.to_string("%Y-%m-%dT%H:%M:%S-00:00").characters()); - return 0; - } else if (print_rfc_5322) { - printf("%s\n", date.to_string("%a, %d %b %Y %H:%M:%S -0000").characters()); - return 0; - } else if (print_rfc_3339) { - printf("%s\n", date.to_string("%Y-%m-%d %H:%M:%S-00:00").characters()); - return 0; - } else { - printf("%s\n", date.to_string().characters()); - return 0; - } -} diff --git a/Userland/ddate.cpp b/Userland/ddate.cpp deleted file mode 100644 index 763a5a3493..0000000000 --- a/Userland/ddate.cpp +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2021, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -class DiscordianDate { -public: - explicit DiscordianDate(Core::DateTime date) - { - m_gregorian_date = date; - m_yold = date.year() + 1166; - - uint16_t day = day_of_yold() + 1; - if (is_leap_year() && day > m_st_tibs_day_of_yold) - --day; - - m_day_of_week = day_of_week_from_day_of_yold(day); - m_season = season_from_day_of_yold(day); - m_date = date_from_day_of_yold(day); - } - - const char* day_of_week() { return m_day_of_week.characters(); }; - const char* season() { return m_season.characters(); }; - uint16_t year() { return yold(); }; - uint16_t yold() { return m_yold; }; - uint16_t day_of_year() { return day_of_yold(); }; - uint16_t day_of_yold() { return m_gregorian_date.day_of_year(); }; - bool is_leap_year() { return m_gregorian_date.is_leap_year(); }; - bool is_st_tibs_day() { return (is_leap_year() && (day_of_yold() + 1) == m_st_tibs_day_of_yold); }; - - String to_string() - { - if (is_st_tibs_day()) - return String::formatted("St. Tib's Day, in the YOLD {}", m_yold); - return String::formatted("{}, day {} of {}, in the YOLD {}", m_day_of_week, m_date, m_season, m_yold); - } - -private: - static const int m_days_in_week = 5; - static const int m_days_in_season = 73; - static const int m_st_tibs_day_of_yold = 60; - Core::DateTime m_gregorian_date; - String m_day_of_week; - String m_season; - int m_date; - uint64_t m_yold; // 64-bit for use after X-Day in the YOLD 8661 - - int date_from_day_of_yold(uint16_t day) - { - return (day % m_days_in_season == 0 ? m_days_in_season : day % m_days_in_season); - } - - const char* day_of_week_from_day_of_yold(uint16_t day) - { - if (is_st_tibs_day()) - return nullptr; - - switch ((day % m_days_in_week) == 0 ? m_days_in_week : (day % m_days_in_week)) { - case 1: - return "Sweetmorn"; - case 2: - return "Boomtime"; - case 3: - return "Pungenday"; - case 4: - return "Prickle-Prickle"; - case 5: - return "Setting Orange"; - default: - return nullptr; - } - } - - const char* season_from_day_of_yold(uint16_t day) - { - if (is_st_tibs_day()) - return nullptr; - - switch (((day % m_days_in_season) == 0 ? day - 1 : day) / m_days_in_season) { - case 0: - return "Chaos"; - case 1: - return "Discord"; - case 2: - return "Confusion"; - case 3: - return "Bureaucracy"; - case 4: - return "The Aftermath"; - default: - return nullptr; - } - } -}; - -int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) -{ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - auto date = Core::DateTime::from_timestamp(time(nullptr)); - printf("Today is %s\n", DiscordianDate(date).to_string().characters()); - - return 0; -} diff --git a/Userland/df.cpp b/Userland/df.cpp deleted file mode 100644 index 53a79eb7aa..0000000000 --- a/Userland/df.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static bool flag_human_readable = false; - -struct FileSystem { - String fs; - size_t total_block_count { 0 }; - size_t free_block_count { 0 }; - size_t total_inode_count { 0 }; - size_t free_inode_count { 0 }; - size_t block_size { 0 }; - String mount_point; -}; - -int main(int argc, char** argv) -{ - Core::ArgsParser args_parser; - args_parser.set_general_help("Display free disk space of each partition."); - args_parser.add_option(flag_human_readable, "Print human-readable sizes", "human-readable", 'h'); - args_parser.parse(argc, argv); - - auto file = Core::File::construct("/proc/df"); - if (!file->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Failed to open /proc/df: %s\n", file->error_string()); - return 1; - } - - if (flag_human_readable) { - printf("Filesystem Size Used Available Mount point\n"); - } else { - printf("Filesystem Blocks Used Available Mount point\n"); - } - - auto file_contents = file->read_all(); - auto json_result = JsonValue::from_string(file_contents); - ASSERT(json_result.has_value()); - auto json = json_result.value().as_array(); - json.for_each([](auto& value) { - auto fs_object = value.as_object(); - auto fs = fs_object.get("class_name").to_string(); - auto total_block_count = fs_object.get("total_block_count").to_u32(); - auto free_block_count = fs_object.get("free_block_count").to_u32(); - [[maybe_unused]] auto total_inode_count = fs_object.get("total_inode_count").to_u32(); - [[maybe_unused]] auto free_inode_count = fs_object.get("free_inode_count").to_u32(); - auto block_size = fs_object.get("block_size").to_u32(); - auto mount_point = fs_object.get("mount_point").to_string(); - - printf("%-10s", fs.characters()); - - if (flag_human_readable) { - printf("%10s ", human_readable_size(total_block_count * block_size).characters()); - printf("%10s ", human_readable_size((total_block_count - free_block_count) * block_size).characters()); - printf("%10s ", human_readable_size(free_block_count * block_size).characters()); - } else { - printf("%10u ", total_block_count); - printf("%10u ", total_block_count - free_block_count); - printf("%10u ", free_block_count); - } - - printf("%s", mount_point.characters()); - printf("\n"); - }); - - return 0; -} diff --git a/Userland/dirname.cpp b/Userland/dirname.cpp deleted file mode 100644 index 2d13c9eb15..0000000000 --- a/Userland/dirname.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include - -int main(int argc, char** argv) -{ - const char* path = nullptr; - Core::ArgsParser args_parser; - args_parser.add_positional_argument(path, "Path", "path"); - args_parser.parse(argc, argv); - - outln("{}", LexicalPath(path).dirname()); - return 0; -} diff --git a/Userland/disasm.cpp b/Userland/disasm.cpp deleted file mode 100644 index 781caf0796..0000000000 --- a/Userland/disasm.cpp +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//#define DISASM_DUMP - -int main(int argc, char** argv) -{ - const char* path = nullptr; - - Core::ArgsParser args_parser; - args_parser.set_general_help( - "Disassemble an executable, and show human-readable " - "assembly code for each function."); - args_parser.add_positional_argument(path, "Path to i386 binary file", "path"); - args_parser.parse(argc, argv); - - auto file_or_error = MappedFile::map(path); - if (file_or_error.is_error()) { - warnln("Could not map file: {}", file_or_error.error().string()); - return 1; - } - - auto& file = *file_or_error.value(); - - struct Symbol { - size_t value; - size_t size; - StringView name; - - size_t address() const { return value; } - size_t address_end() const { return value + size; } - - bool contains(size_t virtual_address) { return address() <= virtual_address && virtual_address < address_end(); } - }; - Vector symbols; - - const u8* asm_data = (const u8*)file.data(); - size_t asm_size = file.size(); - size_t file_offset = 0; - Vector::Iterator current_symbol = symbols.begin(); - OwnPtr symbol_provider; // nullptr for non-ELF disassembly. - OwnPtr elf; - if (asm_size >= 4 && strncmp((const char*)asm_data, "\u007fELF", 4) == 0) { - elf = make(asm_data, asm_size); - if (elf->is_valid()) { - symbol_provider = make(*elf); - elf->for_each_section_of_type(SHT_PROGBITS, [&](const ELF::Image::Section& section) { - // FIXME: Disassemble all SHT_PROGBITS sections, not just .text. - if (section.name() != ".text") - return IterationDecision::Continue; - asm_data = (const u8*)section.raw_data(); - asm_size = section.size(); - file_offset = section.address(); - return IterationDecision::Break; - }); - symbols.ensure_capacity(elf->symbol_count() + 1); - symbols.append({ 0, 0, StringView() }); // Sentinel. - elf->for_each_symbol([&](const ELF::Image::Symbol& symbol) { - symbols.append({ symbol.value(), symbol.size(), symbol.name() }); - return IterationDecision::Continue; - }); - quick_sort(symbols, [](auto& a, auto& b) { - if (a.value != b.value) - return a.value < b.value; - if (a.size != b.size) - return a.size < b.size; - return a.name < b.name; - }); -#ifdef DISASM_DUMP - for (size_t i = 0; i < symbols.size(); ++i) - dbg() << symbols[i].name << ": " << (void*)(uintptr_t)symbols[i].value << ", " << symbols[i].size; -#endif - } - } - - X86::SimpleInstructionStream stream(asm_data, asm_size); - X86::Disassembler disassembler(stream); - - bool is_first_symbol = true; - bool current_instruction_is_in_symbol = false; - - for (;;) { - auto offset = stream.offset(); - auto insn = disassembler.next(); - if (!insn.has_value()) - break; - - // Prefix regions of instructions belonging to a symbol with the symbol's name. - // Separate regions of instructions belonging to distinct symbols with newlines, - // and separate regions of instructions not belonging to symbols from regions belonging to symbols with newlines. - // Interesting cases: - // - More than 1 symbol covering a region of instructions (ICF, D1/D2) - // - Symbols of size 0 that don't cover any instructions but are at an address (want to print them, separated from instructions both before and after) - // Invariant: current_symbol is the largest instruction containing insn, or it is the largest instruction that has an address less than the instruction's address. - size_t virtual_offset = file_offset + offset; - if (current_symbol < symbols.end() && !current_symbol->contains(virtual_offset)) { - if (!is_first_symbol && current_instruction_is_in_symbol) { - // The previous instruction was part of a symbol that doesn't cover the current instruction, so separate it from the current instruction with a newline. - outln(); - current_instruction_is_in_symbol = (current_symbol + 1 < symbols.end() && (current_symbol + 1)->contains(virtual_offset)); - } - - // Try to find symbol covering current instruction, if one exists. - while (current_symbol + 1 < symbols.end() && !(current_symbol + 1)->contains(virtual_offset) && (current_symbol + 1)->address() <= virtual_offset) { - ++current_symbol; - if (!is_first_symbol) - outln("\n({} ({:p}-{:p}))\n", current_symbol->name, current_symbol->address(), current_symbol->address_end()); - } - while (current_symbol + 1 < symbols.end() && (current_symbol + 1)->contains(virtual_offset)) { - if (!is_first_symbol && !current_instruction_is_in_symbol) - outln(); - ++current_symbol; - current_instruction_is_in_symbol = true; - outln("{} ({:p}-{:p}):", current_symbol->name, current_symbol->address(), current_symbol->address_end()); - } - - is_first_symbol = false; - } - - outln("{:p} {}", virtual_offset, insn.value().to_string(virtual_offset, symbol_provider)); - } -} diff --git a/Userland/disk_benchmark.cpp b/Userland/disk_benchmark.cpp deleted file mode 100644 index de3ff19849..0000000000 --- a/Userland/disk_benchmark.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct Result { - u64 write_bps; - u64 read_bps; -}; - -static Result average_result(const Vector& results) -{ - Result average; - - for (auto& res : results) { - average.write_bps += res.write_bps; - average.read_bps += res.read_bps; - } - - average.write_bps /= results.size(); - average.read_bps /= results.size(); - - return average; -} - -static void exit_with_usage(int rc) -{ - fprintf(stderr, "Usage: disk_benchmark [-h] [-d directory] [-t time_per_benchmark] [-f file_size1,file_size2,...] [-b block_size1,block_size2,...]\n"); - exit(rc); -} - -static Result benchmark(const String& filename, int file_size, int block_size, ByteBuffer& buffer, bool allow_cache); - -int main(int argc, char** argv) -{ - char* directory = strdup("."); - int time_per_benchmark = 10; - Vector file_sizes; - Vector block_sizes; - bool allow_cache = false; - - int opt; - while ((opt = getopt(argc, argv, "chd:t:f:b:")) != -1) { - switch (opt) { - case 'h': - exit_with_usage(0); - break; - case 'c': - allow_cache = true; - break; - case 'd': - directory = strdup(optarg); - break; - case 't': - time_per_benchmark = atoi(optarg); - break; - case 'f': - for (auto size : String(optarg).split(',')) - file_sizes.append(atoi(size.characters())); - break; - case 'b': - for (auto size : String(optarg).split(',')) - block_sizes.append(atoi(size.characters())); - break; - } - } - - if (file_sizes.size() == 0) { - file_sizes = { 131072, 262144, 524288, 1048576, 5242880 }; - } - if (block_sizes.size() == 0) { - block_sizes = { 8192, 32768, 65536 }; - } - - umask(0644); - - auto filename = String::format("%s/disk_benchmark.tmp", directory); - - for (auto file_size : file_sizes) { - for (auto block_size : block_sizes) { - if (block_size > file_size) - continue; - - auto buffer = ByteBuffer::create_uninitialized(block_size); - - Vector results; - - printf("Running: file_size=%d block_size=%d\n", file_size, block_size); - Core::ElapsedTimer timer; - timer.start(); - while (timer.elapsed() < time_per_benchmark * 1000) { - printf("."); - fflush(stdout); - results.append(benchmark(filename, file_size, block_size, buffer, allow_cache)); - usleep(100); - } - auto average = average_result(results); - printf("\nFinished: runs=%zu time=%dms write_bps=%llu read_bps=%llu\n", results.size(), timer.elapsed(), average.write_bps, average.read_bps); - - sleep(1); - } - } - - if (isatty(0)) { - printf("Press any key to exit...\n"); - fgetc(stdin); - } -} - -Result benchmark(const String& filename, int file_size, int block_size, ByteBuffer& buffer, bool allow_cache) -{ - int flags = O_CREAT | O_TRUNC | O_RDWR; - if (!allow_cache) - flags |= O_DIRECT; - - int fd = open(filename.characters(), flags, 0644); - if (fd == -1) { - perror("open"); - exit(1); - } - - auto cleanup_and_exit = [fd, filename]() { - close(fd); - unlink(filename.characters()); - exit(1); - }; - - Result res; - - Core::ElapsedTimer timer; - - timer.start(); - int nwrote = 0; - for (int j = 0; j < file_size; j += block_size) { - int n = write(fd, buffer.data(), block_size); - if (n < 0) { - perror("write"); - cleanup_and_exit(); - } - nwrote += n; - } - - res.write_bps = (u64)(timer.elapsed() ? (file_size / timer.elapsed()) : file_size) * 1000; - - if (lseek(fd, 0, SEEK_SET) < 0) { - perror("lseek"); - cleanup_and_exit(); - } - - timer.start(); - int nread = 0; - while (nread < file_size) { - int n = read(fd, buffer.data(), block_size); - if (n < 0) { - perror("read"); - cleanup_and_exit(); - } - nread += n; - } - - res.read_bps = (u64)(timer.elapsed() ? (file_size / timer.elapsed()) : file_size) * 1000; - - if (close(fd) != 0) { - perror("close"); - cleanup_and_exit(); - } - - if (unlink(filename.characters()) != 0) { - perror("unlink"); - cleanup_and_exit(); - } - - return res; -} diff --git a/Userland/dmesg.cpp b/Userland/dmesg.cpp deleted file mode 100644 index 415bad6dd6..0000000000 --- a/Userland/dmesg.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/proc/dmesg", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - auto f = Core::File::construct("/proc/dmesg"); - if (!f->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "open: failed to open /proc/dmesg: %s\n", f->error_string()); - return 1; - } - const auto& b = f->read_all(); - for (size_t i = 0; i < b.size(); ++i) - putchar(b[i]); - return 0; -} diff --git a/Userland/du.cpp b/Userland/du.cpp deleted file mode 100644 index fed2898eb4..0000000000 --- a/Userland/du.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (c) 2020, Fei Wu - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct DuOption { - enum class TimeType { - NotUsed, - Modification, - Access, - Status - }; - - bool all = false; - bool apparent_size = false; - int threshold = 0; - TimeType time_type = TimeType::NotUsed; - Vector excluded_patterns; -}; - -static int parse_args(int argc, char** argv, Vector& files, DuOption& du_option, int& max_depth); -static int print_space_usage(const String& path, const DuOption& du_option, int max_depth); - -int main(int argc, char** argv) -{ - Vector files; - DuOption du_option; - int max_depth = INT_MAX; - - if (parse_args(argc, argv, files, du_option, max_depth)) - return 1; - - for (const auto& file : files) { - if (print_space_usage(file, du_option, max_depth)) - return 1; - } - - return 0; -} - -int parse_args(int argc, char** argv, Vector& files, DuOption& du_option, int& max_depth) -{ - bool summarize = false; - const char* pattern = nullptr; - const char* exclude_from = nullptr; - Vector files_to_process; - - Core::ArgsParser::Option time_option { - true, - "Show time of type time-type of any file in the directory, or any of its subdirectories. " - "Available choices: mtime, modification, ctime, status, use, atime, access", - "time", - 0, - "time-type", - [&du_option](const char* s) { - if (!strcmp(s, "mtime") || !strcmp(s, "modification")) - du_option.time_type = DuOption::TimeType::Modification; - else if (!strcmp(s, "ctime") || !strcmp(s, "status") || !strcmp(s, "use")) - du_option.time_type = DuOption::TimeType::Status; - else if (!strcmp(s, "atime") || !strcmp(s, "access")) - du_option.time_type = DuOption::TimeType::Access; - else - return false; - - return true; - } - }; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Display actual or apparent disk usage of files or directories."); - args_parser.add_option(du_option.all, "Write counts for all files, not just directories", "all", 'a'); - args_parser.add_option(du_option.apparent_size, "Print apparent sizes, rather than disk usage", "apparent-size", 0); - args_parser.add_option(max_depth, "Print the total for a directory or file only if it is N or fewer levels below the command line argument", "max-depth", 'd', "N"); - args_parser.add_option(summarize, "Display only a total for each argument", "summarize", 's'); - args_parser.add_option(du_option.threshold, "Exclude entries smaller than size if positive, or entries greater than size if negative", "threshold", 't', "size"); - args_parser.add_option(move(time_option)); - args_parser.add_option(pattern, "Exclude files that match pattern", "exclude", 0, "pattern"); - args_parser.add_option(exclude_from, "Exclude files that match any pattern in file", "exclude_from", 'X', "file"); - args_parser.add_positional_argument(files_to_process, "File to process", "file", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (summarize) - max_depth = 0; - - if (pattern) - du_option.excluded_patterns.append(pattern); - if (exclude_from) { - auto file = Core::File::construct(exclude_from); - bool success = file->open(Core::IODevice::ReadOnly); - ASSERT(success); - if (const auto buff = file->read_all()) { - String patterns = String::copy(buff, Chomp); - du_option.excluded_patterns.append(patterns.split('\n')); - } - } - - for (auto* file : files_to_process) { - files.append(file); - } - - if (files.is_empty()) { - files.append("."); - } - - return 0; -} - -int print_space_usage(const String& path, const DuOption& du_option, int max_depth) -{ - struct stat path_stat; - if (lstat(path.characters(), &path_stat) < 0) { - perror("lstat"); - return 1; - } - - if (--max_depth >= 0 && S_ISDIR(path_stat.st_mode)) { - auto di = Core::DirIterator(path, Core::DirIterator::SkipParentAndBaseDir); - if (di.has_error()) { - fprintf(stderr, "DirIterator: %s\n", di.error_string()); - return 1; - } - while (di.has_next()) { - const auto child_path = di.next_full_path(); - if (du_option.all || Core::File::is_directory(child_path)) { - if (print_space_usage(child_path, du_option, max_depth)) - return 1; - } - } - } - - const auto basename = LexicalPath(path).basename(); - for (const auto& pattern : du_option.excluded_patterns) { - if (basename.matches(pattern, CaseSensitivity::CaseSensitive)) - return 0; - } - - long long size = path_stat.st_size; - if (du_option.apparent_size) { - const auto block_size = 512; - size = path_stat.st_blocks * block_size; - } - - if ((du_option.threshold > 0 && size < du_option.threshold) || (du_option.threshold < 0 && size > -du_option.threshold)) - return 0; - - const long long block_size = 1024; - size = size / block_size + (size % block_size != 0); - - if (du_option.time_type == DuOption::TimeType::NotUsed) - printf("%lld\t%s\n", size, path.characters()); - else { - auto time = path_stat.st_mtime; - switch (du_option.time_type) { - case DuOption::TimeType::Access: - time = path_stat.st_atime; - break; - case DuOption::TimeType::Status: - time = path_stat.st_ctime; - default: - break; - } - - const auto formatted_time = Core::DateTime::from_timestamp(time).to_string(); - printf("%lld\t%s\t%s\n", size, formatted_time.characters(), path.characters()); - } - - return 0; -} diff --git a/Userland/echo.cpp b/Userland/echo.cpp deleted file mode 100644 index 0ac1ce6bb5..0000000000 --- a/Userland/echo.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector values; - bool no_trailing_newline = false; - - Core::ArgsParser args_parser; - args_parser.add_option(no_trailing_newline, "Do not output a trailing newline", nullptr, 'n'); - args_parser.add_positional_argument(values, "Values to print out", "string", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - for (size_t i = 0; i < values.size(); ++i) { - fputs(values[i], stdout); - if (i != values.size() - 1) - fputc(' ', stdout); - } - if (!no_trailing_newline) - printf("\n"); - return 0; -} diff --git a/Userland/env.cpp b/Userland/env.cpp deleted file mode 100644 index 630ff81749..0000000000 --- a/Userland/env.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath exec", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* filename = nullptr; - - for (int idx = 1; idx < argc; ++idx) { - if (idx == 1) { - if (StringView { argv[idx] } == "-i" || StringView { argv[idx] } == "--ignore-environment") { - clearenv(); - continue; - } - } - if (StringView { argv[idx] }.contains('=')) { - putenv(argv[idx]); - } else { - filename = argv[idx]; - argv += idx; - break; - } - } - - if (filename == nullptr) { - for (auto entry = environ; *entry != nullptr; ++entry) - printf("%s\n", *entry); - - return 0; - } - - String filepath = Core::find_executable_in_path(filename); - - if (filepath.is_null()) { - warnln("no {} in path", filename); - return 1; - } - - execv(filepath.characters(), argv); - - perror("execv"); - return 1; -} diff --git a/Userland/expr.cpp b/Userland/expr.cpp deleted file mode 100644 index 896409a6d9..0000000000 --- a/Userland/expr.cpp +++ /dev/null @@ -1,620 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static void print_help_and_exit() -{ - outln(R"( -Usage: expr EXPRESSION - expr [--help] - -Print the value of EXPRESSION to standard output.)"); - exit(0); -} - -template -[[noreturn]] void fail(Args&&... args) -{ - warn("ERROR: \e[31m"); - warnln(args...); - warn("\e[0m"); - exit(1); -} - -class Expression { -public: - enum Precedence { - Or, - And, - Comp, - ArithS, - ArithM, - StringO, - Paren, - }; - static NonnullOwnPtr parse(Queue& args, Precedence prec = Or); - - enum class Type { - Integer, - String, - }; - - virtual bool truth() const = 0; - virtual int integer() const = 0; - virtual String string() const = 0; - virtual Type type() const = 0; - virtual ~Expression() { } -}; - -class ValueExpression : public Expression { -public: - ValueExpression(int v) - : as_integer(v) - , m_type(Type::Integer) - { - } - - ValueExpression(String&& v) - : as_string(move(v)) - , m_type(Type::String) - { - } - - virtual ~ValueExpression() { } - -private: - virtual bool truth() const override - { - if (m_type == Type::String) - return !as_string.is_empty(); - return integer() != 0; - } - virtual int integer() const override - { - switch (m_type) { - case Type::Integer: - return as_integer; - case Type::String: - if (auto converted = as_string.to_int(); converted.has_value()) - return converted.value(); - fail("Not an integer: '{}'", as_string); - } - ASSERT_NOT_REACHED(); - } - virtual String string() const override - { - switch (m_type) { - case Type::Integer: - return String::formatted("{}", as_integer); - case Type::String: - return as_string; - } - ASSERT_NOT_REACHED(); - } - virtual Type type() const override { return m_type; } - - union { - int as_integer; - String as_string; - }; - Type m_type { Type::String }; -}; - -class BooleanExpression : public Expression { -public: - enum class BooleanOperator { - And, - Or, - }; - static BooleanOperator op_from(const StringView& sv) - { - if (sv == "&") - return BooleanOperator::And; - return BooleanOperator::Or; - } - BooleanExpression(BooleanOperator op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) - : m_op(op) - , m_left(move(left)) - , m_right(move(right)) - { - if (m_op == BooleanOperator::Or) - m_left_truth = m_left->truth(); - else - m_right_truth = m_right->truth(); - } - -private: - virtual bool truth() const override - { - if (m_op == BooleanOperator::Or) - return m_left_truth ? true : m_right->truth(); - return m_right_truth ? m_left->truth() : false; - } - - virtual int integer() const override - { - switch (m_op) { - case BooleanOperator::And: - if (m_right_truth) - return m_left->integer(); - return 0; - case BooleanOperator::Or: - if (m_left_truth) - return m_left->integer(); - return m_right->integer(); - } - ASSERT_NOT_REACHED(); - } - - virtual String string() const override - { - switch (m_op) { - case BooleanOperator::And: - if (m_right_truth) - return m_left->string(); - return "0"; - case BooleanOperator::Or: - if (m_left_truth) - return m_left->string(); - return m_right->string(); - } - ASSERT_NOT_REACHED(); - } - virtual Type type() const override - { - switch (m_op) { - case BooleanOperator::And: - if (m_right_truth) - return m_left->type(); - return m_right->type(); - case BooleanOperator::Or: - if (m_left_truth) - return m_left->type(); - return m_right->type(); - } - ASSERT_NOT_REACHED(); - } - - BooleanOperator m_op { BooleanOperator::And }; - NonnullOwnPtr m_left, m_right; - bool m_left_truth { false }, m_right_truth { false }; -}; - -class ComparisonExpression : public Expression { -public: - enum class ComparisonOperation { - Less, - LessEq, - Eq, - Neq, - GreaterEq, - Greater, - }; - - static ComparisonOperation op_from(const StringView& sv) - { - if (sv == "<") - return ComparisonOperation::Less; - if (sv == "<=") - return ComparisonOperation::LessEq; - if (sv == "=") - return ComparisonOperation::Eq; - if (sv == "!=") - return ComparisonOperation::Neq; - if (sv == ">=") - return ComparisonOperation::GreaterEq; - return ComparisonOperation::Greater; - } - - ComparisonExpression(ComparisonOperation op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) - : m_op(op) - , m_left(move(left)) - , m_right(move(right)) - { - } - -private: - template - bool compare(const T& left, const T& right) const - { - switch (m_op) { - case ComparisonOperation::Less: - return left < right; - case ComparisonOperation::LessEq: - return left == right || left < right; - case ComparisonOperation::Eq: - return left == right; - case ComparisonOperation::Neq: - return left != right; - case ComparisonOperation::GreaterEq: - return !(left < right); - case ComparisonOperation::Greater: - return left != right && !(left < right); - } - ASSERT_NOT_REACHED(); - } - - virtual bool truth() const override - { - switch (m_left->type()) { - case Type::Integer: - return compare(m_left->integer(), m_right->integer()); - case Type::String: - return compare(m_left->string(), m_right->string()); - } - ASSERT_NOT_REACHED(); - } - virtual int integer() const override { return truth(); } - virtual String string() const override { return truth() ? "1" : "0"; } - virtual Type type() const override { return Type::Integer; } - - ComparisonOperation m_op { ComparisonOperation::Less }; - NonnullOwnPtr m_left, m_right; -}; - -class ArithmeticExpression : public Expression { -public: - enum class ArithmeticOperation { - Sum, - Difference, - Product, - Quotient, - Remainder, - }; - static ArithmeticOperation op_from(const StringView& sv) - { - if (sv == "+") - return ArithmeticOperation::Sum; - if (sv == "-") - return ArithmeticOperation::Difference; - if (sv == "*") - return ArithmeticOperation::Product; - if (sv == "/") - return ArithmeticOperation::Quotient; - return ArithmeticOperation::Remainder; - } - ArithmeticExpression(ArithmeticOperation op, NonnullOwnPtr&& left, NonnullOwnPtr&& right) - : m_op(op) - , m_left(move(left)) - , m_right(move(right)) - { - } - -private: - virtual bool truth() const override - { - switch (m_op) { - case ArithmeticOperation::Sum: - return m_left->truth() || m_right->truth(); - default: - return integer() != 0; - } - } - virtual int integer() const override - { - auto right = m_right->integer(); - if (right == 0) { - if (m_op == ArithmeticOperation::Product) - return 0; - if (m_op == ArithmeticOperation::Quotient || m_op == ArithmeticOperation::Remainder) - fail("Division by zero"); - } - - auto left = m_left->integer(); - switch (m_op) { - case ArithmeticOperation::Product: - return right * left; - case ArithmeticOperation::Sum: - return right + left; - case ArithmeticOperation::Difference: - return left - right; - case ArithmeticOperation::Quotient: - return left / right; - case ArithmeticOperation::Remainder: - return left % right; - } - ASSERT_NOT_REACHED(); - } - virtual String string() const override - { - return String::formatted("{}", integer()); - } - virtual Type type() const override - { - return Type::Integer; - } - - ArithmeticOperation m_op { ArithmeticOperation::Sum }; - NonnullOwnPtr m_left, m_right; -}; - -class StringExpression : public Expression { -public: - enum class StringOperation { - Substring, - Index, - Length, - Match, - }; - - StringExpression(StringOperation op, NonnullOwnPtr string, OwnPtr pos_or_chars = {}, OwnPtr length = {}) - : m_op(op) - , m_str(move(string)) - , m_pos_or_chars(move(pos_or_chars)) - , m_length(move(length)) - { - } - -private: - virtual bool truth() const override { return integer() != 0; } - virtual int integer() const override - { - if (m_op == StringOperation::Substring || m_op == StringOperation::Match) { - auto substr = string(); - if (auto integer = substr.to_int(); integer.has_value()) - return integer.value(); - else - fail("Not an integer: '{}'", substr); - } - - if (m_op == StringOperation::Index) { - if (auto idx = m_str->string().index_of(m_pos_or_chars->string()); idx.has_value()) - return idx.value() + 1; - return 0; - } - - if (m_op == StringOperation::Length) - return m_str->string().length(); - - ASSERT_NOT_REACHED(); - } - static auto safe_substring(const String& str, int start, int length) - { - if (start < 1 || (size_t)start > str.length()) - fail("Index out of range"); - --start; - if (str.length() - start < (size_t)length) - fail("Index out of range"); - return str.substring(start, length); - } - virtual String string() const override - { - if (m_op == StringOperation::Substring) - return safe_substring(m_str->string(), m_pos_or_chars->integer(), m_length->integer()); - - if (m_op == StringOperation::Match) { - auto match = m_compiled_regex->match(m_str->string(), PosixFlags::Global); - if (m_compiled_regex->parser_result.capture_groups_count == 0) { - if (!match.success) - return "0"; - - size_t count = 0; - for (auto& m : match.matches) - count += m.view.length(); - - return String::number(count); - } else { - if (!match.success) - return ""; - - StringBuilder result; - for (auto& m : match.capture_group_matches) { - for (auto& e : m) - result.append(e.view.to_string()); - } - - return result.build(); - } - } - - return String::number(integer()); - } - virtual Type type() const override - { - if (m_op == StringOperation::Substring) - return Type::String; - if (m_op == StringOperation::Match) { - if (!m_pos_or_chars) - fail("'match' expects a string pattern"); - - ensure_regex(); - if (m_compiled_regex->parser_result.capture_groups_count == 0) - return Type::Integer; - - return Type::String; - } - return Type::Integer; - } - - void ensure_regex() const - { - if (!m_compiled_regex) - m_compiled_regex = make>(m_pos_or_chars->string()); - } - - StringOperation m_op { StringOperation::Substring }; - NonnullOwnPtr m_str; - OwnPtr m_pos_or_chars, m_length; - mutable OwnPtr> m_compiled_regex; -}; - -NonnullOwnPtr Expression::parse(Queue& args, Precedence prec) -{ - switch (prec) { - case Or: { - auto left = parse(args, And); - while (!args.is_empty() && args.head() == "|") { - args.dequeue(); - auto right = parse(args, And); - left = make(BooleanExpression::BooleanOperator::Or, move(left), move(right)); - } - return left; - } - case And: { - auto left = parse(args, Comp); - while (!args.is_empty() && args.head() == "&") { - args.dequeue(); - auto right = parse(args, Comp); - left = make(BooleanExpression::BooleanOperator::And, move(left), move(right)); - } - return left; - } - case Comp: { - auto left = parse(args, ArithS); - while (!args.is_empty() && args.head().is_one_of("<", "<=", "=", "!=", "=>", ">")) { - auto op = args.dequeue(); - auto right = parse(args, ArithM); - left = make(ComparisonExpression::op_from(op), move(left), move(right)); - } - return left; - } - case ArithS: { - auto left = parse(args, ArithM); - while (!args.is_empty() && args.head().is_one_of("+", "-")) { - auto op = args.dequeue(); - auto right = parse(args, ArithM); - left = make(ArithmeticExpression::op_from(op), move(left), move(right)); - } - return left; - } - case ArithM: { - auto left = parse(args, StringO); - while (!args.is_empty() && args.head().is_one_of("*", "/", "%")) { - auto op = args.dequeue(); - auto right = parse(args, StringO); - left = make(ArithmeticExpression::op_from(op), move(left), move(right)); - } - return left; - } - case StringO: { - if (args.is_empty()) - fail("Expected a term"); - - OwnPtr left; - - while (!args.is_empty()) { - auto& op = args.head(); - if (op == "+") { - args.dequeue(); - left = make(args.dequeue()); - } else if (op == "substr") { - args.dequeue(); - auto str = parse(args, Paren); - auto pos = parse(args, Paren); - auto len = parse(args, Paren); - left = make(StringExpression::StringOperation::Substring, move(str), move(pos), move(len)); - } else if (op == "index") { - args.dequeue(); - auto str = parse(args, Paren); - auto chars = parse(args, Paren); - left = make(StringExpression::StringOperation::Index, move(str), move(chars)); - } else if (op == "match") { - args.dequeue(); - auto str = parse(args, Paren); - auto pattern = parse(args, Paren); - left = make(StringExpression::StringOperation::Match, move(str), move(pattern)); - } else if (op == "length") { - args.dequeue(); - auto str = parse(args, Paren); - left = make(StringExpression::StringOperation::Length, move(str)); - } else if (!left) { - left = parse(args, Paren); - } - - if (!args.is_empty() && args.head() == ":") { - args.dequeue(); - auto right = parse(args, Paren); - left = make(StringExpression::StringOperation::Match, left.release_nonnull(), move(right)); - } else { - return left.release_nonnull(); - } - } - - return left.release_nonnull(); - } - case Paren: { - if (args.is_empty()) - fail("Expected a term"); - - if (args.head() == "(") { - args.dequeue(); - auto expr = parse(args); - if (args.head() != ")") - fail("Expected a close paren"); - args.dequeue(); - return expr; - } - - return make(args.dequeue()); - } - } - - fail("Invalid expression"); -} - -int main(int argc, char** argv) -{ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil(nullptr, nullptr) < 0) { - perror("unveil"); - return 1; - } - - if ((argc == 2 && StringView { "--help" } == argv[1]) || argc == 1) - print_help_and_exit(); - - Queue args; - for (int i = 1; i < argc; ++i) - args.enqueue(argv[i]); - - auto expression = Expression::parse(args); - if (!args.is_empty()) - fail("Extra tokens at the end of the expression"); - - switch (expression->type()) { - case Expression::Type::Integer: - outln("{}", expression->integer()); - break; - case Expression::Type::String: - outln("{}", expression->string()); - break; - } - return 0; -} diff --git a/Userland/false.cpp b/Userland/false.cpp deleted file mode 100644 index c7a39e47e8..0000000000 --- a/Userland/false.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include - -int main(int, char**) -{ - return 1; -} diff --git a/Userland/fgrep.cpp b/Userland/fgrep.cpp deleted file mode 100644 index bf71e54c77..0000000000 --- a/Userland/fgrep.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (argc < 2) { - printf("usage: fgrep \n"); - return 0; - } - for (;;) { - char buf[4096]; - auto* str = fgets(buf, sizeof(buf), stdin); - if (str && strstr(str, argv[1])) - write(1, buf, strlen(buf)); - if (feof(stdin)) - return 0; - ASSERT(str); - } - return 0; -} diff --git a/Userland/find.cpp b/Userland/find.cpp deleted file mode 100644 index 872cf1717e..0000000000 --- a/Userland/find.cpp +++ /dev/null @@ -1,492 +0,0 @@ -/* - * Copyright (c) 2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -bool g_follow_symlinks = false; -bool g_there_was_an_error = false; -bool g_have_seen_action_command = false; - -[[noreturn]] static void fatal_error(const char* format, ...) -{ - fputs("\033[31m", stderr); - - va_list ap; - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); - - fputs("\033[0m\n", stderr); - exit(1); -} - -class Command { -public: - virtual ~Command() { } - virtual bool evaluate(const char* file_path) const = 0; -}; - -class StatCommand : public Command { -public: - virtual bool evaluate(const struct stat&) const = 0; - -private: - virtual bool evaluate(const char* file_path) const override - { - struct stat stat; - auto stat_func = g_follow_symlinks ? ::stat : ::lstat; - int rc = stat_func(file_path, &stat); - if (rc < 0) { - perror(file_path); - g_there_was_an_error = true; - return false; - } - return evaluate(stat); - } -}; - -class TypeCommand final : public StatCommand { -public: - TypeCommand(const char* arg) - { - StringView type = arg; - if (type.length() != 1 || !StringView("bcdlpfs").contains(type[0])) - fatal_error("Invalid mode: \033[1m%s", arg); - m_type = type[0]; - } - -private: - virtual bool evaluate(const struct stat& stat) const override - { - auto type = stat.st_mode; - switch (m_type) { - case 'b': - return S_ISBLK(type); - case 'c': - return S_ISCHR(type); - case 'd': - return S_ISDIR(type); - case 'l': - return S_ISLNK(type); - case 'p': - return S_ISFIFO(type); - case 'f': - return S_ISREG(type); - case 's': - return S_ISSOCK(type); - default: - // We've verified this is a correct character before. - ASSERT_NOT_REACHED(); - } - } - - char m_type { 0 }; -}; - -class LinksCommand final : public StatCommand { -public: - LinksCommand(const char* arg) - { - auto number = StringView(arg).to_uint(); - if (!number.has_value()) - fatal_error("Invalid number: \033[1m%s", arg); - m_links = number.value(); - } - -private: - virtual bool evaluate(const struct stat& stat) const override - { - return stat.st_nlink == m_links; - } - - nlink_t m_links { 0 }; -}; - -class UserCommand final : public StatCommand { -public: - UserCommand(const char* arg) - { - if (struct passwd* passwd = getpwnam(arg)) { - m_uid = passwd->pw_uid; - } else { - // Attempt to parse it as decimal UID. - auto number = StringView(arg).to_uint(); - if (!number.has_value()) - fatal_error("Invalid user: \033[1m%s", arg); - m_uid = number.value(); - } - } - -private: - virtual bool evaluate(const struct stat& stat) const override - { - return stat.st_uid == m_uid; - } - - uid_t m_uid { 0 }; -}; - -class GroupCommand final : public StatCommand { -public: - GroupCommand(const char* arg) - { - if (struct group* gr = getgrnam(arg)) { - m_gid = gr->gr_gid; - } else { - // Attempt to parse it as decimal GID. - auto number = StringView(arg).to_int(); - if (!number.has_value()) - fatal_error("Invalid group: \033[1m%s", arg); - m_gid = number.value(); - } - } - -private: - virtual bool evaluate(const struct stat& stat) const override - { - return stat.st_gid == m_gid; - } - - gid_t m_gid { 0 }; -}; - -class SizeCommand final : public StatCommand { -public: - SizeCommand(const char* arg) - { - StringView view = arg; - if (view.ends_with('c')) { - m_is_bytes = true; - view = view.substring_view(0, view.length() - 1); - } - auto number = view.to_uint(); - if (!number.has_value()) - fatal_error("Invalid size: \033[1m%s", arg); - m_size = number.value(); - } - -private: - virtual bool evaluate(const struct stat& stat) const override - { - if (m_is_bytes) - return stat.st_size == m_size; - - auto size_divided_by_512_rounded_up = (stat.st_size + 511) / 512; - return size_divided_by_512_rounded_up == m_size; - } - - off_t m_size { 0 }; - bool m_is_bytes { false }; -}; - -class NameCommand : public Command { -public: - NameCommand(const char* pattern, CaseSensitivity case_sensitivity) - : m_pattern(pattern) - , m_case_sensitivity(case_sensitivity) - { - } - -private: - virtual bool evaluate(const char* file_path) const override - { - LexicalPath path { file_path }; - return path.basename().matches(m_pattern, m_case_sensitivity); - } - - StringView m_pattern; - CaseSensitivity m_case_sensitivity { CaseSensitivity::CaseSensitive }; -}; - -class PrintCommand final : public Command { -public: - PrintCommand(char terminator = '\n') - : m_terminator(terminator) - { - } - -private: - virtual bool evaluate(const char* file_path) const override - { - printf("%s%c", file_path, m_terminator); - return true; - } - - char m_terminator { '\n' }; -}; - -class ExecCommand final : public Command { -public: - ExecCommand(Vector&& argv) - : m_argv(move(argv)) - { - } - -private: - virtual bool evaluate(const char* file_path) const override - { - pid_t pid = fork(); - - if (pid < 0) { - perror("fork"); - g_there_was_an_error = true; - return false; - } else if (pid == 0) { - // Replace any occurrences of "{}" with the path. Since we're in the - // child and going to exec real soon, let's just const_cast away the - // constness. - auto argv = const_cast&>(m_argv); - for (auto& arg : argv) { - if (StringView(arg) == "{}") - arg = const_cast(file_path); - } - argv.append(nullptr); - execvp(m_argv[0], argv.data()); - perror("execvp"); - exit(1); - } else { - int status; - int rc = waitpid(pid, &status, 0); - if (rc < 0) { - perror("waitpid"); - g_there_was_an_error = true; - return false; - } - return WIFEXITED(status) && WEXITSTATUS(status) == 0; - } - } - - Vector m_argv; -}; - -class AndCommand final : public Command { -public: - AndCommand(NonnullOwnPtr&& lhs, NonnullOwnPtr&& rhs) - : m_lhs(move(lhs)) - , m_rhs(move(rhs)) - { - } - -private: - virtual bool evaluate(const char* file_path) const override - { - return m_lhs->evaluate(file_path) && m_rhs->evaluate(file_path); - } - - NonnullOwnPtr m_lhs; - NonnullOwnPtr m_rhs; -}; - -class OrCommand final : public Command { -public: - OrCommand(NonnullOwnPtr&& lhs, NonnullOwnPtr&& rhs) - : m_lhs(move(lhs)) - , m_rhs(move(rhs)) - { - } - -private: - virtual bool evaluate(const char* file_path) const override - { - return m_lhs->evaluate(file_path) || m_rhs->evaluate(file_path); - } - - NonnullOwnPtr m_lhs; - NonnullOwnPtr m_rhs; -}; - -static OwnPtr parse_complex_command(char* argv[]); - -// Parse a simple command starting at optind; leave optind at its the last -// argument. Return nullptr if we reach the end of arguments. -static OwnPtr parse_simple_command(char* argv[]) -{ - StringView arg = argv[optind]; - - if (arg.is_null()) { - return {}; - } else if (arg == "(") { - optind++; - auto command = parse_complex_command(argv); - if (command && argv[optind] && StringView(argv[++optind]) == ")") - return command; - fatal_error("Unmatched \033[1m("); - } else if (arg == "-type") { - return make(argv[++optind]); - } else if (arg == "-links") { - return make(argv[++optind]); - } else if (arg == "-user") { - return make(argv[++optind]); - } else if (arg == "-group") { - return make(argv[++optind]); - } else if (arg == "-size") { - return make(argv[++optind]); - } else if (arg == "-name") { - return make(argv[++optind], CaseSensitivity::CaseSensitive); - } else if (arg == "-iname") { - return make(argv[++optind], CaseSensitivity::CaseInsensitive); - } else if (arg == "-print") { - g_have_seen_action_command = true; - return make(); - } else if (arg == "-print0") { - g_have_seen_action_command = true; - return make(0); - } else if (arg == "-exec") { - g_have_seen_action_command = true; - Vector command_argv; - while (argv[++optind] && StringView(argv[optind]) != ";") - command_argv.append(argv[optind]); - return make(move(command_argv)); - } else { - fatal_error("Unsupported command \033[1m%s", argv[optind]); - } -} - -static OwnPtr parse_complex_command(char* argv[]) -{ - auto command = parse_simple_command(argv); - - while (command && argv[optind] && argv[optind + 1]) { - StringView arg = argv[++optind]; - - enum { And, - Or } binary_operation - = And; - - if (arg == "-a") { - optind++; - binary_operation = And; - } else if (arg == "-o") { - optind++; - binary_operation = Or; - } else if (arg == ")") { - // Ooops, looked too far. - optind--; - return command; - } else { - // Juxtaposition is an And too, and there's nothing to skip. - binary_operation = And; - } - - auto rhs = parse_complex_command(argv); - if (!rhs) - fatal_error("Missing right-hand side"); - - if (binary_operation == And) - command = make(command.release_nonnull(), rhs.release_nonnull()); - else - command = make(command.release_nonnull(), rhs.release_nonnull()); - } - - return command; -} - -static NonnullOwnPtr parse_all_commands(char* argv[]) -{ - auto command = parse_complex_command(argv); - - if (g_have_seen_action_command) { - ASSERT(command); - return command.release_nonnull(); - } - - if (!command) { - return make(); - } - - return make(command.release_nonnull(), make()); -} - -static const char* parse_options(int argc, char* argv[]) -{ - // Sadly, we can't use Core::ArgsParser, because find accepts arguments in - // an extremely unusual format. We're going to try to use getopt(), though. - opterr = 0; - while (true) { - int opt = getopt(argc, argv, "+L"); - switch (opt) { - case -1: { - // No more options. - StringView arg = argv[optind]; - if (!arg.is_null() && !arg.starts_with('-')) { - // It's our root path! - return argv[optind++]; - } else { - // It's a part of the script, and our root path is the current - // directory by default. - return "."; - } - } - case '?': - // Some error. Most likely, it's getopt() getting confused about - // what it thought was an option, but is actually a command. Return - // the default path, and hope the command parsing logic deals with - // this. - return "."; - case 'L': - g_follow_symlinks = true; - break; - default: - ASSERT_NOT_REACHED(); - } - } -} - -static void walk_tree(const char* root_path, Command& command) -{ - command.evaluate(root_path); - - Core::DirIterator dir_iterator(root_path, Core::DirIterator::SkipParentAndBaseDir); - if (dir_iterator.has_error() && dir_iterator.error() == ENOTDIR) - return; - - while (dir_iterator.has_next()) - walk_tree(dir_iterator.next_full_path().characters(), command); - - if (dir_iterator.has_error()) { - fprintf(stderr, "%s: %s\n", root_path, dir_iterator.error_string()); - g_there_was_an_error = true; - } -} - -int main(int argc, char* argv[]) -{ - auto root_path = parse_options(argc, argv); - auto command = parse_all_commands(argv); - walk_tree(root_path, *command); - return g_there_was_an_error ? 1 : 0; -} diff --git a/Userland/flock.cpp b/Userland/flock.cpp deleted file mode 100644 index dade8a2c44..0000000000 --- a/Userland/flock.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (argc < 3) { - printf("usage: flock \n"); - return 0; - } - - pid_t child_pid; - if ((errno = posix_spawnp(&child_pid, argv[2], nullptr, nullptr, &argv[2], environ))) { - perror("posix_spawn"); - return 1; - } - - int status; - if (waitpid(child_pid, &status, 0) < 0) { - perror("waitpid"); - return 1; - } - return WEXITSTATUS(status); -} diff --git a/Userland/functrace.cpp b/Userland/functrace.cpp deleted file mode 100644 index dcb2d046b3..0000000000 --- a/Userland/functrace.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (c) 2020, Itamar S. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static OwnPtr g_debug_session; -static bool g_should_output_color = false; - -static void handle_sigint(int) -{ - printf("Debugger: SIGINT\n"); - - // The destructor of DebugSession takes care of detaching - g_debug_session = nullptr; -} - -static void print_function_call(String function_name, size_t depth) -{ - for (size_t i = 0; i < depth; ++i) { - out(" "); - } - outln("=> {}", function_name); -} - -static void print_syscall(PtraceRegisters& regs, size_t depth) -{ - for (size_t i = 0; i < depth; ++i) { - printf(" "); - } - const char* begin_color = g_should_output_color ? "\033[34;1m" : ""; - const char* end_color = g_should_output_color ? "\033[0m" : ""; - outln("=> {}SC_{}(0x{:x}, 0x{:x}, 0x{:x}){}", - begin_color, - Syscall::to_string((Syscall::Function)regs.eax), - regs.edx, - regs.ecx, - regs.ebx, - end_color); -} - -static NonnullOwnPtr> instrument_code() -{ - auto instrumented = make>(); - g_debug_session->for_each_loaded_library([&](const Debug::DebugSession::LoadedLibrary& lib) { - lib.debug_info->elf().for_each_section_of_type(SHT_PROGBITS, [&](const ELF::Image::Section& section) { - if (section.name() != ".text") - return IterationDecision::Continue; - - X86::SimpleInstructionStream stream((const u8*)((u32)lib.file->data() + section.offset()), section.size()); - X86::Disassembler disassembler(stream); - for (;;) { - auto offset = stream.offset(); - void* instruction_address = (void*)(section.address() + offset + lib.base_address); - auto insn = disassembler.next(); - if (!insn.has_value()) - break; - if (insn.value().mnemonic() == "RET" || insn.value().mnemonic() == "CALL") { - g_debug_session->insert_breakpoint(instruction_address); - instrumented->set(instruction_address, insn.value()); - } - } - return IterationDecision::Continue; - }); - return IterationDecision::Continue; - }); - return instrumented; -} - -int main(int argc, char** argv) -{ - if (pledge("stdio proc exec rpath sigaction ptrace", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (isatty(STDOUT_FILENO)) - g_should_output_color = true; - - const char* command = nullptr; - Core::ArgsParser args_parser; - args_parser.add_positional_argument(command, - "The program to be traced, along with its arguments", - "program", Core::ArgsParser::Required::Yes); - args_parser.parse(argc, argv); - - auto result = Debug::DebugSession::exec_and_attach(command); - if (!result) { - warnln("Failed to start debugging session for: \"{}\"", command); - exit(1); - } - g_debug_session = result.release_nonnull(); - - auto instrumented = instrument_code(); - - struct sigaction sa; - memset(&sa, 0, sizeof(struct sigaction)); - sa.sa_handler = handle_sigint; - sigaction(SIGINT, &sa, nullptr); - - size_t depth = 0; - bool new_function = true; - - g_debug_session->run(Debug::DebugSession::DesiredInitialDebugeeState::Running, [&](Debug::DebugSession::DebugBreakReason reason, Optional regs) { - if (reason == Debug::DebugSession::DebugBreakReason::Exited) { - outln("Program exited."); - return Debug::DebugSession::DebugDecision::Detach; - } - - if (reason == Debug::DebugSession::DebugBreakReason::Syscall) { - print_syscall(regs.value(), depth + 1); - return Debug::DebugSession::DebugDecision::ContinueBreakAtSyscall; - } - - if (new_function) { - auto function_name = g_debug_session->symbolicate(regs.value().eip); - print_function_call(function_name.value().symbol, depth); - new_function = false; - return Debug::DebugSession::ContinueBreakAtSyscall; - } - auto instruction = instrumented->get((void*)regs.value().eip).value(); - - if (instruction.mnemonic() == "RET") { - if (depth != 0) - --depth; - return Debug::DebugSession::ContinueBreakAtSyscall; - } - - // FIXME: we could miss some leaf functions that were called with a jump - ASSERT(instruction.mnemonic() == "CALL"); - - ++depth; - new_function = true; - - return Debug::DebugSession::DebugDecision::SingleStep; - }); -} diff --git a/Userland/gml-format.cpp b/Userland/gml-format.cpp deleted file mode 100644 index 79ce530a32..0000000000 --- a/Userland/gml-format.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2021, Linus Groh - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -bool format_file(const StringView&, bool); - -bool format_file(const StringView& path, bool inplace) -{ - auto read_from_stdin = path == "-"; - RefPtr file; - if (read_from_stdin) { - file = Core::File::standard_input(); - } else { - auto open_mode = inplace ? Core::File::ReadWrite : Core::File::ReadOnly; - auto file_or_error = Core::File::open(path, open_mode); - if (file_or_error.is_error()) { - warnln("Could not open {}: {}", path, file_or_error.error()); - return false; - } - file = file_or_error.value(); - } - auto formatted_gml = GUI::format_gml(file->read_all()); - if (formatted_gml.is_null()) { - warnln("Failed to parse GML!"); - return false; - } - if (inplace && !read_from_stdin) { - if (!file->seek(0) || !file->truncate(0)) { - warnln("Could not truncate {}: {}", path, file->error_string()); - return false; - } - if (!file->write(formatted_gml)) { - warnln("Could not write to {}: {}", path, file->error_string()); - return false; - } - } else { - out("{}", formatted_gml); - } - return true; -} - -int main(int argc, char** argv) -{ -#ifdef __serenity__ - if (pledge("stdio rpath wpath cpath", nullptr) < 0) { - perror("pledge"); - return 1; - } -#endif - - bool inplace = false; - Vector files; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Format GML files."); - args_parser.add_option(inplace, "Write formatted contents back to file rather than standard output", "inplace", 'i'); - args_parser.add_positional_argument(files, "File(s) to process", "path", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - -#ifdef __serenity__ - if (!inplace) { - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - } -#endif - - unsigned exit_code = 0; - - if (files.is_empty()) - files.append("-"); - for (auto& file : files) { - if (!format_file(file, inplace)) - exit_code = 1; - } - - return exit_code; -} diff --git a/Userland/grep.cpp b/Userland/grep.cpp deleted file mode 100644 index 25ab2e900c..0000000000 --- a/Userland/grep.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (c) 2020, Emanuel Sprung - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -enum class BinaryFileMode { - Binary, - Text, - Skip, -}; - -template -void fail(StringView format, Ts... args) -{ - fprintf(stderr, "\x1b[31m"); - warnln(format, forward(args)...); - fprintf(stderr, "\x1b[0m"); - abort(); -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector files; - - bool recursive { false }; - bool use_ere { true }; - const char* pattern = nullptr; - BinaryFileMode binary_mode { BinaryFileMode::Binary }; - bool case_insensitive = false; - - Core::ArgsParser args_parser; - args_parser.add_option(recursive, "Recursively scan files starting in working directory", "recursive", 'r'); - args_parser.add_option(use_ere, "Extended regular expressions (default)", "extended-regexp", 'E'); - args_parser.add_option(pattern, "Pattern", "regexp", 'e', "Pattern"); - args_parser.add_option(case_insensitive, "Make matches case-insensitive", nullptr, 'i'); - args_parser.add_option(Core::ArgsParser::Option { - .requires_argument = true, - .help_string = "Action to take for binary files ([binary], text, skip)", - .long_name = "binary-mode", - .accept_value = [&](auto* str) { - if (StringView { "text" } == str) - binary_mode = BinaryFileMode::Text; - else if (StringView { "binary" } == str) - binary_mode = BinaryFileMode::Binary; - else if (StringView { "skip" } == str) - binary_mode = BinaryFileMode::Skip; - else - return false; - return true; - }, - }); - args_parser.add_option(Core::ArgsParser::Option { - .requires_argument = false, - .help_string = "Treat binary files as text (same as --binary-mode text)", - .long_name = "text", - .short_name = 'a', - .accept_value = [&](auto) { - binary_mode = BinaryFileMode::Text; - return true; - }, - }); - args_parser.add_option(Core::ArgsParser::Option { - .requires_argument = false, - .help_string = "Ignore binary files (same as --binary-mode skip)", - .long_name = nullptr, - .short_name = 'I', - .accept_value = [&](auto) { - binary_mode = BinaryFileMode::Skip; - return true; - }, - }); - args_parser.add_positional_argument(files, "File(s) to process", "file", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (!use_ere) - return 0; - - // mock grep behaviour: if -e is omitted, use first positional argument as pattern - if (pattern == nullptr && files.size()) - pattern = files.take_first(); - - PosixOptions options {}; - if (case_insensitive) - options |= PosixFlags::Insensitive; - - Regex re(pattern, options); - if (re.parser_result.error != Error::NoError) { - return 1; - } - - auto matches = [&](StringView str, StringView filename = "", bool print_filename = false, bool is_binary = false) { - size_t last_printed_char_pos { 0 }; - if (is_binary && binary_mode == BinaryFileMode::Skip) - return false; - - auto result = re.match(str, PosixFlags::Global); - if (result.success) { - if (is_binary && binary_mode == BinaryFileMode::Binary) { - outln("binary file \x1B[34m{}\x1B[0m matches", filename); - } else { - if (result.matches.size() && print_filename) { - out("\x1B[34m{}:\x1B[0m", filename); - } - - for (auto& match : result.matches) { - - out("{}\x1B[32m{}\x1B[0m", - StringView(&str[last_printed_char_pos], match.global_offset - last_printed_char_pos), - match.view.to_string()); - last_printed_char_pos = match.global_offset + match.view.length(); - } - outln("{}", StringView(&str[last_printed_char_pos], str.length() - last_printed_char_pos)); - } - - return true; - } - - return false; - }; - - auto handle_file = [&matches, binary_mode](StringView filename, bool print_filename) -> bool { - auto file = Core::File::construct(filename); - if (!file->open(Core::IODevice::ReadOnly)) { - warnln("Failed to open {}: {}", filename, file->error_string()); - return false; - } - - while (file->can_read_line()) { - auto line = file->read_line(); - auto is_binary = memchr(line.characters(), 0, line.length()) != nullptr; - - if (matches(line, filename, print_filename, is_binary) && is_binary && binary_mode == BinaryFileMode::Binary) - return true; - } - return true; - }; - - auto add_directory = [&handle_file](String base, Optional recursive, auto handle_directory) -> void { - Core::DirIterator it(recursive.value_or(base), Core::DirIterator::Flags::SkipDots); - while (it.has_next()) { - auto path = it.next_full_path(); - if (!Core::File::is_directory(path)) { - auto key = path.substring_view(base.length() + 1, path.length() - base.length() - 1); - handle_file(key, true); - } else { - handle_directory(base, path, handle_directory); - } - } - }; - - if (!files.size() && !recursive) { - auto stdin_file = Core::File::standard_input(); - while (!stdin_file->eof()) { - auto line = stdin_file->read_line(); - bool is_binary = line.bytes().contains_slow(0); - - if (is_binary && binary_mode == BinaryFileMode::Skip) - return 1; - - if (matches(line, "stdin", false, is_binary) && is_binary && binary_mode == BinaryFileMode::Binary) - return 0; - } - } else { - if (recursive) { - add_directory(".", {}, add_directory); - - } else { - bool print_filename { files.size() > 1 }; - for (auto& filename : files) { - if (!handle_file(filename, print_filename)) - return 1; - } - } - } - - return 0; -} diff --git a/Userland/gron.cpp b/Userland/gron.cpp deleted file mode 100644 index 0c1b50eca9..0000000000 --- a/Userland/gron.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -static bool use_color = false; -static void print(const String& name, const JsonValue&, Vector& trail); - -static const char* color_name = ""; -static const char* color_index = ""; -static const char* color_brace = ""; -static const char* color_bool = ""; -static const char* color_null = ""; -static const char* color_string = ""; -static const char* color_off = ""; - -int main(int argc, char** argv) -{ - if (pledge("stdio tty rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (isatty(STDOUT_FILENO)) - use_color = true; - - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (argc != 2 || !strcmp(argv[1], "--help")) { - fprintf(stderr, "usage: gron \n"); - fprintf(stderr, "Print each value in a JSON file with its fully expanded key.\n"); - return 0; - } - auto file = Core::File::construct(argv[1]); - if (!file->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Couldn't open %s for reading: %s\n", argv[1], file->error_string()); - return 1; - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - auto file_contents = file->read_all(); - auto json = JsonValue::from_string(file_contents); - ASSERT(json.has_value()); - - if (use_color) { - color_name = "\033[33;1m"; - color_index = "\033[35;1m"; - color_brace = "\033[36m"; - color_bool = "\033[32;1m"; - color_string = "\033[31;1m"; - color_null = "\033[34;1m"; - color_off = "\033[0m"; - } - - Vector trail; - print("json", json.value(), trail); - return 0; -} - -static void print(const String& name, const JsonValue& value, Vector& trail) -{ - for (size_t i = 0; i < trail.size(); ++i) - printf("%s", trail[i].characters()); - - printf("%s%s%s = ", color_name, name.characters(), color_off); - - if (value.is_object()) { - printf("%s{}%s;\n", color_brace, color_off); - trail.append(String::format("%s%s%s.", color_name, name.characters(), color_off)); - value.as_object().for_each_member([&](auto& on, auto& ov) { print(on, ov, trail); }); - trail.take_last(); - return; - } - if (value.is_array()) { - printf("%s[]%s;\n", color_brace, color_off); - trail.append(String::format("%s%s%s", color_name, name.characters(), color_off)); - for (int i = 0; i < value.as_array().size(); ++i) { - auto element_name = String::format("%s%s[%s%s%d%s%s]%s", color_off, color_brace, color_off, color_index, i, color_off, color_brace, color_off); - print(element_name, value.as_array()[i], trail); - } - trail.take_last(); - return; - } - switch (value.type()) { - case JsonValue::Type::Null: - printf("%s", color_null); - break; - case JsonValue::Type::Bool: - printf("%s", color_bool); - break; - case JsonValue::Type::String: - printf("%s", color_string); - break; - default: - printf("%s", color_index); - break; - } - - printf("%s%s;\n", value.serialized().characters(), color_off); -} diff --git a/Userland/gunzip.cpp b/Userland/gunzip.cpp deleted file mode 100644 index 58678d1b88..0000000000 --- a/Userland/gunzip.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -static void decompress_file(Buffered& input_stream, Buffered& output_stream) -{ - auto gzip_stream = Compress::GzipDecompressor { input_stream }; - - u8 buffer[4096]; - - while (!gzip_stream.unreliable_eof()) { - const auto nread = gzip_stream.read({ buffer, sizeof(buffer) }); - output_stream.write_or_error({ buffer, nread }); - } -} - -int main(int argc, char** argv) -{ - Vector filenames; - bool keep_input_files { false }; - bool write_to_stdout { false }; - - Core::ArgsParser args_parser; - args_parser.add_option(keep_input_files, "Keep (don't delete) input files", "keep", 'k'); - args_parser.add_option(write_to_stdout, "Write to stdout, keep original files unchanged", "stdout", 'c'); - args_parser.add_positional_argument(filenames, "File to decompress", "FILE"); - args_parser.parse(argc, argv); - - if (write_to_stdout) - keep_input_files = true; - - for (String filename : filenames) { - if (!filename.ends_with(".gz")) - filename = String::format("%s.gz", filename.characters()); - - const auto input_filename = filename; - const auto output_filename = filename.substring_view(0, filename.length() - 3); - - auto input_stream_result = Core::InputFileStream::open_buffered(input_filename); - - if (write_to_stdout) { - auto stdout = Core::OutputFileStream::stdout_buffered(); - decompress_file(input_stream_result.value(), stdout); - } else { - auto output_stream_result = Core::OutputFileStream::open_buffered(output_filename); - decompress_file(input_stream_result.value(), output_stream_result.value()); - } - - if (!keep_input_files) { - const auto retval = unlink(String { input_filename }.characters()); - ASSERT(retval == 0); - } - } -} diff --git a/Userland/head.cpp b/Userland/head.cpp deleted file mode 100644 index 132df61ee6..0000000000 --- a/Userland/head.cpp +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int head(const String& filename, bool print_filename, int line_count, int char_count); - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - int line_count = 0; - int char_count = 0; - bool never_print_filenames = false; - bool always_print_filenames = false; - Vector files; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Print the beginning ('head') of a file."); - args_parser.add_option(line_count, "Number of lines to print (default 10)", "lines", 'n', "number"); - args_parser.add_option(char_count, "Number of characters to print", "characters", 'c', "number"); - args_parser.add_option(never_print_filenames, "Never print file names", "quiet", 'q'); - args_parser.add_option(always_print_filenames, "Always print file names", "verbose", 'v'); - args_parser.add_positional_argument(files, "File to process", "file", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (line_count == 0 && char_count == 0) { - line_count = 10; - } - - bool print_filenames = files.size() > 1; - if (always_print_filenames) - print_filenames = true; - else if (never_print_filenames) - print_filenames = false; - - if (files.is_empty()) { - return head("", print_filenames, line_count, char_count); - } - - int rc = 0; - - for (auto& file : files) { - if (head(file, print_filenames, line_count, char_count) != 0) { - rc = 1; - } - } - - return rc; -} - -int head(const String& filename, bool print_filename, int line_count, int char_count) -{ - bool is_stdin = false; - FILE* fp = nullptr; - - if (filename == "" || filename == "-") { - fp = stdin; - is_stdin = true; - } else { - fp = fopen(filename.characters(), "r"); - if (!fp) { - fprintf(stderr, "can't open %s for reading: %s\n", filename.characters(), strerror(errno)); - return 1; - } - } - - if (print_filename) { - if (is_stdin) { - puts("==> standard input <=="); - } else { - printf("==> %s <==\n", filename.characters()); - } - } - - if (line_count) { - for (int line = 0; line < line_count; ++line) { - char buffer[BUFSIZ]; - auto* str = fgets(buffer, sizeof(buffer), fp); - if (!str) - break; - - // specifically use fputs rather than puts, because fputs doesn't add - // its own newline. - fputs(str, stdout); - } - } else if (char_count) { - char buffer[BUFSIZ]; - - while (char_count) { - int nread = fread(buffer, 1, min(BUFSIZ, char_count), fp); - if (nread > 0) { - int ncomplete = 0; - - while (ncomplete < nread) { - int nwrote = fwrite(&buffer[ncomplete], 1, nread - ncomplete, stdout); - if (nwrote > 0) - ncomplete += nwrote; - - if (feof(stdout)) { - fprintf(stderr, "unexpected eof writing to stdout\n"); - return 1; - } - - if (ferror(stdout)) { - fprintf(stderr, "error writing to stdout\n"); - return 1; - } - } - } - - char_count -= nread; - - if (feof(fp)) - break; - - if (ferror(fp)) { - fprintf(stderr, "error reading input\n"); - break; - } - } - } - - fclose(fp); - - if (print_filename) { - puts(""); - } - - return 0; -} diff --git a/Userland/hexdump.cpp b/Userland/hexdump.cpp deleted file mode 100644 index a4c4b04cb8..0000000000 --- a/Userland/hexdump.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - Core::ArgsParser args_parser; - const char* path = nullptr; - args_parser.add_positional_argument(path, "Input", "input", Core::ArgsParser::Required::No); - - args_parser.parse(argc, argv); - - RefPtr file; - - if (!path) { - file = Core::File::standard_input(); - } else { - auto file_or_error = Core::File::open(path, Core::File::ReadOnly); - if (file_or_error.is_error()) { - warnln("Failed to open {}: {}", path, file_or_error.error()); - return 1; - } - file = file_or_error.value(); - } - - auto contents = file->read_all(); - - Vector line; - - auto print_line = [&] { - for (size_t i = 0; i < 16; ++i) { - if (i < line.size()) - printf("%02x ", line[i]); - else - printf(" "); - - if (i == 7) - printf(" "); - } - - printf(" "); - - for (size_t i = 0; i < 16; ++i) { - if (i < line.size() && isprint(line[i])) - putchar(line[i]); - else - putchar(' '); - } - - putchar('\n'); - }; - - for (size_t i = 0; i < contents.size(); ++i) { - line.append(contents[i]); - - if (line.size() == 16) { - print_line(); - line.clear(); - } - } - - if (!line.is_empty()) - print_line(); - - return 0; -} diff --git a/Userland/host.cpp b/Userland/host.cpp deleted file mode 100644 index d77054b868..0000000000 --- a/Userland/host.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio dns", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* name_or_ip = nullptr; - Core::ArgsParser args_parser; - args_parser.set_general_help("Convert between domain name and IPv4 address."); - args_parser.add_positional_argument(name_or_ip, "Domain name or IPv4 address", "name"); - args_parser.parse(argc, argv); - - // If input looks like an IPv4 address, we should do a reverse lookup. - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(53); - int rc = inet_pton(AF_INET, name_or_ip, &addr.sin_addr); - if (rc == 1) { - // Okay, let's do a reverse lookup. - auto* hostent = gethostbyaddr(&addr.sin_addr, sizeof(in_addr), AF_INET); - if (!hostent) { - fprintf(stderr, "Reverse lookup failed for '%s'\n", name_or_ip); - return 1; - } - printf("%s is %s\n", name_or_ip, hostent->h_name); - return 0; - } - - auto* hostent = gethostbyname(name_or_ip); - if (!hostent) { - fprintf(stderr, "Lookup failed for '%s'\n", name_or_ip); - return 1; - } - - char buffer[32]; - const char* ip_str = inet_ntop(AF_INET, hostent->h_addr_list[0], buffer, sizeof(buffer)); - - printf("%s is %s\n", name_or_ip, ip_str); - return 0; -} diff --git a/Userland/hostname.cpp b/Userland/hostname.cpp deleted file mode 100644 index 68c6dfe030..0000000000 --- a/Userland/hostname.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - const char* hostname = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(hostname, "Hostname to set", "hostname", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (!hostname) { - char buffer[HOST_NAME_MAX]; - int rc = gethostname(buffer, sizeof(buffer)); - if (rc < 0) { - perror("gethostname"); - return 1; - } - printf("%s\n", buffer); - } else { - if (strlen(hostname) >= HOST_NAME_MAX) { - fprintf(stderr, "Hostname must be less than %i characters\n", HOST_NAME_MAX); - return 1; - } - sethostname(hostname, strlen(hostname)); - } - return 0; -} diff --git a/Userland/html.cpp b/Userland/html.cpp deleted file mode 100644 index b6e2524474..0000000000 --- a/Userland/html.cpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - auto app = GUI::Application::construct(argc, argv); - - auto f = Core::File::construct(); - URL url; - bool success; - if (argc < 2) { - success = f->open(STDIN_FILENO, Core::IODevice::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No); - } else { - url = URL::create_with_file_protocol(argv[1]); - f->set_filename(argv[1]); - success = f->open(Core::IODevice::OpenMode::ReadOnly); - } - if (!success) { - fprintf(stderr, "Error: %s\n", f->error_string()); - return 1; - } - - auto html = f->read_all(); - - auto window = GUI::Window::construct(); - window->set_title("HTML"); - auto& widget = window->set_main_widget(); - widget.on_title_change = [&](auto& title) { - window->set_title(String::formatted("{} - HTML", title)); - }; - widget.load_html(html, url); - window->show(); - - auto menubar = GUI::MenuBar::construct(); - - auto& app_menu = menubar->add_menu("HTML"); - app_menu.add_action(GUI::CommonActions::make_quit_action([&](auto&) { - app->quit(); - })); - - auto& help_menu = menubar->add_menu("Help"); - help_menu.add_action(GUI::CommonActions::make_about_action("HTML", GUI::Icon::default_icon("filetype-html"), window)); - - app->set_menubar(move(menubar)); - - window->set_icon(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png")); - - return app->exec(); -} diff --git a/Userland/id.cpp b/Userland/id.cpp deleted file mode 100644 index f0caa31508..0000000000 --- a/Userland/id.cpp +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -static int print_id_objects(); - -static bool flag_print_uid = false; -static bool flag_print_gid = false; -static bool flag_print_name = false; -static bool flag_print_gid_all = false; - -int main(int argc, char** argv) -{ - if (unveil("/etc/passwd", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/etc/group", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil(nullptr, nullptr) < 0) { - perror("unveil"); - return 1; - } - - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Core::ArgsParser args_parser; - args_parser.add_option(flag_print_uid, "Print UID", nullptr, 'u'); - args_parser.add_option(flag_print_gid, "Print GID", nullptr, 'g'); - args_parser.add_option(flag_print_gid_all, "Print all GIDs", nullptr, 'G'); - args_parser.add_option(flag_print_name, "Print name", nullptr, 'n'); - args_parser.parse(argc, argv); - - if (flag_print_name && !(flag_print_uid || flag_print_gid || flag_print_gid_all)) { - fprintf(stderr, "cannot print only names or real IDs in default format\n"); - return 1; - } - - if (flag_print_uid + flag_print_gid + flag_print_gid_all > 1) { - fprintf(stderr, "cannot print \"only\" of more than one choice\n"); - return 1; - } - - int status = print_id_objects(); - return status; -} - -static bool print_uid_object(uid_t uid) -{ - if (flag_print_name) { - struct passwd* pw = getpwuid(uid); - printf("%s", pw ? pw->pw_name : "n/a"); - } else - printf("%u", uid); - - return true; -} - -static bool print_gid_object(gid_t gid) -{ - if (flag_print_name) { - struct group* gr = getgrgid(gid); - printf("%s", gr ? gr->gr_name : "n/a"); - } else - printf("%u", gid); - return true; -} - -static bool print_gid_list() -{ - int extra_gid_count = getgroups(0, nullptr); - if (extra_gid_count) { - auto* extra_gids = (gid_t*)alloca(extra_gid_count * sizeof(gid_t)); - int rc = getgroups(extra_gid_count, extra_gids); - - if (rc < 0) { - perror("\ngetgroups"); - return false; - } - - for (int g = 0; g < extra_gid_count; ++g) { - auto* gr = getgrgid(extra_gids[g]); - if (flag_print_name && gr) - printf("%s", gr->gr_name); - else - printf("%u", extra_gids[g]); - if (g != extra_gid_count - 1) - printf(" "); - } - } - return true; -} - -static bool print_full_id_list() -{ - - uid_t uid = getuid(); - gid_t gid = getgid(); - struct passwd* pw = getpwuid(uid); - struct group* gr = getgrgid(gid); - - printf("uid=%u(%s) gid=%u(%s)", uid, pw ? pw->pw_name : "n/a", gid, gr ? gr->gr_name : "n/a"); - - int extra_gid_count = getgroups(0, nullptr); - if (extra_gid_count) { - auto* extra_gids = (gid_t*)alloca(extra_gid_count * sizeof(gid_t)); - int rc = getgroups(extra_gid_count, extra_gids); - if (rc < 0) { - perror("\ngetgroups"); - return false; - } - printf(" groups="); - for (int g = 0; g < extra_gid_count; ++g) { - auto* gr = getgrgid(extra_gids[g]); - if (gr) - printf("%u(%s)", extra_gids[g], gr->gr_name); - else - printf("%u", extra_gids[g]); - if (g != extra_gid_count - 1) - printf(","); - } - } - return true; -} - -static int print_id_objects() -{ - if (flag_print_uid) { - if (!print_uid_object(getuid())) - return 1; - } else if (flag_print_gid) { - if (!print_gid_object(getgid())) - return 1; - } else if (flag_print_gid_all) { - if (!print_gid_list()) - return 1; - } else { - if (!print_full_id_list()) - return 1; - } - - printf("\n"); - return 0; -} diff --git a/Userland/ifconfig.cpp b/Userland/ifconfig.cpp deleted file mode 100644 index 2a1b000d9b..0000000000 --- a/Userland/ifconfig.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - const char* value_ipv4 = nullptr; - const char* value_adapter = nullptr; - const char* value_gateway = nullptr; - const char* value_mask = nullptr; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Display or modify the configuration of each network interface."); - args_parser.add_option(value_ipv4, "Set the IP address of the selected network", "ipv4", 'i', "The new IP of the network"); - args_parser.add_option(value_adapter, "Select a specific network adapter to configure", "adapter", 'a', "The name of a network adapter"); - args_parser.add_option(value_gateway, "Set the default gateway of the selected network", "gateway", 'g', "The new IP of the gateway"); - args_parser.add_option(value_mask, "Set the network mask of the selected network", "mask", 'm', "The new network mask"); - args_parser.parse(argc, argv); - - if (!value_ipv4 && !value_adapter && !value_gateway && !value_mask) { - - auto file = Core::File::construct("/proc/net/adapters"); - if (!file->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Error: %s\n", file->error_string()); - return 1; - } - - auto file_contents = file->read_all(); - auto json = JsonValue::from_string(file_contents); - ASSERT(json.has_value()); - json.value().as_array().for_each([](auto& value) { - auto if_object = value.as_object(); - - auto name = if_object.get("name").to_string(); - auto class_name = if_object.get("class_name").to_string(); - auto mac_address = if_object.get("mac_address").to_string(); - auto ipv4_address = if_object.get("ipv4_address").to_string(); - auto gateway = if_object.get("ipv4_gateway").to_string(); - auto netmask = if_object.get("ipv4_netmask").to_string(); - auto packets_in = if_object.get("packets_in").to_u32(); - auto bytes_in = if_object.get("bytes_in").to_u32(); - auto packets_out = if_object.get("packets_out").to_u32(); - auto bytes_out = if_object.get("bytes_out").to_u32(); - auto mtu = if_object.get("mtu").to_u32(); - - printf("%s:\n", name.characters()); - printf("\tmac: %s\n", mac_address.characters()); - printf("\tipv4: %s\n", ipv4_address.characters()); - printf("\tnetmask: %s\n", netmask.characters()); - printf("\tgateway: %s\n", gateway.characters()); - printf("\tclass: %s\n", class_name.characters()); - printf("\tRX: %u packets %u bytes (%s)\n", packets_in, bytes_in, human_readable_size(bytes_in).characters()); - printf("\tTX: %u packets %u bytes (%s)\n", packets_out, bytes_out, human_readable_size(bytes_out).characters()); - printf("\tMTU: %u\n", mtu); - printf("\n"); - }); - } else { - - if (!value_adapter) { - fprintf(stderr, "No network adapter was specified.\n"); - return 1; - } - - String ifname = value_adapter; - - if (value_ipv4) { - auto address = IPv4Address::from_string(value_ipv4); - - if (!address.has_value()) { - fprintf(stderr, "Invalid IPv4 address: '%s'\n", value_ipv4); - return 1; - } - - int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (fd < 0) { - perror("socket"); - return 1; - } - - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - - bool fits = ifname.copy_characters_to_buffer(ifr.ifr_name, IFNAMSIZ); - if (!fits) { - fprintf(stderr, "Interface name '%s' is too long\n", ifname.characters()); - return 1; - } - ifr.ifr_addr.sa_family = AF_INET; - ((sockaddr_in&)ifr.ifr_addr).sin_addr.s_addr = address.value().to_in_addr_t(); - - int rc = ioctl(fd, SIOCSIFADDR, &ifr); - if (rc < 0) { - perror("ioctl(SIOCSIFADDR)"); - return 1; - } - } - - if (value_mask) { - auto address = IPv4Address::from_string(value_mask); - - if (!address.has_value()) { - fprintf(stderr, "Invalid IPv4 mask: '%s'\n", value_mask); - return 1; - } - - int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (fd < 0) { - perror("socket"); - return 1; - } - - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - - bool fits = ifname.copy_characters_to_buffer(ifr.ifr_name, IFNAMSIZ); - if (!fits) { - fprintf(stderr, "Interface name '%s' is too long\n", ifname.characters()); - return 1; - } - ifr.ifr_netmask.sa_family = AF_INET; - ((sockaddr_in&)ifr.ifr_netmask).sin_addr.s_addr = address.value().to_in_addr_t(); - - int rc = ioctl(fd, SIOCSIFNETMASK, &ifr); - if (rc < 0) { - perror("ioctl(SIOCSIFNETMASK)"); - return 1; - } - } - - if (value_gateway) { - auto address = IPv4Address::from_string(value_gateway); - - int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); - if (fd < 0) { - perror("socket"); - return 1; - } - - struct rtentry rt; - memset(&rt, 0, sizeof(rt)); - - rt.rt_dev = const_cast(ifname.characters()); - rt.rt_gateway.sa_family = AF_INET; - ((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = address.value().to_in_addr_t(); - rt.rt_flags = RTF_UP | RTF_GATEWAY; - - int rc = ioctl(fd, SIOCADDRT, &rt); - if (rc < 0) { - perror("ioctl(SIOCADDRT)"); - return 1; - } - } - } - return 0; -} diff --git a/Userland/ini.cpp b/Userland/ini.cpp deleted file mode 100644 index 5ac2c445a3..0000000000 --- a/Userland/ini.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2020, Linus Groh - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath wpath cpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* path = nullptr; - const char* group = nullptr; - const char* key = nullptr; - const char* value_to_write = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(path, "Path to INI file", "path"); - args_parser.add_positional_argument(group, "Group name", "group"); - args_parser.add_positional_argument(key, "Key name", "key"); - args_parser.add_positional_argument(value_to_write, "Value to write", "value", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (!Core::File::exists(path)) { - fprintf(stderr, "File does not exist: '%s'\n", path); - return 1; - } - - auto config = Core::ConfigFile::open(path); - - if (value_to_write) { - config->write_entry(group, key, value_to_write); - config->sync(); - return 0; - } - - auto value = config->read_entry(group, key); - if (!value.is_empty()) - printf("%s\n", value.characters()); - - return 0; -} diff --git a/Userland/jp.cpp b/Userland/jp.cpp deleted file mode 100644 index cb7bef1f95..0000000000 --- a/Userland/jp.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static void print(const JsonValue& value, int indent = 0, bool use_color = true); -static void print_indent(int indent) -{ - for (int i = 0; i < indent; ++i) - out(" "); -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* path = nullptr; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Pretty-print a JSON file with syntax-coloring and indentation."); - args_parser.add_positional_argument(path, "Path to JSON file", "path"); - args_parser.parse(argc, argv); - - auto file = Core::File::construct(path); - if (!file->open(Core::IODevice::ReadOnly)) { - warnln("Couldn't open {} for reading: {}", path, file->error_string()); - return 1; - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - auto file_contents = file->read_all(); - auto json = JsonValue::from_string(file_contents); - if (!json.has_value()) { - warnln("Couldn't parse {} as JSON", path); - return 1; - } - - print(json.value(), 0, isatty(STDOUT_FILENO)); - outln(); - - return 0; -} - -void print(const JsonValue& value, int indent, bool use_color) -{ - if (value.is_object()) { - size_t printed_members = 0; - auto object = value.as_object(); - outln("{{"); - object.for_each_member([&](auto& member_name, auto& member_value) { - ++printed_members; - print_indent(indent + 1); - if (use_color) - out("\"\033[33;1m{}\033[0m\": ", member_name); - else - out("\"{}\": ", member_name); - print(member_value, indent + 1, use_color); - if (printed_members < static_cast(object.size())) - out(","); - outln(); - }); - print_indent(indent); - out("}}"); - return; - } - if (value.is_array()) { - size_t printed_entries = 0; - auto array = value.as_array(); - outln("["); - array.for_each([&](auto& entry_value) { - ++printed_entries; - print_indent(indent + 1); - print(entry_value, indent + 1, use_color); - if (printed_entries < static_cast(array.size())) - out(","); - outln(); - }); - print_indent(indent); - out("]"); - return; - } - if (use_color) { - if (value.is_string()) - out("\033[31;1m"); - else if (value.is_number()) - out("\033[35;1m"); - else if (value.is_bool()) - out("\033[32;1m"); - else if (value.is_null()) - out("\033[34;1m"); - } - if (value.is_string()) - out("\""); - out("{}", value.to_string()); - if (value.is_string()) - out("\""); - if (use_color) - out("\033[0m"); -} diff --git a/Userland/js.cpp b/Userland/js.cpp deleted file mode 100644 index 036c097d7a..0000000000 --- a/Userland/js.cpp +++ /dev/null @@ -1,918 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * Copyright (c) 2020, Linus Groh - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#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 -#include -#include -#include - -RefPtr vm; -Vector repl_statements; - -class ReplObject : public JS::GlobalObject { -public: - ReplObject(); - virtual void initialize() override; - virtual ~ReplObject() override; - -private: - virtual const char* class_name() const override { return "ReplObject"; } - - JS_DECLARE_NATIVE_FUNCTION(exit_interpreter); - JS_DECLARE_NATIVE_FUNCTION(repl_help); - JS_DECLARE_NATIVE_FUNCTION(load_file); - JS_DECLARE_NATIVE_FUNCTION(save_to_file); -}; - -static bool s_dump_ast = false; -static bool s_print_last_result = false; -static RefPtr s_editor; -static String s_history_path = String::formatted("{}/.js-history", Core::StandardPaths::home_directory()); -static int s_repl_line_level = 0; -static bool s_fail_repl = false; - -static String prompt_for_level(int level) -{ - static StringBuilder prompt_builder; - prompt_builder.clear(); - prompt_builder.append("> "); - - for (auto i = 0; i < level; ++i) - prompt_builder.append(" "); - - return prompt_builder.build(); -} - -static String read_next_piece() -{ - StringBuilder piece; - - auto line_level_delta_for_next_line { 0 }; - - do { - auto line_result = s_editor->get_line(prompt_for_level(s_repl_line_level)); - - line_level_delta_for_next_line = 0; - - if (line_result.is_error()) { - s_fail_repl = true; - return ""; - } - - auto& line = line_result.value(); - s_editor->add_to_history(line); - - piece.append(line); - auto lexer = JS::Lexer(line); - - enum { - NotInLabelOrObjectKey, - InLabelOrObjectKeyIdentifier, - InLabelOrObjectKey - } label_state { NotInLabelOrObjectKey }; - - for (JS::Token token = lexer.next(); token.type() != JS::TokenType::Eof; token = lexer.next()) { - switch (token.type()) { - case JS::TokenType::BracketOpen: - case JS::TokenType::CurlyOpen: - case JS::TokenType::ParenOpen: - label_state = NotInLabelOrObjectKey; - s_repl_line_level++; - break; - case JS::TokenType::BracketClose: - case JS::TokenType::CurlyClose: - case JS::TokenType::ParenClose: - label_state = NotInLabelOrObjectKey; - s_repl_line_level--; - break; - - case JS::TokenType::Identifier: - case JS::TokenType::StringLiteral: - if (label_state == NotInLabelOrObjectKey) - label_state = InLabelOrObjectKeyIdentifier; - else - label_state = NotInLabelOrObjectKey; - break; - case JS::TokenType::Colon: - if (label_state == InLabelOrObjectKeyIdentifier) - label_state = InLabelOrObjectKey; - else - label_state = NotInLabelOrObjectKey; - break; - default: - break; - } - } - - if (label_state == InLabelOrObjectKey) { - // If there's a label or object literal key at the end of this line, - // prompt for more lines but do not change the line level. - line_level_delta_for_next_line += 1; - } - } while (s_repl_line_level + line_level_delta_for_next_line > 0); - - return piece.to_string(); -} - -static void print_value(JS::Value value, HashTable& seen_objects); - -static void print_type(const FlyString& name) -{ - out("[\033[36;1m{}\033[0m]", name); -} - -static void print_separator(bool& first) -{ - out(first ? " " : ", "); - first = false; -} - -static void print_array(JS::Array& array, HashTable& seen_objects) -{ - out("["); - bool first = true; - for (auto it = array.indexed_properties().begin(false); it != array.indexed_properties().end(); ++it) { - print_separator(first); - auto value = it.value_and_attributes(&array).value; - // The V8 repl doesn't throw an exception here, and instead just - // prints 'undefined'. We may choose to replicate that behavior in - // the future, but for now lets just catch the error - if (vm->exception()) - return; - print_value(value, seen_objects); - } - if (!first) - out(" "); - out("]"); -} - -static void print_object(JS::Object& object, HashTable& seen_objects) -{ - out("{{"); - bool first = true; - for (auto& entry : object.indexed_properties()) { - print_separator(first); - out("\"\033[33;1m{}\033[0m\": ", entry.index()); - auto value = entry.value_and_attributes(&object).value; - // The V8 repl doesn't throw an exception here, and instead just - // prints 'undefined'. We may choose to replicate that behavior in - // the future, but for now lets just catch the error - if (vm->exception()) - return; - print_value(value, seen_objects); - } - for (auto& it : object.shape().property_table_ordered()) { - print_separator(first); - if (it.key.is_string()) { - out("\"\033[33;1m{}\033[0m\": ", it.key.to_display_string()); - } else { - out("[\033[33;1m{}\033[0m]: ", it.key.to_display_string()); - } - print_value(object.get_direct(it.value.offset), seen_objects); - } - if (!first) - out(" "); - out("}}"); -} - -static void print_function(const JS::Object& object, HashTable&) -{ - print_type(object.class_name()); - if (is(object)) - out(" {}", static_cast(object).name()); - else if (is(object)) - out(" {}", static_cast(object).name()); -} - -static void print_date(const JS::Object& date, HashTable&) -{ - print_type("Date"); - out(" \033[34;1m{}\033[0m", static_cast(date).string()); -} - -static void print_error(const JS::Object& object, HashTable&) -{ - auto& error = static_cast(object); - print_type(error.name()); - if (!error.message().is_empty()) - out(" \033[31;1m{}\033[0m", error.message()); -} - -static void print_regexp_object(const JS::Object& object, HashTable&) -{ - auto& regexp_object = static_cast(object); - // Use RegExp.prototype.source rather than RegExpObject::pattern() so we get proper escaping - auto source = regexp_object.get("source").to_primitive_string(object.global_object())->string(); - print_type("RegExp"); - out(" \033[34;1m/{}/{}\033[0m", source, regexp_object.flags()); -} - -static void print_proxy_object(const JS::Object& object, HashTable& seen_objects) -{ - auto& proxy_object = static_cast(object); - print_type("Proxy"); - out("\n target: "); - print_value(&proxy_object.target(), seen_objects); - out("\n handler: "); - print_value(&proxy_object.handler(), seen_objects); -} - -static void print_array_buffer(const JS::Object& object, HashTable& seen_objects) -{ - auto& array_buffer = static_cast(object); - auto& buffer = array_buffer.buffer(); - auto byte_length = array_buffer.byte_length(); - print_type("ArrayBuffer"); - out("\n byteLength: "); - print_value(JS::Value((double)byte_length), seen_objects); - outln(); - for (size_t i = 0; i < byte_length; ++i) { - out("{:02x}", buffer[i]); - if (i + 1 < byte_length) { - if ((i + 1) % 32 == 0) - outln(); - else if ((i + 1) % 16 == 0) - out(" "); - else - out(" "); - } - } -} - -static void print_typed_array(const JS::Object& object, HashTable& seen_objects) -{ - auto& typed_array_base = static_cast(object); - auto length = typed_array_base.array_length(); - print_type(object.class_name()); - out("\n length: "); - print_value(JS::Value(length), seen_objects); - out("\n byteLength: "); - print_value(JS::Value(typed_array_base.byte_length()), seen_objects); - out("\n buffer: "); - print_type("ArrayBuffer"); - out(" @ {:p}", typed_array_base.viewed_array_buffer()); - if (!length) - return; - outln(); - // FIXME: This kinda sucks. -#define __JS_ENUMERATE(ClassName, snake_name, PrototypeName, ConstructorName, ArrayType) \ - if (StringView(object.class_name()) == StringView(#ClassName)) { \ - out("[ "); \ - auto& typed_array = static_cast(typed_array_base); \ - auto data = typed_array.data(); \ - for (size_t i = 0; i < length; ++i) { \ - if (i > 0) \ - out(", "); \ - print_value(JS::Value(data[i]), seen_objects); \ - } \ - out(" ]"); \ - return; \ - } - JS_ENUMERATE_TYPED_ARRAYS -#undef __JS_ENUMERATE - ASSERT_NOT_REACHED(); -} - -static void print_primitive_wrapper_object(const FlyString& name, const JS::Object& object, HashTable& seen_objects) -{ - // BooleanObject, NumberObject, StringObject - print_type(name); - out(" "); - print_value(object.value_of(), seen_objects); -} - -static void print_value(JS::Value value, HashTable& seen_objects) -{ - if (value.is_empty()) { - out("\033[34;1m\033[0m"); - return; - } - - if (value.is_object()) { - if (seen_objects.contains(&value.as_object())) { - // FIXME: Maybe we should only do this for circular references, - // not for all reoccurring objects. - out("", &value.as_object()); - return; - } - seen_objects.set(&value.as_object()); - } - - if (value.is_array()) - return print_array(static_cast(value.as_object()), seen_objects); - - if (value.is_object()) { - auto& object = value.as_object(); - if (object.is_function()) - return print_function(object, seen_objects); - if (is(object)) - return print_date(object, seen_objects); - if (is(object)) - return print_error(object, seen_objects); - if (is(object)) - return print_regexp_object(object, seen_objects); - if (is(object)) - return print_proxy_object(object, seen_objects); - if (is(object)) - return print_array_buffer(object, seen_objects); - if (object.is_typed_array()) - return print_typed_array(object, seen_objects); - if (is(object)) - return print_primitive_wrapper_object("String", object, seen_objects); - if (is(object)) - return print_primitive_wrapper_object("Number", object, seen_objects); - if (is(object)) - return print_primitive_wrapper_object("Boolean", object, seen_objects); - return print_object(object, seen_objects); - } - - if (value.is_string()) - out("\033[32;1m"); - else if (value.is_number() || value.is_bigint()) - out("\033[35;1m"); - else if (value.is_boolean()) - out("\033[33;1m"); - else if (value.is_null()) - out("\033[33;1m"); - else if (value.is_undefined()) - out("\033[34;1m"); - if (value.is_string()) - out("\""); - else if (value.is_negative_zero()) - out("-"); - out("{}", value.to_string_without_side_effects()); - if (value.is_string()) - out("\""); - out("\033[0m"); -} - -static void print(JS::Value value) -{ - HashTable seen_objects; - print_value(value, seen_objects); - outln(); -} - -static bool file_has_shebang(AK::ByteBuffer file_contents) -{ - if (file_contents.size() >= 2 && file_contents[0] == '#' && file_contents[1] == '!') - return true; - return false; -} - -static StringView strip_shebang(AK::ByteBuffer file_contents) -{ - size_t i = 0; - for (i = 2; i < file_contents.size(); ++i) { - if (file_contents[i] == '\n') - break; - } - return StringView((const char*)file_contents.data() + i, file_contents.size() - i); -} - -static bool write_to_file(const StringView& path) -{ - int fd = open_with_path_length(path.characters_without_null_termination(), path.length(), O_WRONLY | O_CREAT | O_TRUNC, 0666); - for (size_t i = 0; i < repl_statements.size(); i++) { - auto line = repl_statements[i]; - if (line.length() && i != repl_statements.size() - 1) { - ssize_t nwritten = write(fd, line.characters(), line.length()); - if (nwritten < 0) { - close(fd); - return false; - } - } - if (i != repl_statements.size() - 1) { - char ch = '\n'; - ssize_t nwritten = write(fd, &ch, 1); - if (nwritten != 1) { - perror("write"); - close(fd); - return false; - } - } - } - close(fd); - return true; -} - -static bool parse_and_run(JS::Interpreter& interpreter, const StringView& source) -{ - auto parser = JS::Parser(JS::Lexer(source)); - auto program = parser.parse_program(); - - if (s_dump_ast) - program->dump(0); - - if (parser.has_errors()) { - auto error = parser.errors()[0]; - auto hint = error.source_location_hint(source); - if (!hint.is_empty()) - outln("{}", hint); - vm->throw_exception(interpreter.global_object(), error.to_string()); - } else { - interpreter.run(interpreter.global_object(), *program); - } - - if (vm->exception()) { - out("Uncaught exception: "); - print(vm->exception()->value()); - auto trace = vm->exception()->trace(); - if (trace.size() > 1) { - unsigned repetitions = 0; - for (size_t i = 0; i < trace.size(); ++i) { - auto& function_name = trace[i]; - if (i + 1 < trace.size() && trace[i + 1] == function_name) { - repetitions++; - continue; - } - if (repetitions > 4) { - // If more than 5 (1 + >4) consecutive function calls with the same name, print - // the name only once and show the number of repetitions instead. This prevents - // printing ridiculously large call stacks of recursive functions. - outln(" -> {}", function_name); - outln(" {} more calls", repetitions); - } else { - for (size_t j = 0; j < repetitions + 1; ++j) - outln(" -> {}", function_name); - } - repetitions = 0; - } - } - vm->clear_exception(); - return false; - } - if (s_print_last_result) - print(vm->last_value()); - return true; -} - -ReplObject::ReplObject() -{ -} - -void ReplObject::initialize() -{ - GlobalObject::initialize(); - define_property("global", this, JS::Attribute::Enumerable); - define_native_function("exit", exit_interpreter); - define_native_function("help", repl_help); - define_native_function("load", load_file, 1); - define_native_function("save", save_to_file, 1); -} - -ReplObject::~ReplObject() -{ -} - -JS_DEFINE_NATIVE_FUNCTION(ReplObject::save_to_file) -{ - if (!vm.argument_count()) - return JS::Value(false); - String save_path = vm.argument(0).to_string_without_side_effects(); - StringView path = StringView(save_path.characters()); - if (write_to_file(path)) { - return JS::Value(true); - } - return JS::Value(false); -} - -JS_DEFINE_NATIVE_FUNCTION(ReplObject::exit_interpreter) -{ - if (!vm.argument_count()) - exit(0); - auto exit_code = vm.argument(0).to_number(global_object); - if (::vm->exception()) - return {}; - exit(exit_code.as_double()); -} - -JS_DEFINE_NATIVE_FUNCTION(ReplObject::repl_help) -{ - outln("REPL commands:"); - outln(" exit(code): exit the REPL with specified code. Defaults to 0."); - outln(" help(): display this menu"); - outln(" load(files): accepts file names as params to load into running session. For example load(\"js/1.js\", \"js/2.js\", \"js/3.js\")"); - outln(" save(file): accepts a file name, writes REPL input history to a file. For example: save(\"foo.txt\")"); - return JS::js_undefined(); -} - -JS_DEFINE_NATIVE_FUNCTION(ReplObject::load_file) -{ - if (!vm.argument_count()) - return JS::Value(false); - - for (auto& file : vm.call_frame().arguments) { - String file_name = file.as_string().string(); - auto js_file = Core::File::construct(file_name); - if (!js_file->open(Core::IODevice::ReadOnly)) { - warnln("Failed to open {}: {}", file_name, js_file->error_string()); - continue; - } - auto file_contents = js_file->read_all(); - - StringView source; - if (file_has_shebang(file_contents)) { - source = strip_shebang(file_contents); - } else { - source = file_contents; - } - parse_and_run(vm.interpreter(), source); - } - return JS::Value(true); -} - -static void repl(JS::Interpreter& interpreter) -{ - while (!s_fail_repl) { - String piece = read_next_piece(); - if (piece.is_empty()) - continue; - repl_statements.append(piece); - parse_and_run(interpreter, piece); - } -} - -static Function interrupt_interpreter; -static void sigint_handler() -{ - interrupt_interpreter(); -} - -class ReplConsoleClient final : public JS::ConsoleClient { -public: - ReplConsoleClient(JS::Console& console) - : ConsoleClient(console) - { - } - - virtual JS::Value log() override - { - outln("{}", vm().join_arguments()); - return JS::js_undefined(); - } - virtual JS::Value info() override - { - outln("(i) {}", vm().join_arguments()); - return JS::js_undefined(); - } - virtual JS::Value debug() override - { - outln("\033[36;1m{}\033[0m", vm().join_arguments()); - return JS::js_undefined(); - } - virtual JS::Value warn() override - { - outln("\033[33;1m{}\033[0m", vm().join_arguments()); - return JS::js_undefined(); - } - virtual JS::Value error() override - { - outln("\033[31;1m{}\033[0m", vm().join_arguments()); - return JS::js_undefined(); - } - virtual JS::Value clear() override - { - out("\033[3J\033[H\033[2J"); - fflush(stdout); - return JS::js_undefined(); - } - virtual JS::Value trace() override - { - outln("{}", vm().join_arguments()); - auto trace = get_trace(); - for (auto& function_name : trace) { - if (function_name.is_empty()) - function_name = ""; - outln(" -> {}", function_name); - } - return JS::js_undefined(); - } - virtual JS::Value count() override - { - auto label = vm().argument_count() ? vm().argument(0).to_string_without_side_effects() : "default"; - auto counter_value = m_console.counter_increment(label); - outln("{}: {}", label, counter_value); - return JS::js_undefined(); - } - virtual JS::Value count_reset() override - { - auto label = vm().argument_count() ? vm().argument(0).to_string_without_side_effects() : "default"; - if (m_console.counter_reset(label)) - outln("{}: 0", label); - else - outln("\033[33;1m\"{}\" doesn't have a count\033[0m", label); - return JS::js_undefined(); - } -}; - -int main(int argc, char** argv) -{ - bool gc_on_every_allocation = false; - bool disable_syntax_highlight = false; - const char* script_path = nullptr; - - Core::ArgsParser args_parser; - args_parser.set_general_help("This is a JavaScript interpreter."); - args_parser.add_option(s_dump_ast, "Dump the AST", "dump-ast", 'A'); - args_parser.add_option(s_print_last_result, "Print last result", "print-last-result", 'l'); - args_parser.add_option(gc_on_every_allocation, "GC on every allocation", "gc-on-every-allocation", 'g'); - args_parser.add_option(disable_syntax_highlight, "Disable live syntax highlighting", "no-syntax-highlight", 's'); - args_parser.add_positional_argument(script_path, "Path to script file", "script", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - bool syntax_highlight = !disable_syntax_highlight; - - vm = JS::VM::create(); - OwnPtr interpreter; - - interrupt_interpreter = [&] { - auto error = JS::Error::create(interpreter->global_object(), "Error", "Received SIGINT"); - vm->throw_exception(interpreter->global_object(), error); - }; - - if (script_path == nullptr) { - s_print_last_result = true; - interpreter = JS::Interpreter::create(*vm); - ReplConsoleClient console_client(interpreter->global_object().console()); - interpreter->global_object().console().set_client(console_client); - interpreter->heap().set_should_collect_on_every_allocation(gc_on_every_allocation); - interpreter->vm().set_underscore_is_last_value(true); - - s_editor = Line::Editor::construct(); - s_editor->load_history(s_history_path); - - signal(SIGINT, [](int) { - if (!s_editor->is_editing()) - sigint_handler(); - s_editor->save_history(s_history_path); - }); - - s_editor->on_display_refresh = [syntax_highlight](Line::Editor& editor) { - auto stylize = [&](Line::Span span, Line::Style styles) { - if (syntax_highlight) - editor.stylize(span, styles); - }; - editor.strip_styles(); - - size_t open_indents = s_repl_line_level; - - auto line = editor.line(); - JS::Lexer lexer(line); - bool indenters_starting_line = true; - for (JS::Token token = lexer.next(); token.type() != JS::TokenType::Eof; token = lexer.next()) { - auto length = token.value().length(); - auto start = token.line_column() - 1; - auto end = start + length; - if (indenters_starting_line) { - if (token.type() != JS::TokenType::ParenClose && token.type() != JS::TokenType::BracketClose && token.type() != JS::TokenType::CurlyClose) { - indenters_starting_line = false; - } else { - --open_indents; - } - } - - switch (token.category()) { - case JS::TokenCategory::Invalid: - stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Red), Line::Style::Underline }); - break; - case JS::TokenCategory::Number: - stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Magenta) }); - break; - case JS::TokenCategory::String: - stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Green), Line::Style::Bold }); - break; - case JS::TokenCategory::Punctuation: - break; - case JS::TokenCategory::Operator: - break; - case JS::TokenCategory::Keyword: - switch (token.type()) { - case JS::TokenType::BoolLiteral: - case JS::TokenType::NullLiteral: - stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Yellow), Line::Style::Bold }); - break; - default: - stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Blue), Line::Style::Bold }); - break; - } - break; - case JS::TokenCategory::ControlKeyword: - stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::Cyan), Line::Style::Italic }); - break; - case JS::TokenCategory::Identifier: - stylize({ start, end }, { Line::Style::Foreground(Line::Style::XtermColor::White), Line::Style::Bold }); - default: - break; - } - } - - editor.set_prompt(prompt_for_level(open_indents)); - }; - - auto complete = [&interpreter](const Line::Editor& editor) -> Vector { - auto line = editor.line(editor.cursor()); - - JS::Lexer lexer { line }; - enum { - Initial, - CompleteVariable, - CompleteNullProperty, - CompleteProperty, - } mode { Initial }; - - StringView variable_name; - StringView property_name; - - // we're only going to complete either - // - - // where N is part of the name of a variable - // - .

- // where N is the complete name of a variable and - // P is part of the name of one of its properties - auto js_token = lexer.next(); - for (; js_token.type() != JS::TokenType::Eof; js_token = lexer.next()) { - switch (mode) { - case CompleteVariable: - switch (js_token.type()) { - case JS::TokenType::Period: - // ... - mode = CompleteNullProperty; - break; - default: - // not a dot, reset back to initial - mode = Initial; - break; - } - break; - case CompleteNullProperty: - if (js_token.is_identifier_name()) { - // ... - mode = CompleteProperty; - property_name = js_token.value(); - } else { - mode = Initial; - } - break; - case CompleteProperty: - // something came after the property access, reset to initial - case Initial: - if (js_token.is_identifier_name()) { - // ...... - mode = CompleteVariable; - variable_name = js_token.value(); - } else { - mode = Initial; - } - break; - } - } - - bool last_token_has_trivia = js_token.trivia().length() > 0; - - if (mode == CompleteNullProperty) { - mode = CompleteProperty; - property_name = ""; - last_token_has_trivia = false; // [tab] is sensible to complete. - } - - if (mode == Initial || last_token_has_trivia) - return {}; // we do not know how to complete this - - Vector results; - - Function list_all_properties = [&results, &list_all_properties](const JS::Shape& shape, auto& property_pattern) { - for (const auto& descriptor : shape.property_table()) { - if (!descriptor.key.is_string()) - continue; - auto key = descriptor.key.as_string(); - if (key.view().starts_with(property_pattern)) { - Line::CompletionSuggestion completion { key, Line::CompletionSuggestion::ForSearch }; - if (!results.contains_slow(completion)) { // hide duplicates - results.append(key); - } - } - } - if (const auto* prototype = shape.prototype()) { - list_all_properties(prototype->shape(), property_pattern); - } - }; - - switch (mode) { - case CompleteProperty: { - auto maybe_variable = vm->get_variable(variable_name, interpreter->global_object()); - if (maybe_variable.is_empty()) { - maybe_variable = interpreter->global_object().get(FlyString(variable_name)); - if (maybe_variable.is_empty()) - break; - } - - auto variable = maybe_variable; - if (!variable.is_object()) - break; - - const auto* object = variable.to_object(interpreter->global_object()); - const auto& shape = object->shape(); - list_all_properties(shape, property_name); - if (results.size()) - editor.suggest(property_name.length()); - break; - } - case CompleteVariable: { - const auto& variable = interpreter->global_object(); - list_all_properties(variable.shape(), variable_name); - if (results.size()) - editor.suggest(variable_name.length()); - break; - } - default: - ASSERT_NOT_REACHED(); - } - - return results; - }; - s_editor->on_tab_complete = move(complete); - repl(*interpreter); - s_editor->save_history(s_history_path); - } else { - interpreter = JS::Interpreter::create(*vm); - ReplConsoleClient console_client(interpreter->global_object().console()); - interpreter->global_object().console().set_client(console_client); - interpreter->heap().set_should_collect_on_every_allocation(gc_on_every_allocation); - - signal(SIGINT, [](int) { - sigint_handler(); - }); - - auto file = Core::File::construct(script_path); - if (!file->open(Core::IODevice::ReadOnly)) { - warnln("Failed to open {}: {}", script_path, file->error_string()); - return 1; - } - auto file_contents = file->read_all(); - - StringView source; - if (file_has_shebang(file_contents)) { - source = strip_shebang(file_contents); - } else { - source = file_contents; - } - - if (!parse_and_run(*interpreter, source)) - return 1; - } - - return 0; -} diff --git a/Userland/keymap.cpp b/Userland/keymap.cpp deleted file mode 100644 index c057187015..0000000000 --- a/Userland/keymap.cpp +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio setkeymap rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/res/keymaps", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil(nullptr, nullptr) < 0) { - perror("unveil"); - return 1; - } - - const char* path = nullptr; - Core::ArgsParser args_parser; - args_parser.add_positional_argument(path, "The mapping file to be used", "file"); - args_parser.parse(argc, argv); - - Keyboard::CharacterMap character_map(path); - int rc = character_map.set_system_map(); - if (rc != 0) - fprintf(stderr, "%s\n", strerror(-rc)); - - return rc; -} diff --git a/Userland/kill.cpp b/Userland/kill.cpp deleted file mode 100644 index e29a6fd54e..0000000000 --- a/Userland/kill.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -static void print_usage_and_exit() -{ - printf("usage: kill [-signal] \n"); - exit(1); -} - -int main(int argc, char** argv) -{ - if (pledge("stdio proc", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (argc == 2 && !strcmp(argv[1], "-l")) { - for (size_t i = 0; i < NSIG; ++i) { - if (i && !(i % 5)) - outln(""); - out("{:2}) {:10}", i, getsignalname(i)); - } - outln(""); - return 0; - } - - if (argc != 2 && argc != 3) - print_usage_and_exit(); - unsigned signum = SIGTERM; - int pid_argi = 1; - if (argc == 3) { - pid_argi = 2; - if (argv[1][0] != '-') - print_usage_and_exit(); - - Optional number; - - if (isalpha(argv[1][1])) { - int value = getsignalbyname(&argv[1][1]); - if (value >= 0 && value < NSIG) - number = value; - } - - if (!number.has_value()) - number = StringView(&argv[1][1]).to_uint(); - - if (!number.has_value()) { - printf("'%s' is not a valid signal name or number\n", &argv[1][1]); - return 2; - } - signum = number.value(); - } - auto pid_opt = String(argv[pid_argi]).to_int(); - if (!pid_opt.has_value()) { - printf("'%s' is not a valid PID\n", argv[pid_argi]); - return 3; - } - pid_t pid = pid_opt.value(); - - int rc = kill(pid, signum); - if (rc < 0) - perror("kill"); - return 0; -} diff --git a/Userland/killall.cpp b/Userland/killall.cpp deleted file mode 100644 index 1caae0a112..0000000000 --- a/Userland/killall.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -static void print_usage_and_exit() -{ - printf("usage: killall [-signal] process_name\n"); - exit(1); -} - -static int kill_all(const String& process_name, const unsigned signum) -{ - auto processes = Core::ProcessStatisticsReader().get_all(); - if (!processes.has_value()) - return 1; - - for (auto& it : processes.value()) { - if (it.value.name == process_name) { - int ret = kill(it.value.pid, signum); - if (ret < 0) - perror("kill"); - } - } - - return 0; -} - -int main(int argc, char** argv) -{ - unsigned signum = SIGTERM; - int name_argi = 1; - - if (argc != 2 && argc != 3) - print_usage_and_exit(); - - if (argc == 3) { - name_argi = 2; - - if (argv[1][0] != '-') - print_usage_and_exit(); - - Optional number; - - if (isalpha(argv[1][1])) { - int value = getsignalbyname(&argv[1][1]); - if (value >= 0 && value < NSIG) - number = value; - } - - if (!number.has_value()) - number = String(&argv[1][1]).to_uint(); - - if (!number.has_value()) { - printf("'%s' is not a valid signal name or number\n", &argv[1][1]); - return 2; - } - signum = number.value(); - } - - return kill_all(argv[name_argi], signum); -} diff --git a/Userland/ln.cpp b/Userland/ln.cpp deleted file mode 100644 index ac9077ec0b..0000000000 --- a/Userland/ln.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio cpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool symbolic = false; - const char* target = nullptr; - const char* path = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(symbolic, "Create a symlink", "symbolic", 's'); - args_parser.add_positional_argument(target, "Link target", "target"); - args_parser.add_positional_argument(path, "Link path", "path", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - String path_buffer; - if (!path) { - path_buffer = LexicalPath(target).basename(); - path = path_buffer.characters(); - } - - if (symbolic) { - int rc = symlink(target, path); - if (rc < 0) { - perror("symlink"); - return 1; - } - return 0; - } - - int rc = link(target, path); - if (rc < 0) { - perror("link"); - return 1; - } - return 0; -} diff --git a/Userland/ls.cpp b/Userland/ls.cpp deleted file mode 100644 index bdeba14361..0000000000 --- a/Userland/ls.cpp +++ /dev/null @@ -1,504 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int do_file_system_object_long(const char* path); -static int do_file_system_object_short(const char* path); - -static bool flag_classify = false; -static bool flag_colorize = false; -static bool flag_long = false; -static bool flag_show_dotfiles = false; -static bool flag_show_almost_all_dotfiles = false; -static bool flag_ignore_backups = false; -static bool flag_list_directories_only = false; -static bool flag_show_inode = false; -static bool flag_print_numeric = false; -static bool flag_hide_group = false; -static bool flag_human_readable = false; -static bool flag_sort_by_timestamp = false; -static bool flag_reverse_sort = false; -static bool flag_disable_hyperlinks = false; - -static size_t terminal_rows = 0; -static size_t terminal_columns = 0; -static bool output_is_terminal = false; - -static HashMap users; -static HashMap groups; - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath tty", nullptr) < 0) { - perror("pledge"); - return 1; - } - - struct winsize ws; - int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); - if (rc == 0) { - terminal_rows = ws.ws_row; - terminal_columns = ws.ws_col; - output_is_terminal = true; - } - if (!isatty(STDOUT_FILENO)) { - flag_disable_hyperlinks = true; - } else { - flag_colorize = true; - } - - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector paths; - - Core::ArgsParser args_parser; - args_parser.set_general_help("List files in a directory."); - args_parser.add_option(flag_show_dotfiles, "Show dotfiles", "all", 'a'); - args_parser.add_option(flag_show_almost_all_dotfiles, "Do not list implied . and .. directories", nullptr, 'A'); - args_parser.add_option(flag_ignore_backups, "Do not list implied entries ending with ~", "--ignore-backups", 'B'); - args_parser.add_option(flag_list_directories_only, "List directories themselves, not their contents", "directory", 'd'); - args_parser.add_option(flag_long, "Display long info", "long", 'l'); - args_parser.add_option(flag_sort_by_timestamp, "Sort files by timestamp", nullptr, 't'); - args_parser.add_option(flag_reverse_sort, "Reverse sort order", "reverse", 'r'); - args_parser.add_option(flag_classify, "Append a file type indicator to entries", "classify", 'F'); - args_parser.add_option(flag_colorize, "Use pretty colors", nullptr, 'G'); - args_parser.add_option(flag_show_inode, "Show inode ids", "inode", 'i'); - args_parser.add_option(flag_print_numeric, "In long format, display numeric UID/GID", "numeric-uid-gid", 'n'); - args_parser.add_option(flag_hide_group, "In long format, do not show group information", nullptr, 'o'); - args_parser.add_option(flag_human_readable, "Print human-readable sizes", "human-readable", 'h'); - args_parser.add_option(flag_disable_hyperlinks, "Disable hyperlinks", "no-hyperlinks", 'K'); - args_parser.add_positional_argument(paths, "Directory to list", "path", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (flag_show_almost_all_dotfiles) - flag_show_dotfiles = true; - - if (flag_long) { - setpwent(); - for (auto* pwd = getpwent(); pwd; pwd = getpwent()) - users.set(pwd->pw_uid, pwd->pw_name); - endpwent(); - setgrent(); - for (auto* grp = getgrent(); grp; grp = getgrent()) - groups.set(grp->gr_gid, grp->gr_name); - endgrent(); - } - - auto do_file_system_object = [&](const char* path) { - if (flag_long) - return do_file_system_object_long(path); - return do_file_system_object_short(path); - }; - - int status = 0; - if (paths.is_empty()) { - status = do_file_system_object("."); - } else if (paths.size() == 1) { - status = do_file_system_object(paths[0]); - } else { - for (auto& path : paths) { - status = do_file_system_object(path); - } - } - return status; -} - -static int print_escaped(const char* name) -{ - int printed = 0; - - Utf8View utf8_name(name); - if (utf8_name.validate()) { - printf("%s", name); - return utf8_name.length(); - } - - for (int i = 0; name[i] != '\0'; i++) { - if (isprint(name[i])) { - putchar(name[i]); - printed++; - } else { - printed += printf("\\%03d", name[i]); - } - } - - return printed; -} - -static String& hostname() -{ - static String s_hostname; - if (s_hostname.is_null()) { - char buffer[HOST_NAME_MAX]; - if (gethostname(buffer, sizeof(buffer)) == 0) - s_hostname = buffer; - else - s_hostname = "localhost"; - } - return s_hostname; -} - -static size_t print_name(const struct stat& st, const String& name, const char* path_for_link_resolution, const char* path_for_hyperlink) -{ - if (!flag_disable_hyperlinks) { - auto full_path = Core::File::real_path_for(path_for_hyperlink); - if (!full_path.is_null()) { - out("\033]8;;file://{}{}\033\\", hostname(), full_path); - } - } - - size_t nprinted = 0; - - if (!flag_colorize || !output_is_terminal) { - nprinted = printf("%s", name.characters()); - } else { - const char* begin_color = ""; - const char* end_color = "\033[0m"; - - if (st.st_mode & S_ISVTX) - begin_color = "\033[42;30;1m"; - else if (st.st_mode & S_ISUID) - begin_color = "\033[41;1m"; - else if (st.st_mode & S_ISGID) - begin_color = "\033[43;1m"; - else if (S_ISLNK(st.st_mode)) - begin_color = "\033[36;1m"; - else if (S_ISDIR(st.st_mode)) - begin_color = "\033[34;1m"; - else if (st.st_mode & 0111) - begin_color = "\033[32;1m"; - else if (S_ISSOCK(st.st_mode)) - begin_color = "\033[35;1m"; - else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) - begin_color = "\033[33;1m"; - printf("%s", begin_color); - nprinted = print_escaped(name.characters()); - printf("%s", end_color); - } - if (S_ISLNK(st.st_mode)) { - if (path_for_link_resolution) { - auto link_destination = Core::File::read_link(path_for_link_resolution); - if (link_destination.is_null()) { - perror("readlink"); - } else { - nprinted += printf(" -> ") + print_escaped(link_destination.characters()); - } - } else { - if (flag_classify) - nprinted += printf("@"); - } - } else if (S_ISDIR(st.st_mode)) { - if (flag_classify) - nprinted += printf("/"); - } else if (st.st_mode & 0111) { - if (flag_classify) - nprinted += printf("*"); - } - - if (!flag_disable_hyperlinks) { - printf("\033]8;;\033\\"); - } - - return nprinted; -} - -static bool print_filesystem_object(const String& path, const String& name, const struct stat& st) -{ - if (flag_show_inode) - printf("%08u ", st.st_ino); - - if (S_ISDIR(st.st_mode)) - printf("d"); - else if (S_ISLNK(st.st_mode)) - printf("l"); - else if (S_ISBLK(st.st_mode)) - printf("b"); - else if (S_ISCHR(st.st_mode)) - printf("c"); - else if (S_ISFIFO(st.st_mode)) - printf("f"); - else if (S_ISSOCK(st.st_mode)) - printf("s"); - else if (S_ISREG(st.st_mode)) - printf("-"); - else - printf("?"); - - printf("%c%c%c%c%c%c%c%c", - st.st_mode & S_IRUSR ? 'r' : '-', - st.st_mode & S_IWUSR ? 'w' : '-', - st.st_mode & S_ISUID ? 's' : (st.st_mode & S_IXUSR ? 'x' : '-'), - st.st_mode & S_IRGRP ? 'r' : '-', - st.st_mode & S_IWGRP ? 'w' : '-', - st.st_mode & S_ISGID ? 's' : (st.st_mode & S_IXGRP ? 'x' : '-'), - st.st_mode & S_IROTH ? 'r' : '-', - st.st_mode & S_IWOTH ? 'w' : '-'); - - if (st.st_mode & S_ISVTX) - printf("t"); - else - printf("%c", st.st_mode & S_IXOTH ? 'x' : '-'); - - printf(" %u", st.st_nlink); - - auto username = users.get(st.st_uid); - if (!flag_print_numeric && username.has_value()) { - printf(" %7s", username.value().characters()); - } else { - printf(" %7u", st.st_uid); - } - - if (!flag_hide_group) { - auto groupname = groups.get(st.st_gid); - if (!flag_print_numeric && groupname.has_value()) { - printf(" %7s", groupname.value().characters()); - } else { - printf(" %7u", st.st_gid); - } - } - - if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { - printf(" %4u,%4u ", major(st.st_rdev), minor(st.st_rdev)); - } else { - if (flag_human_readable) { - printf(" %10s ", human_readable_size((size_t)st.st_size).characters()); - } else { - printf(" %10zd ", st.st_size); - } - } - - printf(" %s ", Core::DateTime::from_timestamp(st.st_mtime).to_string().characters()); - - print_name(st, name, path.characters(), path.characters()); - - printf("\n"); - return true; -} - -static int do_file_system_object_long(const char* path) -{ - if (flag_list_directories_only) { - struct stat stat; - int rc = lstat(path, &stat); - if (rc < 0) { - perror("lstat"); - memset(&stat, 0, sizeof(stat)); - } - if (print_filesystem_object(path, path, stat)) - return 0; - return 2; - } - - auto flags = Core::DirIterator::SkipDots; - if (flag_show_dotfiles) - flags = Core::DirIterator::Flags::NoFlags; - if (flag_show_almost_all_dotfiles) - flags = Core::DirIterator::SkipParentAndBaseDir; - - Core::DirIterator di(path, flags); - - if (di.has_error()) { - if (di.error() == ENOTDIR) { - struct stat stat; - int rc = lstat(path, &stat); - if (rc < 0) { - perror("lstat"); - memset(&stat, 0, sizeof(stat)); - } - if (print_filesystem_object(path, path, stat)) - return 0; - return 2; - } - fprintf(stderr, "%s: %s\n", path, di.error_string()); - return 1; - } - - struct FileMetadata { - String name; - String path; - struct stat stat; - }; - - Vector files; - while (di.has_next()) { - FileMetadata metadata; - metadata.name = di.next_path(); - ASSERT(!metadata.name.is_empty()); - - if (metadata.name.ends_with('~') && flag_ignore_backups && metadata.name != path) - continue; - - StringBuilder builder; - builder.append(path); - builder.append('/'); - builder.append(metadata.name); - metadata.path = builder.to_string(); - ASSERT(!metadata.path.is_null()); - int rc = lstat(metadata.path.characters(), &metadata.stat); - if (rc < 0) { - perror("lstat"); - memset(&metadata.stat, 0, sizeof(metadata.stat)); - } - files.append(move(metadata)); - } - - quick_sort(files, [](auto& a, auto& b) { - if (flag_sort_by_timestamp) { - if (flag_reverse_sort) - return a.stat.st_mtime > b.stat.st_mtime; - return a.stat.st_mtime < b.stat.st_mtime; - } - // Fine, sort by name then! - if (flag_reverse_sort) - return a.name > b.name; - return a.name < b.name; - }); - - for (auto& file : files) { - if (!print_filesystem_object(file.path, file.name, file.stat)) - return 2; - } - return 0; -} - -static bool print_filesystem_object_short(const char* path, const char* name, size_t* nprinted) -{ - struct stat st; - int rc = lstat(path, &st); - if (rc == -1) { - printf("lstat(%s) failed: %s\n", path, strerror(errno)); - return false; - } - - if (flag_show_inode) - printf("%08u ", st.st_ino); - - *nprinted = print_name(st, name, nullptr, path); - return true; -} - -int do_file_system_object_short(const char* path) -{ - if (flag_list_directories_only) { - size_t nprinted = 0; - bool status = print_filesystem_object_short(path, path, &nprinted); - printf("\n"); - if (status) - return 0; - return 2; - } - - auto flags = Core::DirIterator::SkipDots; - if (flag_show_dotfiles) - flags = Core::DirIterator::Flags::NoFlags; - if (flag_show_almost_all_dotfiles) - flags = Core::DirIterator::SkipParentAndBaseDir; - - Core::DirIterator di(path, flags); - if (di.has_error()) { - if (di.error() == ENOTDIR) { - size_t nprinted = 0; - bool status = print_filesystem_object_short(path, path, &nprinted); - printf("\n"); - if (status) - return 0; - return 2; - } - fprintf(stderr, "%s: %s\n", path, di.error_string()); - return 1; - } - - Vector names; - size_t longest_name = 0; - while (di.has_next()) { - String name = di.next_path(); - - if (name.ends_with('~') && flag_ignore_backups && name != path) - continue; - - names.append(name); - if (names.last().length() > longest_name) - longest_name = name.length(); - } - quick_sort(names); - - size_t printed_on_row = 0; - size_t nprinted = 0; - for (size_t i = 0; i < names.size(); ++i) { - auto& name = names[i]; - StringBuilder builder; - builder.append(path); - builder.append('/'); - builder.append(name); - if (!print_filesystem_object_short(builder.to_string().characters(), name.characters(), &nprinted)) - return 2; - int offset = 0; - if (terminal_columns > longest_name) - offset = terminal_columns % longest_name / (terminal_columns / longest_name); - - // The offset must be at least 2 because: - // - With each file an additional char is printed e.g. '@','*'. - // - Each filename must be separated by a space. - size_t column_width = longest_name + max(offset, 2); - printed_on_row += column_width; - - for (size_t j = nprinted; i != (names.size() - 1) && j < column_width; ++j) - printf(" "); - if ((printed_on_row + column_width) >= terminal_columns) { - printf("\n"); - printed_on_row = 0; - } - } - if (printed_on_row) - printf("\n"); - return 0; -} diff --git a/Userland/lsirq.cpp b/Userland/lsirq.cpp deleted file mode 100644 index 8a24308c20..0000000000 --- a/Userland/lsirq.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2020, Liav A. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/proc/interrupts", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - auto proc_interrupts = Core::File::construct("/proc/interrupts"); - if (!proc_interrupts->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Error: %s\n", proc_interrupts->error_string()); - return 1; - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - printf("%4s %-10s\n", " ", "CPU0"); - auto file_contents = proc_interrupts->read_all(); - auto json = JsonValue::from_string(file_contents); - ASSERT(json.has_value()); - json.value().as_array().for_each([](auto& value) { - auto handler = value.as_object(); - auto purpose = handler.get("purpose").to_string(); - auto interrupt = handler.get("interrupt_line").to_string(); - auto controller = handler.get("controller").to_string(); - auto call_count = handler.get("call_count").to_string(); - - printf("%4s: %-10s %-10s %-30s\n", - interrupt.characters(), call_count.characters(), controller.characters(), purpose.characters()); - }); - - return 0; -} diff --git a/Userland/lsof.cpp b/Userland/lsof.cpp deleted file mode 100644 index 029565cedc..0000000000 --- a/Userland/lsof.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2020, Maciej Zygmanowski - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct OpenFile { - int fd; - int pid; - String type; - String name; - String state; - String full_name; -}; - -static bool parse_name(StringView name, OpenFile& file) -{ - GenericLexer lexer(name); - auto component1 = lexer.consume_until(':'); - - if (lexer.tell_remaining() == 0) { - file.name = component1; - return true; - } else { - file.type = component1; - auto component2 = lexer.consume_while([](char c) { return isprint(c) && !isspace(c) && c != '('; }); - lexer.ignore_while(isspace); - file.name = component2; - - if (lexer.tell_remaining() == 0) { - return true; - } else { - if (!lexer.consume_specific('(')) { - dbgln("parse_name: expected ("); - return false; - } - - auto component3 = lexer.consume_until(')'); - if (lexer.tell_remaining() != 0) { - dbgln("parse_name: expected EOF"); - return false; - } - - file.state = component3; - return true; - } - } -} - -static Vector get_open_files_by_pid(pid_t pid) -{ - auto file = Core::File::open(String::format("/proc/%d/fds", pid), Core::IODevice::OpenMode::ReadOnly); - if (file.is_error()) { - printf("lsof: PID %d: %s\n", pid, file.error().characters()); - return Vector(); - } - auto data = file.value()->read_all(); - - JsonParser parser(data); - auto result = parser.parse(); - - if (!result.has_value()) { - ASSERT_NOT_REACHED(); - } - - Vector files; - result.value().as_array().for_each([pid, &files](const JsonValue& object) { - OpenFile open_file; - open_file.pid = pid; - open_file.fd = object.as_object().get("fd").to_int(); - - String name = object.as_object().get("absolute_path").to_string(); - ASSERT(parse_name(name, open_file)); - open_file.full_name = name; - - files.append(open_file); - }); - return files; -} - -static void display_entry(const OpenFile& file, const Core::ProcessStatistics& statistics) -{ - printf("%-28s %4d %4d %-10s %4d %s\n", statistics.name.characters(), file.pid, statistics.pgid, statistics.username.characters(), file.fd, file.full_name.characters()); -} - -int main(int argc, char* argv[]) -{ - if (pledge("stdio rpath proc", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/proc", "r") < 0) { - perror("unveil /proc"); - return 1; - } - - // needed by ProcessStatisticsReader::get_all() - if (unveil("/etc/passwd", "r") < 0) { - perror("unveil /etc/passwd"); - return 1; - } - - unveil(nullptr, nullptr); - - bool arg_all_processes { false }; - int arg_fd { -1 }; - const char* arg_uid { nullptr }; - int arg_uid_int = -1; - int arg_pgid { -1 }; - pid_t arg_pid { -1 }; - const char* arg_file_name { nullptr }; - - if (argc == 1) - arg_all_processes = true; - else { - Core::ArgsParser parser; - parser.set_general_help("List open files of a processes. This can mean actual files in the file system, sockets, pipes, etc."); - parser.add_option(arg_pid, "Select by PID", nullptr, 'p', "pid"); - parser.add_option(arg_fd, "Select by file descriptor", nullptr, 'd', "fd"); - parser.add_option(arg_uid, "Select by login/UID", nullptr, 'u', "login/UID"); - parser.add_option(arg_pgid, "Select by process group ID", nullptr, 'g', "PGID"); - parser.add_positional_argument(arg_file_name, "File name", "file name", Core::ArgsParser::Required::No); - parser.parse(argc, argv); - } - { - // try convert UID to int - auto arg = String(arg_uid).to_int(); - if (arg.has_value()) - arg_uid_int = arg.value(); - } - - printf("%-28s %4s %4s %-10s %4s %s\n", "COMMAND", "PID", "PGID", "USER", "FD", "NAME"); - auto processes = Core::ProcessStatisticsReader::get_all(); - if (!processes.has_value()) - return 1; - if (arg_pid == -1) { - for (auto process : processes.value()) { - if (process.key == 0) - continue; - auto open_files = get_open_files_by_pid(process.key); - - if (open_files.is_empty()) - continue; - - for (auto file : open_files) { - if ((arg_all_processes) - || (arg_fd != -1 && file.fd == arg_fd) - || (arg_uid_int != -1 && (int)process.value.uid == arg_uid_int) - || (arg_uid != nullptr && process.value.username == arg_uid) - || (arg_pgid != -1 && (int)process.value.pgid == arg_pgid) - || (arg_file_name != nullptr && file.name == arg_file_name)) - display_entry(file, process.value); - } - } - } else { - auto open_files = get_open_files_by_pid(arg_pid); - - if (open_files.is_empty()) - return 0; - - for (auto file : open_files) { - display_entry(file, processes.value().get(arg_pid).value()); - } - } - - return 0; -} diff --git a/Userland/lspci.cpp b/Userland/lspci.cpp deleted file mode 100644 index 4f42e26018..0000000000 --- a/Userland/lspci.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/res/pci.ids", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/proc/pci", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - auto db = PCIDB::Database::open(); - if (!db) - warnln("Couldn't open PCI ID database"); - - auto proc_pci = Core::File::construct("/proc/pci"); - if (!proc_pci->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Error: %s\n", proc_pci->error_string()); - return 1; - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - auto file_contents = proc_pci->read_all(); - auto json = JsonValue::from_string(file_contents); - ASSERT(json.has_value()); - json.value().as_array().for_each([db](auto& value) { - auto dev = value.as_object(); - auto seg = dev.get("seg").to_u32(); - auto bus = dev.get("bus").to_u32(); - auto slot = dev.get("slot").to_u32(); - auto function = dev.get("function").to_u32(); - auto vendor_id = dev.get("vendor_id").to_u32(); - auto device_id = dev.get("device_id").to_u32(); - auto revision_id = dev.get("revision_id").to_u32(); - auto class_id = dev.get("class").to_u32(); - - String vendor_name; - String device_name; - String class_name; - - if (db) { - vendor_name = db->get_vendor(vendor_id); - device_name = db->get_device(vendor_id, device_id); - class_name = db->get_class(class_id); - } - - if (vendor_name.is_empty()) - vendor_name = String::format("%02x", vendor_id); - if (device_name.is_empty()) - device_name = String::format("%02x", device_id); - if (class_name.is_empty()) - class_name = String::format("%04x", class_id); - - outln("{:04x}:{:02x}:{:02x}.{} {}: {} {} (rev {:02x})", seg, bus, slot, function, class_name, vendor_name, device_name, revision_id); - }); - - return 0; -} diff --git a/Userland/man.cpp b/Userland/man.cpp deleted file mode 100644 index 079660494c..0000000000 --- a/Userland/man.cpp +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char* argv[]) -{ - int view_width = 0; - if (isatty(STDOUT_FILENO)) { - struct winsize ws; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) - view_width = ws.ws_col; - } - - if (view_width == 0) - view_width = 80; - - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/usr/share/man", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - const char* section = nullptr; - const char* name = nullptr; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Read manual pages. Try 'man man' to get started."); - args_parser.add_positional_argument(section, "Section of the man page", "section", Core::ArgsParser::Required::No); - args_parser.add_positional_argument(name, "Name of the man page", "name"); - - args_parser.parse(argc, argv); - - auto make_path = [name](const char* section) { - return String::format("/usr/share/man/man%s/%s.md", section, name); - }; - if (!section) { - const char* sections[] = { - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8" - }; - for (auto s : sections) { - String path = make_path(s); - if (access(path.characters(), R_OK) == 0) { - section = s; - break; - } - } - if (!section) { - fprintf(stderr, "No man page for %s\n", name); - exit(1); - } - } - - auto file = Core::File::construct(); - file->set_filename(make_path(section)); - - if (!file->open(Core::IODevice::OpenMode::ReadOnly)) { - perror("Failed to open man page file"); - exit(1); - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - dbgln("Loading man page from {}", file->filename()); - auto buffer = file->read_all(); - auto source = String::copy(buffer); - - printf("%s(%s)\t\tSerenityOS manual\n", name, section); - - auto document = Markdown::Document::parse(source); - ASSERT(document); - - String rendered = document->render_for_terminal(view_width); - printf("%s", rendered.characters()); -} diff --git a/Userland/md.cpp b/Userland/md.cpp deleted file mode 100644 index 04e77d28cd..0000000000 --- a/Userland/md.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char* argv[]) -{ - if (pledge("stdio rpath tty", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* file_name = nullptr; - bool html = false; - int view_width = 0; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Render Markdown to some other format."); - args_parser.add_option(html, "Render to HTML rather than for the terminal", "html", 'H'); - args_parser.add_option(view_width, "Viewport width for the terminal (defaults to current terminal width)", "view-width", 0, "width"); - args_parser.add_positional_argument(file_name, "Path to Markdown file", "path", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (!html && view_width == 0) { - if (isatty(STDOUT_FILENO)) { - struct winsize ws; - if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) < 0) - view_width = 80; - else - view_width = ws.ws_col; - - } else { - view_width = 80; - } - } - auto file = Core::File::construct(); - bool success; - if (file_name == nullptr) { - success = file->open(STDIN_FILENO, Core::IODevice::OpenMode::ReadOnly, Core::File::ShouldCloseFileDescriptor::No); - } else { - file->set_filename(file_name); - success = file->open(Core::IODevice::OpenMode::ReadOnly); - } - if (!success) { - fprintf(stderr, "Error: %s\n", file->error_string()); - return 1; - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - auto buffer = file->read_all(); - dbgln("Read size {}", buffer.size()); - - auto input = String::copy(buffer); - auto document = Markdown::Document::parse(input); - - if (!document) { - fprintf(stderr, "Error parsing\n"); - return 1; - } - - String res = html ? document->render_to_html() : document->render_for_terminal(view_width); - printf("%s", res.characters()); -} diff --git a/Userland/misbehaving-application.cpp b/Userland/misbehaving-application.cpp deleted file mode 100644 index cee1ccab1c..0000000000 --- a/Userland/misbehaving-application.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int, char**) -{ - Core::EventLoop event_loop; - - auto timer = Core::Timer::construct(10, [&] { - dbgln("Now hanging!"); - while (true) { - sleep(1); - } - }); - - return event_loop.exec(); -} diff --git a/Userland/mkdir.cpp b/Userland/mkdir.cpp deleted file mode 100644 index 04badc9836..0000000000 --- a/Userland/mkdir.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2020, Linus Groh - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio cpath rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool create_parents = false; - Vector directories; - - Core::ArgsParser args_parser; - args_parser.add_option(create_parents, "Create parent directories if they don't exist", "parents", 'p'); - args_parser.add_positional_argument(directories, "Directories to create", "directories"); - args_parser.parse(argc, argv); - - // FIXME: Support -m/--mode option - mode_t mode = 0755; - - bool has_errors = false; - - for (auto& directory : directories) { - LexicalPath lexical_path(directory); - if (!create_parents) { - if (mkdir(lexical_path.string().characters(), mode) < 0) { - perror("mkdir"); - has_errors = true; - } - continue; - } - StringBuilder path_builder; - if (lexical_path.is_absolute()) - path_builder.append("/"); - for (auto& part : lexical_path.parts()) { - path_builder.append(part); - auto path = path_builder.build(); - struct stat st; - if (stat(path.characters(), &st) < 0) { - if (errno != ENOENT) { - perror("stat"); - has_errors = true; - break; - } - if (mkdir(path.characters(), mode) < 0) { - perror("mkdir"); - has_errors = true; - break; - } - } else { - if (!S_ISDIR(st.st_mode)) { - fprintf(stderr, "mkdir: cannot create directory '%s': not a directory\n", path.characters()); - has_errors = true; - break; - } - } - path_builder.append("/"); - } - } - return has_errors ? 1 : 0; -} diff --git a/Userland/mkfifo.cpp b/Userland/mkfifo.cpp deleted file mode 100644 index ba06d1b06b..0000000000 --- a/Userland/mkfifo.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2020, Peter Elliott - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio dpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - mode_t mode = 0666; - Vector paths; - - Core::ArgsParser args_parser; - // FIXME: add -m for file modes - args_parser.add_positional_argument(paths, "Paths of FIFOs to create", "paths"); - args_parser.parse(argc, argv); - - int exit_code = 0; - - for (auto path : paths) { - if (mkfifo(path, mode) < 0) { - perror("mkfifo"); - exit_code = 1; - } - } - - return exit_code; -} diff --git a/Userland/mknod.cpp b/Userland/mknod.cpp deleted file mode 100644 index 49ef4cdacc..0000000000 --- a/Userland/mknod.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -constexpr unsigned encoded_device(unsigned major, unsigned minor) -{ - return (minor & 0xff) | (major << 8) | ((minor & ~0xff) << 12); -} - -static int usage() -{ - printf("usage: mknod [ ]\n"); - return 0; -} - -int main(int argc, char** argv) -{ - if (pledge("stdio dpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - // FIXME: Add some kind of option for specifying the file permissions. - if (argc < 3) - return usage(); - - if (argv[2][0] == 'p') { - if (argc != 3) - return usage(); - } else if (argc != 5) { - return usage(); - } - - const char* name = argv[1]; - mode_t mode = 0666; - switch (argv[2][0]) { - case 'c': - case 'u': - mode |= S_IFCHR; - break; - case 'b': - mode |= S_IFBLK; - break; - case 'p': - mode |= S_IFIFO; - break; - default: - return usage(); - } - - int major = 0; - int minor = 0; - if (argc == 5) { - major = atoi(argv[3]); - minor = atoi(argv[4]); - } - - int rc = mknod(name, mode, encoded_device(major, minor)); - if (rc < 0) { - perror("mknod"); - return 1; - } - return 0; -} diff --git a/Userland/modload.cpp b/Userland/modload.cpp deleted file mode 100644 index da86c93af4..0000000000 --- a/Userland/modload.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - const char* path = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(path, "Path to the module to load", "path"); - args_parser.parse(argc, argv); - - int rc = module_load(path, strlen(path)); - if (rc < 0) { - perror("module_load"); - return 1; - } - return 0; -} diff --git a/Userland/modunload.cpp b/Userland/modunload.cpp deleted file mode 100644 index b6e089bdd3..0000000000 --- a/Userland/modunload.cpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - const char* name = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(name, "Name of the module to unload", "name"); - args_parser.parse(argc, argv); - - int rc = module_unload(name, strlen(name)); - if (rc < 0) { - perror("module_unload"); - return 1; - } - return 0; -} diff --git a/Userland/more.cpp b/Userland/more.cpp deleted file mode 100644 index 94b34e44cb..0000000000 --- a/Userland/more.cpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -static int key_fd; - -static void wait_for_key() -{ - printf("\033[7m--[ more ]--\033[0m"); - fflush(stdout); - char dummy; - [[maybe_unused]] auto rc = read(key_fd, &dummy, 1); - printf("\n"); -} - -int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) -{ - if (pledge("stdio rpath tty", nullptr) < 0) { - perror("pledge"); - return 1; - } - - key_fd = STDOUT_FILENO; - - struct winsize ws; - ioctl(1, TIOCGWINSZ, &ws); - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - unsigned lines_printed = 0; - while (!feof(stdin)) { - char buffer[BUFSIZ]; - auto* str = fgets(buffer, sizeof(buffer), stdin); - if (!str) - break; - printf("%s", str); - ++lines_printed; - if ((lines_printed % (ws.ws_row - 1)) == 0) { - wait_for_key(); - } - } - - close(key_fd); - return 0; -} diff --git a/Userland/mount.cpp b/Userland/mount.cpp deleted file mode 100644 index 9e313d7a19..0000000000 --- a/Userland/mount.cpp +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int parse_options(const StringView& options) -{ - int flags = 0; - Vector parts = options.split_view(','); - for (auto& part : parts) { - if (part == "defaults") - continue; - else if (part == "nodev") - flags |= MS_NODEV; - else if (part == "noexec") - flags |= MS_NOEXEC; - else if (part == "nosuid") - flags |= MS_NOSUID; - else if (part == "bind") - flags |= MS_BIND; - else if (part == "ro") - flags |= MS_RDONLY; - else if (part == "remount") - flags |= MS_REMOUNT; - else - fprintf(stderr, "Ignoring invalid option: %s\n", part.to_string().characters()); - } - return flags; -} - -static bool is_source_none(const char* source) -{ - return !strcmp("none", source); -} - -static int get_source_fd(const char* source) -{ - if (is_source_none(source)) - return -1; - int fd = open(source, O_RDWR); - if (fd < 0) - fd = open(source, O_RDONLY); - if (fd < 0) { - int saved_errno = errno; - auto message = String::format("Failed to open: %s\n", source); - errno = saved_errno; - perror(message.characters()); - } - return fd; -} - -static bool mount_all() -{ - // Mount all filesystems listed in /etc/fstab. - dbgln("Mounting all filesystems..."); - - auto fstab = Core::File::construct("/etc/fstab"); - if (!fstab->open(Core::IODevice::OpenMode::ReadOnly)) { - fprintf(stderr, "Failed to open /etc/fstab: %s\n", fstab->error_string()); - return false; - } - - bool all_ok = true; - while (fstab->can_read_line()) { - auto line = fstab->read_line(); - - // Skip comments and blank lines. - if (line.is_empty() || line.starts_with("#")) - continue; - - Vector parts = line.split('\t'); - if (parts.size() < 3) { - fprintf(stderr, "Invalid fstab entry: %s\n", line.characters()); - all_ok = false; - continue; - } - - const char* mountpoint = parts[1].characters(); - const char* fstype = parts[2].characters(); - int flags = parts.size() >= 4 ? parse_options(parts[3]) : 0; - - if (strcmp(mountpoint, "/") == 0) { - dbgln("Skipping mounting root"); - continue; - } - - const char* filename = parts[0].characters(); - - int fd = get_source_fd(filename); - - dbg() << "Mounting " << filename << "(" << fstype << ")" - << " on " << mountpoint; - int rc = mount(fd, mountpoint, fstype, flags); - if (rc != 0) { - fprintf(stderr, "Failed to mount %s (FD: %d) (%s) on %s: %s\n", filename, fd, fstype, mountpoint, strerror(errno)); - all_ok = false; - continue; - } - } - - return all_ok; -} - -static bool print_mounts() -{ - // Output info about currently mounted filesystems. - auto df = Core::File::construct("/proc/df"); - if (!df->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Failed to open /proc/df: %s\n", df->error_string()); - return false; - } - - auto content = df->read_all(); - auto json = JsonValue::from_string(content); - ASSERT(json.has_value()); - - json.value().as_array().for_each([](auto& value) { - auto fs_object = value.as_object(); - auto class_name = fs_object.get("class_name").to_string(); - auto mount_point = fs_object.get("mount_point").to_string(); - auto source = fs_object.get("source").as_string_or("none"); - auto readonly = fs_object.get("readonly").to_bool(); - auto mount_flags = fs_object.get("mount_flags").to_int(); - - printf("%s on %s type %s (", source.characters(), mount_point.characters(), class_name.characters()); - - if (readonly || mount_flags & MS_RDONLY) - printf("ro"); - else - printf("rw"); - - if (mount_flags & MS_NODEV) - printf(",nodev"); - if (mount_flags & MS_NOEXEC) - printf(",noexec"); - if (mount_flags & MS_NOSUID) - printf(",nosuid"); - if (mount_flags & MS_BIND) - printf(",bind"); - - printf(")\n"); - }); - - return true; -} - -int main(int argc, char** argv) -{ - const char* source = nullptr; - const char* mountpoint = nullptr; - const char* fs_type = nullptr; - const char* options = nullptr; - bool should_mount_all = false; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(source, "Source path", "source", Core::ArgsParser::Required::No); - args_parser.add_positional_argument(mountpoint, "Mount point", "mountpoint", Core::ArgsParser::Required::No); - args_parser.add_option(fs_type, "File system type", nullptr, 't', "fstype"); - args_parser.add_option(options, "Mount options", nullptr, 'o', "options"); - args_parser.add_option(should_mount_all, "Mount all file systems listed in /etc/fstab", nullptr, 'a'); - args_parser.parse(argc, argv); - - if (should_mount_all) { - return mount_all() ? 0 : 1; - } - - if (!source && !mountpoint) - return print_mounts() ? 0 : 1; - - if (source && mountpoint) { - if (!fs_type) - fs_type = "ext2"; - int flags = options ? parse_options(options) : 0; - - int fd = get_source_fd(source); - - if (mount(fd, mountpoint, fs_type, flags) < 0) { - perror("mount"); - return 1; - } - return 0; - } - - args_parser.print_usage(stderr, argv[0]); - return 1; -} diff --git a/Userland/mv.cpp b/Userland/mv.cpp deleted file mode 100644 index de07baf09b..0000000000 --- a/Userland/mv.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath wpath cpath fattr", nullptr) < 0) { - perror("pledge"); - return 1; - } - - // NOTE: The "force" option is a dummy for now, it's just here to silence scripts that use "mv -f" - // In the future, it might be used to cancel out an "-i" interactive option. - bool force = false; - bool verbose = false; - - Vector paths; - - Core::ArgsParser args_parser; - args_parser.add_option(force, "Force", "force", 'f'); - args_parser.add_option(verbose, "Verbose", "verbose", 'v'); - args_parser.add_positional_argument(paths, "Paths to files being moved followed by target location", "paths"); - args_parser.parse(argc, argv); - - if (paths.size() < 2) { - args_parser.print_usage(stderr, argv[0]); - return 1; - } - - auto original_new_path = paths.take_last(); - - struct stat st; - - int rc = lstat(original_new_path, &st); - if (rc != 0 && errno != ENOENT) { - perror("lstat"); - return 1; - } - - if (paths.size() > 1 && !S_ISDIR(st.st_mode)) { - warnln("Target is not a directory: {}", original_new_path); - return 1; - } - - for (auto& old_path : paths) { - String combined_new_path; - const char* new_path = original_new_path; - if (rc == 0 && S_ISDIR(st.st_mode)) { - auto old_basename = LexicalPath(old_path).basename(); - combined_new_path = String::format("%s/%s", original_new_path, old_basename.characters()); - new_path = combined_new_path.characters(); - } - - rc = rename(old_path, new_path); - if (rc < 0) { - perror("rename"); - return 1; - } - - if (verbose) - printf("renamed '%s' -> '%s'\n", old_path, new_path); - } - - return 0; -} diff --git a/Userland/nc.cpp b/Userland/nc.cpp deleted file mode 100644 index 8d4f0a7613..0000000000 --- a/Userland/nc.cpp +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - bool should_listen = false; - bool verbose = false; - bool should_close = false; - const char* addr = nullptr; - int port = 0; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Network cat: Connect to network sockets as if it were a file."); - args_parser.add_option(should_listen, "Listen instead of connecting", "listen", 'l'); - args_parser.add_option(verbose, "Log everything that's happening", "verbose", 'v'); - args_parser.add_option(should_close, "Close connection after reading stdin to the end", nullptr, 'N'); - args_parser.add_positional_argument(addr, "Address to connect to or listen on", "address"); - args_parser.add_positional_argument(port, "Port to connect to or listen on", "port"); - args_parser.parse(argc, argv); - - int fd; - - if (should_listen) { - int listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (listen_fd < 0) { - perror("socket"); - return 1; - } - - struct sockaddr_in sa; - memset(&sa, 0, sizeof sa); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - sa.sin_addr.s_addr = htonl(INADDR_ANY); - if (addr) { - if (inet_pton(AF_INET, addr, &sa.sin_addr) < 0) { - perror("inet_pton"); - return 1; - } - } - - if (bind(listen_fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { - perror("bind"); - return 1; - } - - if (listen(listen_fd, 1) == -1) { - perror("listen"); - return 1; - } - - char addr_str[100]; - - struct sockaddr_in sin; - socklen_t len; - - len = sizeof(sin); - if (getsockname(listen_fd, (struct sockaddr*)&sin, &len) == -1) { - perror("getsockname"); - return 1; - } - if (verbose) - fprintf(stderr, "waiting for a connection on %s:%d\n", inet_ntop(sin.sin_family, &sin.sin_addr, addr_str, sizeof(addr_str) - 1), ntohs(sin.sin_port)); - - len = sizeof(sin); - fd = accept(listen_fd, (struct sockaddr*)&sin, &len); - if (fd == -1) { - perror("accept"); - return 1; - } - - if (verbose) - fprintf(stderr, "got connection from %s:%d\n", inet_ntop(sin.sin_family, &sin.sin_addr, addr_str, sizeof(addr_str) - 1), ntohs(sin.sin_port)); - - if (close(listen_fd) == -1) { - perror("close"); - return 1; - }; - } else { - fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) { - perror("socket"); - return 1; - } - - struct timeval timeout { - 3, 0 - }; - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { - perror("setsockopt"); - return 1; - } - if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { - perror("setsockopt"); - return 1; - } - - char addr_str[100]; - - struct sockaddr_in dst_addr; - memset(&dst_addr, 0, sizeof(dst_addr)); - - dst_addr.sin_family = AF_INET; - dst_addr.sin_port = htons(port); - if (inet_pton(AF_INET, addr, &dst_addr.sin_addr) < 0) { - perror("inet_pton"); - return 1; - } - - if (verbose) - fprintf(stderr, "connecting to %s:%d\n", inet_ntop(dst_addr.sin_family, &dst_addr.sin_addr, addr_str, sizeof(addr_str) - 1), ntohs(dst_addr.sin_port)); - if (connect(fd, (struct sockaddr*)&dst_addr, sizeof(dst_addr)) < 0) { - perror("connect"); - return 1; - } - if (verbose) - fprintf(stderr, "connected!\n"); - } - - bool stdin_closed = false; - bool fd_closed = false; - - fd_set readfds, writefds, exceptfds; - - while (!stdin_closed || !fd_closed) { - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&exceptfds); - - int highest_fd = 0; - - if (!stdin_closed) { - FD_SET(STDIN_FILENO, &readfds); - FD_SET(STDIN_FILENO, &exceptfds); - highest_fd = max(highest_fd, STDIN_FILENO); - } - if (!fd_closed) { - FD_SET(fd, &readfds); - FD_SET(fd, &exceptfds); - highest_fd = max(highest_fd, fd); - } - - int ready = select(highest_fd + 1, &readfds, &writefds, &exceptfds, nullptr); - if (ready == -1) { - if (errno == EINTR) - continue; - - perror("select"); - return 1; - } - - if (!stdin_closed && FD_ISSET(STDIN_FILENO, &readfds)) { - char buf[1024]; - int nread = read(STDIN_FILENO, buf, sizeof(buf)); - if (nread < 0) { - perror("read(STDIN_FILENO)"); - return 1; - } - - // stdin closed - if (nread == 0) { - stdin_closed = true; - if (verbose) - fprintf(stderr, "stdin closed\n"); - if (should_close) { - close(fd); - fd_closed = true; - } - } else if (write(fd, buf, nread) < 0) { - perror("write(fd)"); - return 1; - } - } - - if (!fd_closed && FD_ISSET(fd, &readfds)) { - char buf[1024]; - int nread = read(fd, buf, sizeof(buf)); - if (nread < 0) { - perror("read(fd)"); - return 1; - } - - // remote end closed - if (nread == 0) { - close(STDIN_FILENO); - stdin_closed = true; - fd_closed = true; - if (verbose) - fprintf(stderr, "remote closed\n"); - } else if (write(STDOUT_FILENO, buf, nread) < 0) { - perror("write(STDOUT_FILENO)"); - return 1; - } - } - } - - return 0; -} diff --git a/Userland/nl.cpp b/Userland/nl.cpp deleted file mode 100644 index ad6e965ae3..0000000000 --- a/Userland/nl.cpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -#include -#include - -enum NumberStyle { - NumberAllLines, - NumberNonEmptyLines, - NumberNoLines, -}; - -int main(int argc, char** argv) -{ - NumberStyle number_style = NumberNonEmptyLines; - int increment = 1; - const char* separator = " "; - int start_number = 1; - int number_width = 6; - Vector files; - - Core::ArgsParser args_parser; - - Core::ArgsParser::Option number_style_option { - true, - "Line numbering style: 't' for non-empty lines, 'a' for all lines, 'n' for no lines", - "body-numbering", - 'b', - "style", - [&number_style](const char* s) { - if (!strcmp(s, "t")) - number_style = NumberNonEmptyLines; - else if (!strcmp(s, "a")) - number_style = NumberAllLines; - else if (!strcmp(s, "n")) - number_style = NumberNoLines; - else - return false; - - return true; - } - }; - - args_parser.add_option(move(number_style_option)); - args_parser.add_option(increment, "Line count increment", "increment", 'i', "number"); - args_parser.add_option(separator, "Separator between line numbers and lines", "separator", 's', "string"); - args_parser.add_option(start_number, "Initial line number", "startnum", 'v', "number"); - args_parser.add_option(number_width, "Number width", "width", 'w', "number"); - args_parser.add_positional_argument(files, "Files to process", "file", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - Vector file_pointers; - if (!files.is_empty()) { - for (auto& file : files) { - FILE* file_pointer = fopen(file, "r"); - if (!file_pointer) { - fprintf(stderr, "unable to open %s\n", file); - continue; - } - file_pointers.append(file_pointer); - } - } else { - file_pointers.append(stdin); - } - - for (auto& file_pointer : file_pointers) { - int line_number = start_number - increment; // so the line number can start at 1 when added below - int previous_character = 0; - int next_character = 0; - while ((next_character = fgetc(file_pointer)) != EOF) { - if (previous_character == 0 || previous_character == '\n') { - if (next_character == '\n' && number_style != NumberAllLines) { - // Skip printing line count on empty lines. - printf("\n"); - continue; - } - if (number_style != NumberNoLines) - printf("%*d%s", number_width, (line_number += increment), separator); - else - printf("%*s", number_width, ""); - } - putchar(next_character); - previous_character = next_character; - } - fclose(file_pointer); - if (previous_character != '\n') - printf("\n"); // for cases where files have no trailing newline - } - return 0; -} diff --git a/Userland/notify.cpp b/Userland/notify.cpp deleted file mode 100644 index cb596e98e5..0000000000 --- a/Userland/notify.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - auto app = GUI::Application::construct(argc, argv); - - Core::ArgsParser args_parser; - const char* title = nullptr; - const char* message = nullptr; - const char* icon_path = nullptr; - args_parser.add_positional_argument(title, "Title of the notification", "title"); - args_parser.add_positional_argument(message, "Message to display in the notification", "message"); - args_parser.add_positional_argument(icon_path, "Path of icon to display in the notification", "icon-path", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - auto notification = GUI::Notification::construct(); - notification->set_text(message); - notification->set_title(title); - notification->set_icon(Gfx::Bitmap::load_from_file(icon_path)); - notification->show(); - - return 0; -} diff --git a/Userland/ntpquery.cpp b/Userland/ntpquery.cpp deleted file mode 100644 index 017a6c0235..0000000000 --- a/Userland/ntpquery.cpp +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (c) 2020, Nico Weber - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#define _BSD_SOURCE -#define _DEFAULT_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// An NtpTimestamp is a 64-bit integer that's a 32.32 binary-fixed point number. -// The integral part in the upper 32 bits represents seconds since 1900-01-01. -// The fractional part in the lower 32 bits stores fractional bits times 2 ** 32. -typedef uint64_t NtpTimestamp; - -struct [[gnu::packed]] NtpPacket { - uint8_t li_vn_mode; - uint8_t stratum; - int8_t poll; - int8_t precision; - - uint32_t root_delay; - uint32_t root_dispersion; - uint32_t reference_id; - - NtpTimestamp reference_timestamp; - NtpTimestamp origin_timestamp; - NtpTimestamp receive_timestamp; - NtpTimestamp transmit_timestamp; - - uint8_t leap_information() const { return li_vn_mode >> 6; } - uint8_t version_number() const { return (li_vn_mode >> 3) & 7; } - uint8_t mode() const { return li_vn_mode & 7; } -}; -static_assert(sizeof(NtpPacket) == 48); - -// NTP measures time in seconds since 1900-01-01, POSIX in seconds since 1970-01-01. -// 1900 wasn't a leap year, so there are 70/4 leap years between 1900 and 1970. -// Overflows a 32-bit signed int, but not a 32-bit unsigned int. -const unsigned SecondsFrom1900To1970 = (70u * 365u + 70u / 4u) * 24u * 60u * 60u; - -static NtpTimestamp ntp_timestamp_from_timeval(const timeval& t) -{ - ASSERT(t.tv_usec >= 0 && t.tv_usec < 1'000'000); // Fits in 20 bits when normalized. - - // Seconds just need translation to the different origin. - uint32_t seconds = t.tv_sec + SecondsFrom1900To1970; - - // Fractional bits are decimal fixed point (*1'000'000) in timeval, but binary fixed-point (* 2**32) in NTP timestamps. - uint32_t fractional_bits = static_cast((static_cast(t.tv_usec) << 32) / 1'000'000); - - return (static_cast(seconds) << 32) | fractional_bits; -} - -static timeval timeval_from_ntp_timestamp(const NtpTimestamp& ntp_timestamp) -{ - timeval t; - t.tv_sec = static_cast(ntp_timestamp >> 32) - SecondsFrom1900To1970; - t.tv_usec = static_cast((static_cast(ntp_timestamp & 0xFFFFFFFFu) * 1'000'000) >> 32); - return t; -} - -static String format_ntp_timestamp(NtpTimestamp ntp_timestamp) -{ - char buffer[28]; // YYYY-MM-DDTHH:MM:SS.UUUUUUZ is 27 characters long. - timeval t = timeval_from_ntp_timestamp(ntp_timestamp); - struct tm tm; - gmtime_r(&t.tv_sec, &tm); - size_t written = strftime(buffer, sizeof(buffer), "%Y-%m-%dT%T.", &tm); - ASSERT(written == 20); - written += snprintf(buffer + written, sizeof(buffer) - written, "%06d", (int)t.tv_usec); - ASSERT(written == 26); - buffer[written++] = 'Z'; - buffer[written] = '\0'; - return buffer; -} - -int main(int argc, char** argv) -{ -#ifdef __serenity__ - if (pledge("stdio inet dns settime", nullptr) < 0) { - perror("pledge"); - return 1; - } -#endif - - bool adjust_time = false; - bool set_time = false; - bool verbose = false; - // FIXME: Change to serenityos.pool.ntp.org once https://manage.ntppool.org/manage/vendor/zone?a=km5a8h&id=vz-14154g is approved. - // Other NTP servers: - // - time.nist.gov - // - time.apple.com - // - time.cloudflare.com (has NTS), https://blog.cloudflare.com/secure-time/ - // - time.windows.com - // - // Leap seconds smearing NTP servers: - // - time.facebook.com , https://engineering.fb.com/production-engineering/ntp-service/ , sine-smears over 18 hours - // - time.google.com , https://developers.google.com/time/smear , linear-smears over 24 hours - const char* host = "time.google.com"; - Core::ArgsParser args_parser; - args_parser.add_option(adjust_time, "Gradually adjust system time (requires root)", "adjust", 'a'); - args_parser.add_option(set_time, "Immediately set system time (requires root)", "set", 's'); - args_parser.add_option(verbose, "Verbose output", "verbose", 'v'); - args_parser.add_positional_argument(host, "NTP server", "host", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (adjust_time && set_time) { - fprintf(stderr, "-a and -s are mutually exclusive\n"); - return 1; - } - -#ifdef __serenity__ - if (!adjust_time && !set_time) { - if (pledge("stdio inet dns", nullptr) < 0) { - perror("pledge"); - return 1; - } - } -#endif - - auto* hostent = gethostbyname(host); - if (!hostent) { - fprintf(stderr, "Lookup failed for '%s'\n", host); - return 1; - } - -#ifdef __serenity__ - if (pledge((adjust_time || set_time) ? "stdio inet settime" : "stdio inet", nullptr) < 0) { - perror("pledge"); - return 1; - } - unveil(nullptr, nullptr); -#endif - - int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (fd < 0) { - perror("socket"); - return 1; - } - - struct timeval timeout { - 5, 0 - }; - if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { - perror("setsockopt"); - return 1; - } - - int enable = 1; - if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable)) < 0) { - perror("setsockopt"); - return 1; - } - - sockaddr_in peer_address; - memset(&peer_address, 0, sizeof(peer_address)); - peer_address.sin_family = AF_INET; - peer_address.sin_port = htons(123); - peer_address.sin_addr.s_addr = *(const in_addr_t*)hostent->h_addr_list[0]; - - NtpPacket packet; - memset(&packet, 0, sizeof(packet)); - packet.li_vn_mode = (4 << 3) | 3; // Version 4, client connection. - - // The server will copy the transmit_timestamp to origin_timestamp in the reply. - // To not leak the local time, keep the time we sent the packet locally and - // send random bytes to the server. - auto random_transmit_timestamp = get_random(); - timeval local_transmit_time; - gettimeofday(&local_transmit_time, nullptr); - packet.transmit_timestamp = random_transmit_timestamp; - - ssize_t rc; - rc = sendto(fd, &packet, sizeof(packet), 0, (const struct sockaddr*)&peer_address, sizeof(peer_address)); - if (rc < 0) { - perror("sendto"); - return 1; - } - if ((size_t)rc < sizeof(packet)) { - fprintf(stderr, "incomplete packet send\n"); - return 1; - } - - iovec iov { &packet, sizeof(packet) }; - char control_message_buffer[CMSG_SPACE(sizeof(timeval))]; - msghdr msg = { &peer_address, sizeof(peer_address), &iov, 1, control_message_buffer, sizeof(control_message_buffer), 0 }; - rc = recvmsg(fd, &msg, 0); - if (rc < 0) { - perror("recvmsg"); - return 1; - } - timeval userspace_receive_time; - gettimeofday(&userspace_receive_time, nullptr); - if ((size_t)rc < sizeof(packet)) { - fprintf(stderr, "incomplete packet recv\n"); - return 1; - } - - cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT(cmsg->cmsg_level == SOL_SOCKET); - ASSERT(cmsg->cmsg_type == SCM_TIMESTAMP); - ASSERT(!CMSG_NXTHDR(&msg, cmsg)); - timeval kernel_receive_time; - memcpy(&kernel_receive_time, CMSG_DATA(cmsg), sizeof(kernel_receive_time)); - - // Checks 3 and 4 from end of section 5 of rfc4330. - if (packet.version_number() != 3 && packet.version_number() != 4) { - fprintf(stderr, "unexpected version number %d\n", packet.version_number()); - return 1; - } - if (packet.mode() != 4) { // 4 means "server", which should be the reply to our 3 ("client") request. - fprintf(stderr, "unexpected mode %d\n", packet.mode()); - return 1; - } - if (packet.stratum == 0 || packet.stratum >= 16) { - fprintf(stderr, "unexpected stratum value %d\n", packet.stratum); - return 1; - } - if (packet.origin_timestamp != random_transmit_timestamp) { - fprintf(stderr, "expected %#016" PRIx64 " as origin timestamp, got %#016" PRIx64 "\n", random_transmit_timestamp, packet.origin_timestamp); - return 1; - } - if (packet.transmit_timestamp == 0) { - fprintf(stderr, "got transmit_timestamp 0\n"); - return 1; - } - - NtpTimestamp origin_timestamp = ntp_timestamp_from_timeval(local_transmit_time); - NtpTimestamp receive_timestamp = be64toh(packet.receive_timestamp); - NtpTimestamp transmit_timestamp = be64toh(packet.transmit_timestamp); - NtpTimestamp destination_timestamp = ntp_timestamp_from_timeval(kernel_receive_time); - - timeval kernel_to_userspace_latency; - timersub(&userspace_receive_time, &kernel_receive_time, &kernel_to_userspace_latency); - - if (set_time) { - // FIXME: Do all the time filtering described in 5905, or at least correct for time of flight. - timeval t = timeval_from_ntp_timestamp(transmit_timestamp); - if (settimeofday(&t, nullptr) < 0) { - perror("settimeofday"); - return 1; - } - } - - if (verbose) { - printf("NTP response from %s:\n", inet_ntoa(peer_address.sin_addr)); - printf("Leap Information: %d\n", packet.leap_information()); - printf("Version Number: %d\n", packet.version_number()); - printf("Mode: %d\n", packet.mode()); - printf("Stratum: %d\n", packet.stratum); - printf("Poll: %d\n", packet.stratum); - printf("Precision: %d\n", packet.precision); - printf("Root delay: %#x\n", ntohl(packet.root_delay)); - printf("Root dispersion: %#x\n", ntohl(packet.root_dispersion)); - - u32 ref_id = ntohl(packet.reference_id); - printf("Reference ID: %#x", ref_id); - if (packet.stratum == 1) { - printf(" ('%c%c%c%c')", (ref_id & 0xff000000) >> 24, (ref_id & 0xff0000) >> 16, (ref_id & 0xff00) >> 8, ref_id & 0xff); - } - printf("\n"); - - printf("Reference timestamp: %#016" PRIx64 " (%s)\n", be64toh(packet.reference_timestamp), format_ntp_timestamp(be64toh(packet.reference_timestamp)).characters()); - printf("Origin timestamp: %#016" PRIx64 " (%s)\n", origin_timestamp, format_ntp_timestamp(origin_timestamp).characters()); - printf("Receive timestamp: %#016" PRIx64 " (%s)\n", receive_timestamp, format_ntp_timestamp(receive_timestamp).characters()); - printf("Transmit timestamp: %#016" PRIx64 " (%s)\n", transmit_timestamp, format_ntp_timestamp(transmit_timestamp).characters()); - printf("Destination timestamp: %#016" PRIx64 " (%s)\n", destination_timestamp, format_ntp_timestamp(destination_timestamp).characters()); - - // When the system isn't under load, user-space t and packet_t are identical. If a shell with `yes` is running, it can be as high as 30ms in this program, - // which gets user-space time immediately after the recvmsg() call. In programs that have an event loop reading from multiple sockets, it could be higher. - printf("Receive latency: %" PRId64 ".%06d s\n", (i64)kernel_to_userspace_latency.tv_sec, (int)kernel_to_userspace_latency.tv_usec); - } - - // Parts of the "Clock Filter" computations, https://tools.ietf.org/html/rfc5905#section-10 - NtpTimestamp T1 = origin_timestamp; - NtpTimestamp T2 = receive_timestamp; - NtpTimestamp T3 = transmit_timestamp; - NtpTimestamp T4 = destination_timestamp; - auto timestamp_difference_in_seconds = [](NtpTimestamp from, NtpTimestamp to) { - return static_cast(to - from) / pow(2.0, 32); - }; - - // The network round-trip time of the request. - // T4-T1 is the wall clock roundtrip time, in local ticks. - // T3-T2 is the server side processing time, in server ticks. - double delay_s = timestamp_difference_in_seconds(T1, T4) - timestamp_difference_in_seconds(T2, T3); - - // The offset from local time to server time, ignoring network delay. - // Both T2-T1 and T3-T4 estimate this; this takes the average of both. - // Or, equivalently, (T1+T4)/2 estimates local time, (T2+T3)/2 estimate server time, this is the difference. - double offset_s = 0.5 * (timestamp_difference_in_seconds(T1, T2) + timestamp_difference_in_seconds(T4, T3)); - if (verbose) - printf("Delay: %f\n", delay_s); - printf("Offset: %f\n", offset_s); - - if (adjust_time) { - long delta_us = static_cast(round(offset_s * 1'000'000)); - timeval delta_timeval; - delta_timeval.tv_sec = delta_us / 1'000'000; - delta_timeval.tv_usec = delta_us % 1'000'000; - if (delta_timeval.tv_usec < 0) { - delta_timeval.tv_sec--; - delta_timeval.tv_usec += 1'000'000; - } - if (adjtime(&delta_timeval, nullptr) < 0) { - perror("adjtime set"); - return 1; - } - } -} diff --git a/Userland/open.cpp b/Userland/open.cpp deleted file mode 100644 index c141eb817f..0000000000 --- a/Userland/open.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2020, Sergey Bugaev - * Copyright (c) 2020, Linus Groh - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char* argv[]) -{ - Core::EventLoop loop; - Vector urls_or_paths; - Core::ArgsParser parser; - parser.set_general_help("Open a file or URL by executing the appropriate program."); - parser.add_positional_argument(urls_or_paths, "URL or file path to open", "url-or-path"); - parser.parse(argc, argv); - - bool all_ok = true; - - for (auto& url_or_path : urls_or_paths) { - auto url = URL::create_with_url_or_path(url_or_path); - - if (url.protocol() == "file") { - auto real_path = Core::File::real_path_for(url.path()); - if (real_path.is_null()) { - // errno *should* be preserved from Core::File::real_path_for(). - warnln("Failed to open '{}': {}", url.path(), strerror(errno)); - all_ok = false; - continue; - } - url = URL::create_with_url_or_path(real_path); - } - - if (!Desktop::Launcher::open(url)) { - warnln("Failed to open '{}'", url); - all_ok = false; - } - } - - return all_ok ? 0 : 1; -} diff --git a/Userland/pape.cpp b/Userland/pape.cpp deleted file mode 100644 index 9c79dd7d5c..0000000000 --- a/Userland/pape.cpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int handle_show_all() -{ - Core::DirIterator di("/res/wallpapers", Core::DirIterator::SkipDots); - if (di.has_error()) { - fprintf(stderr, "DirIterator: %s\n", di.error_string()); - return 1; - } - - while (di.has_next()) { - String name = di.next_path(); - printf("%s\n", name.characters()); - } - return 0; -} - -static int handle_show_current() -{ - printf("%s\n", GUI::Desktop::the().wallpaper().characters()); - return 0; -} - -static int handle_set_pape(const String& name) -{ - StringBuilder builder; - builder.append("/res/wallpapers/"); - builder.append(name); - String path = builder.to_string(); - if (!GUI::Desktop::the().set_wallpaper(path)) { - fprintf(stderr, "pape: Failed to set wallpaper %s\n", path.characters()); - return 1; - } - return 0; -}; - -int main(int argc, char** argv) -{ - bool show_all = false; - bool show_current = false; - const char* name = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(show_all, "Show all wallpapers", "show-all", 'a'); - args_parser.add_option(show_current, "Show current wallpaper", "show-current", 'c'); - args_parser.add_positional_argument(name, "Wallpaper to set", "name", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - auto app = GUI::Application::construct(argc, argv); - - if (show_all) - return handle_show_all(); - else if (show_current) - return handle_show_current(); - - return handle_set_pape(name); -} diff --git a/Userland/passwd.cpp b/Userland/passwd.cpp deleted file mode 100644 index b0a36e437a..0000000000 --- a/Userland/passwd.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2020, Peter Elliott - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (geteuid() != 0) { - warnln("Not running as root :^("); - return 1; - } - - if (pledge("stdio wpath rpath cpath tty id", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/etc/passwd", "rwc") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/etc/group", "rwc") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/etc/shadow", "rwc") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - bool del = false; - bool lock = false; - bool unlock = false; - const char* username = nullptr; - - auto args_parser = Core::ArgsParser(); - args_parser.set_general_help("Modify an account password."); - args_parser.add_option(del, "Delete password", "delete", 'd'); - args_parser.add_option(lock, "Lock password", "lock", 'l'); - args_parser.add_option(unlock, "Unlock password", "unlock", 'u'); - args_parser.add_positional_argument(username, "Username", "username", Core::ArgsParser::Required::No); - - args_parser.parse(argc, argv); - - uid_t current_uid = getuid(); - - auto account_or_error = (username) - ? Core::Account::from_name(username, Core::Account::OpenPasswdFile::ReadWrite, Core::Account::OpenShadowFile::ReadWrite) - : Core::Account::from_uid(current_uid, Core::Account::OpenPasswdFile::ReadWrite, Core::Account::OpenShadowFile::ReadWrite); - - if (account_or_error.is_error()) { - warnln("Core::Account::{}: {}", (username) ? "from_name" : "from_uid", account_or_error.error()); - return 1; - } - - // Drop privileges after opening all the files through the Core::Account object. - auto gid = getgid(); - if (setresgid(gid, gid, gid) < 0) { - perror("setresgid"); - return 1; - } - - auto uid = getuid(); - if (setresuid(uid, uid, uid) < 0) { - perror("setresuid"); - return 1; - } - - // Make sure /etc/passwd is open and ready for reading, then we can drop a bunch of pledge promises. - setpwent(); - - if (pledge("stdio tty", nullptr) < 0) { - perror("pledge"); - return 1; - } - - // target_account is the account we are changing the password of. - auto& target_account = account_or_error.value(); - - if (current_uid != 0 && current_uid != target_account.uid()) { - warnln("You can't modify passwd for {}", username); - return 1; - } - - if (del) { - target_account.delete_password(); - } else if (lock) { - target_account.set_password_enabled(false); - } else if (unlock) { - target_account.set_password_enabled(true); - } else { - auto new_password = Core::get_password("New password: "); - if (new_password.is_error()) { - warnln("{}", new_password.error()); - return 1; - } - - target_account.set_password(new_password.value().characters()); - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (!target_account.sync()) { - perror("Core::Account::Sync"); - } - - return 0; -} diff --git a/Userland/paste.cpp b/Userland/paste.cpp deleted file mode 100644 index df19f15e4c..0000000000 --- a/Userland/paste.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2019-2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char* argv[]) -{ - bool print_type = false; - bool no_newline = false; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Paste from the clipboard to stdout."); - args_parser.add_option(print_type, "Display the copied type", "print-type", 0); - args_parser.add_option(no_newline, "Do not append a newline", "no-newline", 'n'); - args_parser.parse(argc, argv); - - auto app = GUI::Application::construct(argc, argv); - - auto& clipboard = GUI::Clipboard::the(); - auto data_and_type = clipboard.data_and_type(); - - if (data_and_type.mime_type.is_null()) { - warnln("Nothing copied"); - return 1; - } - - if (!print_type) { - out("{}", StringView(data_and_type.data)); - // Append a newline to text contents, unless the caller says otherwise. - if (data_and_type.mime_type.starts_with("text/") && !no_newline) - outln(); - } else { - outln("{}", data_and_type.mime_type); - } - - return 0; -} diff --git a/Userland/pidof.cpp b/Userland/pidof.cpp deleted file mode 100644 index 71ae5ea767..0000000000 --- a/Userland/pidof.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int pid_of(const String& process_name, bool single_shot, bool omit_pid, pid_t pid) -{ - bool displayed_at_least_one = false; - - auto processes = Core::ProcessStatisticsReader().get_all(); - if (!processes.has_value()) - return 1; - - for (auto& it : processes.value()) { - if (it.value.name == process_name) { - if (!omit_pid || it.value.pid != pid) { - printf(" %d" + (displayed_at_least_one ? 0 : 1), it.value.pid); - displayed_at_least_one = true; - - if (single_shot) - break; - } - } - } - - if (displayed_at_least_one) - printf("\n"); - - return 0; -} - -int main(int argc, char** argv) -{ - bool single_shot = false; - const char* omit_pid_value = nullptr; - const char* process_name = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(single_shot, "Only return one pid", nullptr, 's'); - args_parser.add_option(omit_pid_value, "Omit the given PID, or the parent process if the special value %PPID is passed", nullptr, 'o', "pid"); - args_parser.add_positional_argument(process_name, "Process name to search for", "process-name"); - - args_parser.parse(argc, argv); - - pid_t pid_to_omit = 0; - if (omit_pid_value) { - if (!strcmp(omit_pid_value, "%PPID")) { - pid_to_omit = getppid(); - } else { - auto number = StringView(omit_pid_value).to_uint(); - if (!number.has_value()) { - fprintf(stderr, "Invalid value for -o\n"); - args_parser.print_usage(stderr, argv[0]); - return 1; - } - pid_to_omit = number.value(); - } - } - return pid_of(process_name, single_shot, omit_pid_value != nullptr, pid_to_omit); -} diff --git a/Userland/ping.cpp b/Userland/ping.cpp deleted file mode 100644 index a960c73dc5..0000000000 --- a/Userland/ping.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static uint16_t internet_checksum(const void* ptr, size_t count) -{ - uint32_t checksum = 0; - auto* w = (const uint16_t*)ptr; - while (count > 1) { - checksum += ntohs(*w++); - if (checksum & 0x80000000) - checksum = (checksum & 0xffff) | (checksum >> 16); - count -= 2; - } - while (checksum >> 16) - checksum = (checksum & 0xffff) + (checksum >> 16); - return htons(~checksum); -} - -static int total_pings; -static int successful_pings; -static uint32_t total_ms; -static int min_ms; -static int max_ms; -static const char* host; - -int main(int argc, char** argv) -{ - if (pledge("stdio id inet dns sigaction", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(host, "Host to ping", "host"); - args_parser.parse(argc, argv); - - int fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); - if (fd < 0) { - perror("socket"); - return 1; - } - - if (setgid(getgid()) || setuid(getuid())) { - fprintf(stderr, "Failed to drop privileges.\n"); - return 1; - } - - if (pledge("stdio inet dns sigaction", nullptr) < 0) { - perror("pledge"); - return 1; - } - - struct timeval timeout { - 1, 0 - }; - - int rc = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); - if (rc < 0) { - perror("setsockopt"); - return 1; - } - - auto* hostent = gethostbyname(host); - if (!hostent) { - printf("Lookup failed for '%s'\n", host); - return 1; - } - - if (pledge("stdio inet sigaction", nullptr) < 0) { - perror("pledge"); - return 1; - } - - pid_t pid = getpid(); - - sockaddr_in peer_address; - memset(&peer_address, 0, sizeof(peer_address)); - peer_address.sin_family = AF_INET; - peer_address.sin_port = 0; - - peer_address.sin_addr.s_addr = *(const in_addr_t*)hostent->h_addr_list[0]; - - struct PingPacket { - struct icmphdr header; - char msg[64 - sizeof(struct icmphdr)]; - }; - - struct PongPacket { - // FIXME: IPv4 headers are not actually fixed-size, handle other sizes. - char ip_header[20]; - struct icmphdr header; - char msg[64 - sizeof(struct icmphdr)]; - }; - - uint16_t seq = 1; - - sighandler_t ret = signal(SIGINT, [](int) { - int packet_loss = 100; - - printf("\n--- %s ping statistics ---\n", host); - - if (total_pings) - packet_loss -= 100.0f * successful_pings / total_pings; - printf("%d packets transmitted, %d received, %d%% packet loss\n", - total_pings, successful_pings, packet_loss); - - int average_ms = 0; - if (successful_pings) - average_ms = total_ms / successful_pings; - printf("rtt min/avg/max = %d/%d/%d ms\n", min_ms, average_ms, max_ms); - - exit(0); - }); - - if (ret == SIG_ERR) { - perror("failed to install SIGINT handler"); - return 1; - } - - for (;;) { - PingPacket ping_packet; - memset(&ping_packet, 0, sizeof(PingPacket)); - - ping_packet.header.type = 8; // Echo request - ping_packet.header.code = 0; - ping_packet.header.un.echo.id = htons(pid); - ping_packet.header.un.echo.sequence = htons(seq++); - - bool fits = String("Hello there!\n").copy_characters_to_buffer(ping_packet.msg, sizeof(ping_packet.msg)); - // It's a constant string, we can be sure that it fits. - ASSERT(fits); - - ping_packet.header.checksum = internet_checksum(&ping_packet, sizeof(PingPacket)); - - struct timeval tv_send; - gettimeofday(&tv_send, nullptr); - - rc = sendto(fd, &ping_packet, sizeof(PingPacket), 0, (const struct sockaddr*)&peer_address, sizeof(sockaddr_in)); - if (rc < 0) { - perror("sendto"); - return 1; - } - - total_pings++; - - for (;;) { - PongPacket pong_packet; - socklen_t peer_address_size = sizeof(peer_address); - rc = recvfrom(fd, &pong_packet, sizeof(PongPacket), 0, (struct sockaddr*)&peer_address, &peer_address_size); - if (rc < 0) { - if (errno == EAGAIN) { - printf("Request (seq=%u) timed out.\n", ntohs(ping_packet.header.un.echo.sequence)); - break; - } - perror("recvfrom"); - return 1; - } - - if (pong_packet.header.type != 0) - continue; - if (pong_packet.header.code != 0) - continue; - if (ntohs(pong_packet.header.un.echo.id) != pid) - continue; - - struct timeval tv_receive; - gettimeofday(&tv_receive, nullptr); - - struct timeval tv_diff; - timersub(&tv_receive, &tv_send, &tv_diff); - - int ms = tv_diff.tv_sec * 1000 + tv_diff.tv_usec / 1000; - successful_pings++; - int seq_dif = ntohs(ping_packet.header.un.echo.sequence) - ntohs(pong_packet.header.un.echo.sequence); - - // Approximation about the timeout of the out of order packet - if (seq_dif) - ms += seq_dif * 1000 * timeout.tv_sec; - - total_ms += ms; - if (min_ms == 0) - min_ms = max_ms = ms; - else if (ms < min_ms) - min_ms = ms; - else if (ms > max_ms) - max_ms = ms; - - char addr_buf[64]; - printf("Pong from %s: id=%u, seq=%u%s, time=%dms\n", - inet_ntop(AF_INET, &peer_address.sin_addr, addr_buf, sizeof(addr_buf)), - ntohs(pong_packet.header.un.echo.id), - ntohs(pong_packet.header.un.echo.sequence), - pong_packet.header.un.echo.sequence != ping_packet.header.un.echo.sequence ? "(!)" : "", - ms); - - // If this was a response to an earlier packet, we still need to wait for the current one. - if (pong_packet.header.un.echo.sequence != ping_packet.header.un.echo.sequence) - continue; - break; - } - - sleep(1); - } - - return 0; -} diff --git a/Userland/pmap.cpp b/Userland/pmap.cpp deleted file mode 100644 index a0cdf12b72..0000000000 --- a/Userland/pmap.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/proc", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - const char* pid; - static bool extended = false; - - Core::ArgsParser args_parser; - args_parser.add_option(extended, "Extended output", nullptr, 'x'); - args_parser.add_positional_argument(pid, "PID", "PID", Core::ArgsParser::Required::Yes); - args_parser.parse(argc, argv); - - auto file = Core::File::construct(String::format("/proc/%s/vm", pid)); - if (!file->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Error: %s\n", file->error_string()); - return 1; - } - - printf("%s:\n", pid); - - if (extended) { - printf("Address Size Resident Dirty Access VMObject Type Purgeable CoW Pages Name\n"); - } else { - printf("Address Size Access Name\n"); - } - - auto file_contents = file->read_all(); - auto json = JsonValue::from_string(file_contents); - ASSERT(json.has_value()); - json.value().as_array().for_each([](auto& value) { - auto map = value.as_object(); - auto address = map.get("address").to_int(); - auto size = map.get("size").to_string(); - - auto readable = map.get("readable").to_bool(); - auto writable = map.get("writable").to_bool(); - auto executable = map.get("executable").to_bool(); - auto access = String::format("%s%s%s", (readable ? "r" : "-"), (writable ? "w" : "-"), (executable ? "x" : "-")); - - printf("%08x ", address); - printf("%-10s ", size.characters()); - if (extended) { - auto resident = map.get("amount_resident").to_string(); - auto dirty = map.get("amount_dirty").to_string(); - auto vmobject = map.get("vmobject").to_string(); - auto purgeable = map.get("purgeable").to_string(); - auto cow_pages = map.get("cow_pages").to_string(); - printf("%-10s ", resident.characters()); - printf("%-10s ", dirty.characters()); - printf("%-6s ", access.characters()); - printf("%-22s ", vmobject.characters()); - printf("%-10s ", purgeable.characters()); - printf("%-12s ", cow_pages.characters()); - } else { - printf("%-6s ", access.characters()); - } - auto name = map.get("name").to_string(); - printf("%-20s ", name.characters()); - printf("\n"); - }); - - return 0; -} diff --git a/Userland/printf.cpp b/Userland/printf.cpp deleted file mode 100644 index bb434e761c..0000000000 --- a/Userland/printf.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -[[gnu::noreturn]] static void fail(const char* message) -{ - fputs("\e[31m", stderr); - fputs(message, stderr); - fputs("\e[0m\n", stderr); - exit(1); -} - -template typename NextArgument> -struct PrintfImpl : public PrintfImplementation::PrintfImpl { - ALWAYS_INLINE PrintfImpl(PutChFunc& putch, char*& bufptr, const int& nwritten) - : PrintfImplementation::PrintfImpl(putch, bufptr, nwritten) - { - } - - ALWAYS_INLINE int format_q(const PrintfImplementation::ModifierState& state, ArgumentListRefT& ap) const - { - auto state_copy = state; - auto str = NextArgument()(ap); - if (!str) - str = "(null)"; - - constexpr auto make_len_or_escape = [](auto str, bool mk_len, size_t field_width, auto putc) { - unsigned len = 2; - if (!mk_len) - putc('"'); - - for (size_t i = 0; str[i] && (mk_len ? true : (field_width >= len)); ++i) { - auto ch = str[i]; - switch (ch) { - case '"': - case '$': - case '\\': - ++len; - if (!mk_len) - putc('\\'); - } - ++len; - if (!mk_len) - putc(ch); - } - - if (!mk_len) - putc('"'); - - return len; - }; - - auto len = make_len_or_escape(str, true, state_copy.field_width, [&](auto c) { this->m_putch(this->m_bufptr, c); }); - - if (!state_copy.dot && (!state_copy.field_width || state_copy.field_width < len)) - state_copy.field_width = len; - size_t pad_amount = state_copy.field_width > len ? state_copy.field_width - len : 0; - - if (!state_copy.left_pad) { - for (size_t i = 0; i < pad_amount; ++i) - this->m_putch(this->m_bufptr, ' '); - } - - make_len_or_escape(str, false, state_copy.field_width, [&](auto c) { this->m_putch(this->m_bufptr, c); }); - - if (state_copy.left_pad) { - for (size_t i = 0; i < pad_amount; ++i) - this->m_putch(this->m_bufptr, ' '); - } - - return state_copy.field_width; - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE T operator()(V) const - { - static_assert(sizeof(V) != sizeof(V), "Base instantiated"); - return declval(); - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE char* operator()(V arg) const - { - if (arg.argc == 0) - fail("Not enough arguments"); - - auto result = *arg.argv++; - --arg.argc; - return result; - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE const char* operator()(V arg) const - { - if (arg.argc == 0) - return ""; - - auto result = *arg.argv++; - --arg.argc; - return result; - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE int operator()(V arg) const - { - if (arg.argc == 0) - return 0; - - auto result = *arg.argv++; - --arg.argc; - return atoi(result); - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE unsigned operator()(V arg) const - { - if (arg.argc == 0) - return 0; - - auto result = *arg.argv++; - --arg.argc; - return strtoul(result, nullptr, 10); - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE i64 operator()(V arg) const - { - if (arg.argc == 0) - return 0; - - auto result = *arg.argv++; - --arg.argc; - return strtoll(result, nullptr, 10); - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE u64 operator()(V arg) const - { - if (arg.argc == 0) - return 0; - - auto result = *arg.argv++; - --arg.argc; - return strtoull(result, nullptr, 10); - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE double operator()(V arg) const - { - if (arg.argc == 0) - return 0; - - auto result = *arg.argv++; - --arg.argc; - return strtod(result, nullptr); - } -}; - -template -struct ArgvNextArgument { - ALWAYS_INLINE int* operator()(V) const - { - ASSERT_NOT_REACHED(); - return nullptr; - } -}; - -struct ArgvWithCount { - char**& argv; - int& argc; -}; - -static String handle_escapes(const char* string) -{ - StringBuilder builder; - for (auto c = *string; c; c = *++string) { - if (c == '\\') { - if (string[1]) { - switch (c = *++string) { - case '\\': - case '"': - builder.append(c); - break; - case 'a': - builder.append('\a'); - break; - case 'b': - builder.append('\b'); - break; - case 'c': - return builder.build(); - case 'e': - builder.append('\e'); - break; - case 'f': - builder.append('\f'); - break; - case 'n': - builder.append('\n'); - break; - case 'r': - builder.append('\r'); - break; - case 't': - builder.append('\t'); - break; - case 'v': - builder.append('\v'); - break; - case 'x': - fail("Unsupported escape '\\x'"); - case 'u': - fail("Unsupported escape '\\u'"); - case 'U': - fail("Unsupported escape '\\U'"); - default: - builder.append(c); - } - } else { - builder.append(c); - } - } else { - builder.append(c); - } - } - - return builder.build(); -} - -int main(int argc, char** argv) -{ - if (argc < 2) - return 1; - - ++argv; - String format = handle_escapes(*(argv++)); - auto format_string = format.characters(); - - argc -= 2; - - ArgvWithCount arg { argv, argc }; - - auto putch = [](auto*, auto ch) { - putchar(ch); - }; - - auto previous_argc = 0; - do { - previous_argc = argc; - PrintfImplementation::printf_internal(putch, nullptr, format_string, arg); - } while (argc && previous_argc != argc); - - return 0; -} diff --git a/Userland/pro.cpp b/Userland/pro.cpp deleted file mode 100644 index 95f9a74ed0..0000000000 --- a/Userland/pro.cpp +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// FIXME: Move this somewhere else when it's needed (e.g. in the Browser) -class ContentDispositionParser { -public: - ContentDispositionParser(const StringView& value) - { - GenericLexer lexer(value); - - lexer.ignore_while(isspace); - - if (lexer.consume_specific("inline")) { - m_kind = Kind::Inline; - if (!lexer.is_eof()) - m_might_be_wrong = true; - return; - } - - if (lexer.consume_specific("attachment")) { - m_kind = Kind::Attachment; - if (lexer.consume_specific(";")) { - lexer.ignore_while(isspace); - if (lexer.consume_specific("filename=")) { - // RFC 2183: "A short (length <= 78 characters) - // parameter value containing only non-`tspecials' characters SHOULD be - // represented as a single `token'." - // Some people seem to take this as generic advice of "if it doesn't have special characters, - // it's safe to specify as a single token" - // So let's just be as lenient as possible. - if (lexer.next_is('"')) - m_filename = lexer.consume_quoted_string(); - else - m_filename = lexer.consume_until(is_any_of("()<>@,;:\\\"/[]?= ")); - } else { - m_might_be_wrong = true; - } - } - return; - } - - if (lexer.consume_specific("form-data")) { - m_kind = Kind::FormData; - while (lexer.consume_specific(";")) { - lexer.ignore_while(isspace); - if (lexer.consume_specific("name=")) { - m_name = lexer.consume_quoted_string(); - } else if (lexer.consume_specific("filename=")) { - if (lexer.next_is('"')) - m_filename = lexer.consume_quoted_string(); - else - m_filename = lexer.consume_until(is_any_of("()<>@,;:\\\"/[]?= ")); - } else { - m_might_be_wrong = true; - } - } - - return; - } - - // FIXME: Support 'filename*' - m_might_be_wrong = true; - } - - enum class Kind { - Inline, - Attachment, - FormData, - }; - - const StringView& filename() const { return m_filename; } - const StringView& name() const { return m_name; } - Kind kind() const { return m_kind; } - bool might_be_wrong() const { return m_might_be_wrong; } - -private: - StringView m_filename; - StringView m_name; - Kind m_kind { Kind::Inline }; - bool m_might_be_wrong { false }; -}; - -template -class ConditionalOutputFileStream final : public OutputFileStream { -public: - template - ConditionalOutputFileStream(ConditionT&& condition, Args... args) - : OutputFileStream(args...) - , m_condition(condition) - { - } - - ~ConditionalOutputFileStream() - { - if (!m_condition()) - return; - - if (!m_buffer.is_empty()) { - OutputFileStream::write(m_buffer); - m_buffer.clear(); - } - } - -private: - size_t write(ReadonlyBytes bytes) override - { - if (!m_condition()) { - write_to_buffer:; - m_buffer.append(bytes.data(), bytes.size()); - return bytes.size(); - } - - if (!m_buffer.is_empty()) { - auto size = OutputFileStream::write(m_buffer); - m_buffer = m_buffer.slice(size, m_buffer.size() - size); - } - - if (!m_buffer.is_empty()) - goto write_to_buffer; - - return OutputFileStream::write(bytes); - } - - ConditionT m_condition; - ByteBuffer m_buffer; -}; - -int main(int argc, char** argv) -{ - const char* url_str = nullptr; - bool save_at_provided_name = false; - const char* data = nullptr; - String method = "GET"; - HashMap request_headers; - - Core::ArgsParser args_parser; - args_parser.set_general_help( - "Download a file from an arbitrary URL. This command uses ProtocolServer, " - "and thus supports at least http, https, and gemini."); - args_parser.add_option(save_at_provided_name, "Write to a file named as the remote file", nullptr, 'O'); - args_parser.add_option(data, "(HTTP only) Send the provided data via an HTTP POST request", "data", 'd', "data"); - args_parser.add_option(Core::ArgsParser::Option { - .requires_argument = true, - .help_string = "Add a header entry to the request", - .long_name = "header", - .short_name = 'H', - .value_name = "header-value", - .accept_value = [&](auto* s) { - StringView header { s }; - auto split = header.find_first_of(':'); - if (!split.has_value()) - return false; - request_headers.set(header.substring_view(0, split.value()), header.substring_view(split.value() + 1)); - return true; - } }); - args_parser.add_positional_argument(url_str, "URL to download from", "url"); - args_parser.parse(argc, argv); - - if (data) { - method = "POST"; - // FIXME: Content-Type? - } - - URL url(url_str); - if (!url.is_valid()) { - fprintf(stderr, "'%s' is not a valid URL\n", url_str); - return 1; - } - - Core::EventLoop loop; - auto protocol_client = Protocol::Client::construct(); - - auto download = protocol_client->start_download(method, url.to_string(), request_headers, data ? StringView { data }.bytes() : ReadonlyBytes {}); - if (!download) { - fprintf(stderr, "Failed to start download for '%s'\n", url_str); - return 1; - } - - u32 previous_downloaded_size { 0 }; - timeval prev_time, current_time, time_diff; - gettimeofday(&prev_time, nullptr); - - bool received_actual_headers = false; - - download->on_progress = [&](Optional maybe_total_size, u32 downloaded_size) { - fprintf(stderr, "\r\033[2K"); - if (maybe_total_size.has_value()) { - fprintf(stderr, "\033]9;%d;%d;\033\\", downloaded_size, maybe_total_size.value()); - fprintf(stderr, "Download progress: %s / %s", human_readable_size(downloaded_size).characters(), human_readable_size(maybe_total_size.value()).characters()); - } else { - fprintf(stderr, "Download progress: %s / ???", human_readable_size(downloaded_size).characters()); - } - - gettimeofday(¤t_time, nullptr); - timersub(¤t_time, &prev_time, &time_diff); - - auto time_diff_ms = time_diff.tv_sec * 1000 + time_diff.tv_usec / 1000; - auto size_diff = downloaded_size - previous_downloaded_size; - - fprintf(stderr, " at %s/s", human_readable_size(((float)size_diff / (float)time_diff_ms) * 1000).characters()); - - previous_downloaded_size = downloaded_size; - prev_time = current_time; - }; - - if (save_at_provided_name) { - download->on_headers_received = [&](auto& response_headers, auto status_code) { - if (received_actual_headers) - return; - dbgln("Received headers! response code = {}", status_code.value_or(0)); - received_actual_headers = true; // And not trailers! - String output_name; - if (auto content_disposition = response_headers.get("Content-Disposition"); content_disposition.has_value()) { - auto& value = content_disposition.value(); - ContentDispositionParser parser(value); - output_name = parser.filename(); - } - - if (output_name.is_empty()) - output_name = url.path(); - - LexicalPath path { output_name }; - output_name = path.basename(); - - // The URL didn't have a name component, e.g. 'serenityos.org' - if (output_name.is_empty() || output_name == "/") { - int i = -1; - do { - output_name = url.host(); - if (i > -1) - output_name = String::format("%s.%d", output_name.characters(), i); - ++i; - } while (Core::File::exists(output_name)); - } - - if (freopen(output_name.characters(), "w", stdout) == nullptr) { - perror("freopen"); - loop.quit(1); - return; - } - }; - } - download->on_finish = [&](bool success, auto) { - fprintf(stderr, "\033]9;-1;\033\\"); - fprintf(stderr, "\n"); - if (!success) - fprintf(stderr, "Download failed :(\n"); - loop.quit(0); - }; - - auto output_stream = ConditionalOutputFileStream { [&] { return save_at_provided_name ? received_actual_headers : true; }, stdout }; - download->stream_into(output_stream); - - dbgln("started download with id {}", download->id()); - - auto rc = loop.exec(); - // FIXME: This shouldn't be needed. - fclose(stdout); - return rc; -} diff --git a/Userland/profile.cpp b/Userland/profile.cpp deleted file mode 100644 index 915518beb0..0000000000 --- a/Userland/profile.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - Core::ArgsParser args_parser; - - const char* pid_argument = nullptr; - const char* cmd_argument = nullptr; - bool enable = false; - bool disable = false; - - args_parser.add_option(pid_argument, "Target PID", nullptr, 'p', "PID"); - args_parser.add_option(enable, "Enable", nullptr, 'e'); - args_parser.add_option(disable, "Disable", nullptr, 'd'); - args_parser.add_option(cmd_argument, "Command", nullptr, 'c', "command"); - - args_parser.parse(argc, argv); - - if (!pid_argument && !cmd_argument) { - args_parser.print_usage(stdout, argv[0]); - return 0; - } - - if (pid_argument) { - if (!(enable ^ disable)) { - fprintf(stderr, "-p requires -e xor -d.\n"); - return 1; - } - - pid_t pid = atoi(pid_argument); - - if (enable) { - if (profiling_enable(pid) < 0) { - perror("profiling_enable"); - return 1; - } - return 0; - } - - if (profiling_disable(pid) < 0) { - perror("profiling_disable"); - return 1; - } - - return 0; - } - - auto cmd_parts = String(cmd_argument).split(' '); - Vector cmd_argv; - - for (auto& part : cmd_parts) - cmd_argv.append(part.characters()); - - cmd_argv.append(nullptr); - - dbgln("Enabling profiling for PID {}", getpid()); - profiling_enable(getpid()); - if (execvp(cmd_argv[0], const_cast(cmd_argv.data())) < 0) { - perror("execv"); - return 1; - } - - return 0; -} diff --git a/Userland/ps.cpp b/Userland/ps.cpp deleted file mode 100644 index 48083c3a46..0000000000 --- a/Userland/ps.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath tty", nullptr) < 0) { - perror("pledge"); - return 1; - } - - String this_tty = ttyname(STDIN_FILENO); - - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/proc/all", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/etc/passwd", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - enum class Alignment { - Left, - Right, - }; - - struct Column { - String title; - Alignment alignment { Alignment::Left }; - int width { 0 }; - String buffer; - }; - - bool every_process_flag = false; - bool full_format_flag = false; - - Core::ArgsParser args_parser; - args_parser.add_option(every_process_flag, "Show every process", nullptr, 'e'); - args_parser.add_option(full_format_flag, "Full format", nullptr, 'f'); - args_parser.parse(argc, argv); - - Vector columns; - - int uid_column = -1; - int pid_column = -1; - int ppid_column = -1; - int state_column = -1; - int tty_column = -1; - int cmd_column = -1; - - auto add_column = [&](auto title, auto alignment, auto width) { - columns.append({ title, alignment, width, {} }); - return columns.size() - 1; - }; - - if (full_format_flag) { - uid_column = add_column("UID", Alignment::Left, 9); - pid_column = add_column("PID", Alignment::Right, 5); - ppid_column = add_column("PPID", Alignment::Right, 5); - state_column = add_column("STATE", Alignment::Left, 12); - tty_column = add_column("TTY", Alignment::Left, 6); - cmd_column = add_column("CMD", Alignment::Left, 0); - } else { - pid_column = add_column("PID", Alignment::Right, 5); - tty_column = add_column("TTY", Alignment::Left, 6); - cmd_column = add_column("CMD", Alignment::Left, 0); - } - - auto print_column = [](auto& column, auto& string) { - if (!column.width) { - printf("%s", string.characters()); - return; - } - if (column.alignment == Alignment::Right) - printf("%*s ", column.width, string.characters()); - else - printf("%-*s ", column.width, string.characters()); - }; - - for (auto& column : columns) - print_column(column, column.title); - printf("\n"); - - auto all_processes = Core::ProcessStatisticsReader::get_all(); - if (!all_processes.has_value()) - return 1; - - for (const auto& it : all_processes.value()) { - const auto& proc = it.value; - auto tty = proc.tty; - - if (!every_process_flag && tty != this_tty) - continue; - - if (tty.starts_with("/dev/")) - tty = tty.characters() + 5; - else - tty = "n/a"; - - auto* state = proc.threads.is_empty() ? "Zombie" : proc.threads.first().state.characters(); - - if (uid_column != -1) - columns[uid_column].buffer = proc.username; - if (pid_column != -1) - columns[pid_column].buffer = String::number(proc.pid); - if (ppid_column != -1) - columns[ppid_column].buffer = String::number(proc.ppid); - if (tty_column != -1) - columns[tty_column].buffer = tty; - if (state_column != -1) - columns[state_column].buffer = state; - if (cmd_column != -1) - columns[cmd_column].buffer = proc.name; - - for (auto& column : columns) - print_column(column, column.buffer); - printf("\n"); - } - - return 0; -} diff --git a/Userland/purge.cpp b/Userland/purge.cpp deleted file mode 100644 index 95257ec563..0000000000 --- a/Userland/purge.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - int mode = 0; - - bool purge_all_volatile = false; - bool purge_all_clean_inode = false; - - Core::ArgsParser args_parser; - args_parser.add_option(purge_all_volatile, "Mode PURGE_ALL_VOLATILE", nullptr, 'v'); - args_parser.add_option(purge_all_clean_inode, "Mode PURGE_ALL_CLEAN_INODE", nullptr, 'c'); - args_parser.parse(argc, argv); - - if (!purge_all_volatile && !purge_all_clean_inode) - purge_all_volatile = purge_all_clean_inode = true; - - if (purge_all_volatile) - mode |= PURGE_ALL_VOLATILE; - if (purge_all_clean_inode) - mode |= PURGE_ALL_CLEAN_INODE; - - int purged_page_count = purge(mode); - if (purged_page_count < 0) { - perror("purge"); - return 1; - } - printf("Purged page count: %d\n", purged_page_count); - return 0; -} diff --git a/Userland/readelf.cpp b/Userland/readelf.cpp deleted file mode 100644 index 08bd2e6607..0000000000 --- a/Userland/readelf.cpp +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char* object_file_type_to_string(Elf32_Half type) -{ - switch (type) { - case ET_NONE: - return "None"; - case ET_REL: - return "Relocatable"; - case ET_EXEC: - return "Executable"; - case ET_DYN: - return "Shared object"; - case ET_CORE: - return "Core"; - default: - return "(?)"; - } -} - -static const char* object_machine_type_to_string(Elf32_Half type) -{ - switch (type) { - case ET_NONE: - return "None"; - case EM_M32: - return "AT&T WE 32100"; - case EM_SPARC: - return "SPARC"; - case EM_386: - return "Intel 80386"; - case EM_68K: - return "Motorola 68000"; - case EM_88K: - return "Motorola 88000"; - case EM_486: - return "Intel 80486"; - case EM_860: - return "Intel 80860"; - case EM_MIPS: - return "MIPS R3000 Big-Endian only"; - default: - return "(?)"; - } -} - -static const char* object_program_header_type_to_string(Elf32_Word type) -{ - 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_SHLIB: - return "SHLIB"; - case PT_PHDR: - return "PHDR"; - case PT_TLS: - return "TLS"; - case PT_LOOS: - return "LOOS"; - case PT_HIOS: - return "HIOS"; - case PT_LOPROC: - return "LOPROC"; - case PT_HIPROC: - return "HIPROC"; - case PT_GNU_EH_FRAME: - return "GNU_EH_FRAME"; - case PT_GNU_RELRO: - return "GNU_RELRO"; - case PT_GNU_STACK: - return "GNU_STACK"; - case PT_OPENBSD_RANDOMIZE: - return "OPENBSD_RANDOMIZE"; - case PT_OPENBSD_WXNEEDED: - return "OPENBSD_WXNEEDED"; - case PT_OPENBSD_BOOTDATA: - return "OPENBSD_BOOTDATA"; - default: - return "(?)"; - } -} - -static const char* object_section_header_type_to_string(Elf32_Word type) -{ - 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 SHT_NUM: - return "NUM"; - case SHT_INIT_ARRAY: - return "INIT_ARRAY"; - case SHT_FINI_ARRAY: - return "FINI_ARRAY"; - case SHT_PREINIT_ARRAY: - return "PREINIT_ARRAY"; - case SHT_GROUP: - return "GROUP"; - case SHT_SYMTAB_SHNDX: - return "SYMTAB_SHNDX"; - case SHT_LOOS: - return "SOOS"; - case SHT_SUNW_dof: - return "SUNW_dof"; - case SHT_GNU_LIBLIST: - return "GNU_LIBLIST"; - case SHT_SUNW_move: - return "SUNW_move"; - case SHT_SUNW_syminfo: - return "SUNW_syminfo"; - case SHT_SUNW_verdef: - return "SUNW_verdef"; - case SHT_SUNW_verneed: - return "SUNW_verneed"; - case SHT_SUNW_versym: // or SHT_HIOS - return "SUNW_versym"; - case SHT_LOPROC: - return "LOPROC"; - case SHT_HIPROC: - return "HIPROC"; - case SHT_LOUSER: - return "LOUSER"; - case SHT_HIUSER: - return "HIUSER"; - case SHT_GNU_HASH: - return "GNU_HASH"; - default: - return "(?)"; - } -} - -static const char* object_symbol_type_to_string(Elf32_Word type) -{ - 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"; - case STT_TLS: - return "TLS"; - case STT_LOPROC: - return "LOPROC"; - case STT_HIPROC: - return "HIPROC"; - default: - return "(?)"; - } -} - -static const char* object_symbol_binding_to_string(Elf32_Word type) -{ - switch (type) { - case STB_LOCAL: - return "LOCAL"; - case STB_GLOBAL: - return "GLOBAL"; - case STB_WEAK: - return "WEAK"; - case STB_NUM: - return "NUM"; - case STB_LOPROC: - return "LOPROC"; - case STB_HIPROC: - return "HIPROC"; - default: - return "(?)"; - } -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* path; - static bool display_all = false; - static bool display_elf_header = false; - static bool display_program_headers = false; - static bool display_section_headers = false; - static bool display_headers = false; - static bool display_symbol_table = false; - static bool display_dynamic_symbol_table = false; - static bool display_core_notes = false; - static bool display_relocations = false; - static bool display_unwind_info = false; - static bool display_dynamic_section = false; - - Core::ArgsParser args_parser; - args_parser.add_option(display_all, "Display all", "all", 'a'); - args_parser.add_option(display_elf_header, "Display ELF header", "file-header", 'h'); - args_parser.add_option(display_program_headers, "Display program headers", "program-headers", 'l'); - args_parser.add_option(display_section_headers, "Display section headers", "section-headers", 'S'); - args_parser.add_option(display_headers, "Equivalent to: -h -l -S", "headers", 'e'); - args_parser.add_option(display_symbol_table, "Display the symbol table", "syms", 's'); - args_parser.add_positional_argument(path, "ELF path", "path"); - args_parser.parse(argc, argv); - - if (argc < 3) { - args_parser.print_usage(stderr, argv[0]); - return -1; - } - - if (display_headers) { - display_elf_header = true; - display_program_headers = true; - display_section_headers = true; - } - - if (display_all) { - display_elf_header = true; - display_program_headers = true; - display_section_headers = true; - display_symbol_table = true; - } - - auto file_or_error = MappedFile::map(path); - - if (file_or_error.is_error()) { - warnln("Unable to map file {}: {}", path, file_or_error.error()); - return -1; - } - - auto elf_image_data = file_or_error.value()->bytes(); - ELF::Image executable_elf(elf_image_data); - - if (!executable_elf.is_valid()) { - fprintf(stderr, "File is not a valid ELF object\n"); - return -1; - } - - String interpreter_path; - - if (!ELF::validate_program_headers(*(const Elf32_Ehdr*)elf_image_data.data(), elf_image_data.size(), (const u8*)elf_image_data.data(), elf_image_data.size(), &interpreter_path)) { - fprintf(stderr, "Invalid ELF headers\n"); - return -1; - } - - if (executable_elf.is_dynamic() && interpreter_path.is_null()) { - fprintf(stderr, "Warning: Dynamic ELF object has no interpreter path\n"); - } - - ELF::Image interpreter_image(elf_image_data); - - if (!interpreter_image.is_valid()) { - fprintf(stderr, "ELF image is invalid\n"); - return -1; - } - - auto& header = *reinterpret_cast(elf_image_data.data()); - - if (display_elf_header) { - printf("ELF header:\n"); - - String magic = String::format("%s", header.e_ident); - printf(" Magic: "); - for (size_t i = 0; i < magic.length(); i++) { - if (isprint(magic[i])) { - printf("%c ", magic[i]); - } else { - printf("%02x ", magic[i]); - } - } - printf("\n"); - - printf(" Type: %d (%s)\n", header.e_type, object_file_type_to_string(header.e_type)); - printf(" Machine: %u (%s)\n", header.e_machine, object_machine_type_to_string(header.e_machine)); - printf(" Version: 0x%x\n", header.e_version); - printf(" Entry point address: 0x%x\n", header.e_entry); - printf(" Start of program headers: %u (bytes into file)\n", header.e_phoff); - printf(" Start of section headers: %u (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); - printf("\n"); - } - - if (display_section_headers) { - if (!display_all) { - printf("There are %u section headers, starting at offset 0x%x:\n", header.e_shnum, header.e_shoff); - printf("\n"); - } - - if (!interpreter_image.section_count()) { - printf("There are no sections in this file.\n"); - } else { - printf("Section Headers:\n"); - printf(" Name Type Address Offset Size Flags\n"); - - interpreter_image.for_each_section([](const ELF::Image::Section& section) { - printf(" %-15s ", section.name().characters_without_null_termination()); - printf("%-15s ", object_section_header_type_to_string(section.type())); - printf("%08x ", section.address()); - printf("%08x ", section.offset()); - printf("%08x ", section.size()); - printf("%u", section.flags()); - printf("\n"); - return IterationDecision::Continue; - }); - } - printf("\n"); - } - - if (display_program_headers) { - if (!display_all) { - printf("Elf file type is %d (%s)\n", header.e_type, object_file_type_to_string(header.e_type)); - printf("Entry point 0x%x\n", header.e_entry); - printf("There are %u program headers, starting at offset %u\n", header.e_phnum, header.e_phoff); - printf("\n"); - } - - printf("Program Headers:\n"); - printf(" Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align\n"); - - interpreter_image.for_each_program_header([](const ELF::Image::ProgramHeader& program_header) { - printf(" %-14s ", object_program_header_type_to_string(program_header.type())); - printf("0x%08x ", program_header.offset()); - printf("%p ", program_header.vaddr().as_ptr()); - printf("%p ", program_header.vaddr().as_ptr()); // FIXME: assumes PhysAddr = VirtAddr - printf("0x%08x ", program_header.size_in_image()); - printf("0x%08x ", program_header.size_in_memory()); - printf("%04x ", program_header.flags()); - printf("0x%08x", program_header.alignment()); - printf("\n"); - - if (program_header.type() == PT_INTERP) - printf(" [Interpreter: %s]\n", program_header.raw_data()); - - return IterationDecision::Continue; - }); - printf("\n"); - } - - if (display_dynamic_section) { - // TODO: Dynamic section - } - - if (display_relocations) { - // TODO: Relocations section - } - - if (display_unwind_info) { - // TODO: Unwind info - } - - if (display_core_notes) { - // TODO: Core notes - } - - if (display_dynamic_symbol_table || display_symbol_table) { - // TODO: dynamic symbol table - if (!interpreter_image.symbol_count()) { - printf("Dynamic symbol information is not available for displaying symbols.\n"); - printf("\n"); - } - } - - if (display_symbol_table) { - if (interpreter_image.symbol_count()) { - printf("Symbol table '.symtab' contains %u entries:\n", interpreter_image.symbol_count()); - printf(" Num: Value Size Type Bind Name\n"); - - interpreter_image.for_each_symbol([](const ELF::Image::Symbol& sym) { - printf(" %4u: ", sym.index()); - printf("%08x ", sym.value()); - printf("%08x ", sym.size()); - printf("%-8s ", object_symbol_type_to_string(sym.type())); - printf("%-8s ", object_symbol_binding_to_string(sym.bind())); - printf("%s", sym.name().characters_without_null_termination()); - printf("\n"); - return IterationDecision::Continue; - }); - } - printf("\n"); - } - - return 0; -} diff --git a/Userland/readlink.cpp b/Userland/readlink.cpp deleted file mode 100644 index de65e6212a..0000000000 --- a/Userland/readlink.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool no_newline = false; - Vector paths; - - Core::ArgsParser args_parser; - args_parser.add_option(no_newline, "Do not append a newline", "no-newline", 'n'); - args_parser.add_positional_argument(paths, "Symlink path", "path"); - args_parser.parse(argc, argv); - - for (const char* path : paths) { - auto destination = Core::File::read_link(path); - if (destination.is_null()) { - perror(path); - return 1; - } - printf("%s", destination.characters()); - if (!no_newline) - putchar('\n'); - } - - return 0; -} diff --git a/Userland/realpath.cpp b/Userland/realpath.cpp deleted file mode 100644 index 328f84a1f7..0000000000 --- a/Userland/realpath.cpp +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* path; - - Core::ArgsParser args_parser; - args_parser.set_general_help( - "Show the 'real' path of a file, by resolving all symbolic links along the way."); - args_parser.add_positional_argument(path, "Path to resolve", "path"); - args_parser.parse(argc, argv); - - char* value = realpath(path, nullptr); - if (value == nullptr) { - perror("realpath"); - return 1; - } - printf("%s\n", value); - free(value); - return 0; -} diff --git a/Userland/reboot.cpp b/Userland/reboot.cpp deleted file mode 100644 index ddca508b98..0000000000 --- a/Userland/reboot.cpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include - -int main(int, char**) -{ - if (reboot() < 0) { - perror("reboot"); - return 1; - } - return 0; -} diff --git a/Userland/rm.cpp b/Userland/rm.cpp deleted file mode 100644 index d563a294df..0000000000 --- a/Userland/rm.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int remove(bool recursive, bool force, String path) -{ - struct stat path_stat; - if (lstat(path.characters(), &path_stat) < 0) { - if (!force) - perror("lstat"); - return force ? 0 : 1; - } - - if (S_ISDIR(path_stat.st_mode) && recursive) { - auto di = Core::DirIterator(path, Core::DirIterator::SkipParentAndBaseDir); - if (di.has_error()) { - if (!force) - fprintf(stderr, "DirIterator: %s\n", di.error_string()); - return 1; - } - - while (di.has_next()) { - int s = remove(true, force, di.next_full_path()); - if (s != 0 && !force) - return s; - } - - int s = rmdir(path.characters()); - if (s < 0 && !force) { - perror("rmdir"); - return 1; - } - } else { - int rc = unlink(path.characters()); - if (rc < 0 && !force) { - perror("unlink"); - return 1; - } - } - return 0; -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath cpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool recursive = false; - bool force = false; - bool verbose = false; - Vector paths; - - Core::ArgsParser args_parser; - args_parser.add_option(recursive, "Delete directories recursively", "recursive", 'r'); - args_parser.add_option(force, "Force", "force", 'f'); - args_parser.add_option(verbose, "Verbose", "verbose", 'v'); - args_parser.add_positional_argument(paths, "Path(s) to remove", "path"); - args_parser.parse(argc, argv); - - int rc = 0; - for (auto& path : paths) { - rc |= remove(recursive, force, path); - if (verbose && rc == 0) - printf("removed '%s'\n", path); - } - return rc; -} diff --git a/Userland/rmdir.cpp b/Userland/rmdir.cpp deleted file mode 100644 index 4d321bb9ab..0000000000 --- a/Userland/rmdir.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio cpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* path; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(path, "Directory to remove", "path"); - args_parser.parse(argc, argv); - - int rc = rmdir(path); - if (rc < 0) { - perror("rmdir"); - return 1; - } - return 0; -} diff --git a/Userland/seq.cpp b/Userland/seq.cpp deleted file mode 100644 index a7a6ea7c22..0000000000 --- a/Userland/seq.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2020, Nico Weber - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -const char* const g_usage = R"(Usage: - seq [-h|--help] - seq LAST - seq FIRST LAST - seq FIRST INCREMENT LAST -)"; - -static void print_usage(FILE* stream) -{ - fputs(g_usage, stream); - return; -} - -static double get_double(const char* name, const char* d_string, int* number_of_decimals) -{ - char* end; - double d = strtod(d_string, &end); - if (d == 0 && end == d_string) { - fprintf(stderr, "%s: invalid argument \"%s\"\n", name, d_string); - print_usage(stderr); - exit(1); - } - if (char* dot = strchr(d_string, '.')) - *number_of_decimals = strlen(dot + 1); - else - *number_of_decimals = 0; - return d; -} - -int main(int argc, const char* argv[]) -{ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - if (unveil(nullptr, nullptr) < 0) { - perror("unveil"); - return 1; - } - - for (int i = 1; i < argc; i++) { - if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { - print_usage(stdout); - exit(0); - } - } - - double start = 1, step = 1, end = 1; - int number_of_start_decimals = 0, number_of_step_decimals = 0, number_of_end_decimals = 0; - switch (argc) { - case 2: - end = get_double(argv[0], argv[1], &number_of_end_decimals); - break; - case 3: - start = get_double(argv[0], argv[1], &number_of_start_decimals); - end = get_double(argv[0], argv[2], &number_of_end_decimals); - break; - case 4: - start = get_double(argv[0], argv[1], &number_of_start_decimals); - step = get_double(argv[0], argv[2], &number_of_step_decimals); - end = get_double(argv[0], argv[3], &number_of_end_decimals); - break; - default: - fprintf(stderr, "%s: unexpected number of arguments\n", argv[0]); - print_usage(stderr); - return 1; - } - - if (step == 0) { - fprintf(stderr, "%s: increment must not be 0\n", argv[0]); - return 1; - } - - if (__builtin_isnan(start) || __builtin_isnan(step) || __builtin_isnan(end)) { - fprintf(stderr, "%s: start, step, and end must not be NaN\n", argv[0]); - return 1; - } - - int number_of_decimals = max(number_of_start_decimals, max(number_of_step_decimals, number_of_end_decimals)); - - int n = (end - start) / step; - double d = start; - for (int i = 0; i <= n; ++i) { - char buf[40]; - snprintf(buf, sizeof(buf), "%f", d); - if (char* dot = strchr(buf, '.')) { - if (number_of_decimals == 0) - *dot = '\0'; - else if ((dot - buf) + 1 + number_of_decimals < (int)sizeof(buf)) - dot[1 + number_of_decimals] = '\0'; - } - printf("%s\n", buf); - d += step; - } - - return 0; -} diff --git a/Userland/shutdown.cpp b/Userland/shutdown.cpp deleted file mode 100644 index 9c080771e8..0000000000 --- a/Userland/shutdown.cpp +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - bool now = false; - - Core::ArgsParser args_parser; - args_parser.add_option(now, "Shut down now", "now", 'n'); - args_parser.parse(argc, argv); - - if (now) { - if (halt() < 0) { - perror("shutdown"); - return 1; - } - } else { - args_parser.print_usage(stderr, argv[0]); - return 1; - } -} diff --git a/Userland/sleep.cpp b/Userland/sleep.cpp deleted file mode 100644 index 6cbc4465a6..0000000000 --- a/Userland/sleep.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -static volatile bool g_interrupted; -static void handle_sigint(int) -{ - g_interrupted = true; -} - -int main(int argc, char** argv) -{ - int secs; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(secs, "Number of seconds to sleep for", "num-seconds"); - args_parser.parse(argc, argv); - - struct sigaction sa; - memset(&sa, 0, sizeof(struct sigaction)); - sa.sa_handler = handle_sigint; - sigaction(SIGINT, &sa, nullptr); - - if (pledge("stdio sigaction", nullptr) < 0) { - perror("pledge"); - return 1; - } - - unsigned remaining = sleep(secs); - if (remaining) { - printf("Sleep interrupted with %u seconds remaining.\n", remaining); - } - - signal(SIGINT, SIG_DFL); - if (g_interrupted) - raise(SIGINT); - - return 0; -} diff --git a/Userland/sort.cpp b/Userland/sort.cpp deleted file mode 100644 index 351d196057..0000000000 --- a/Userland/sort.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) -{ - if (pledge("stdio", nullptr) > 0) { - perror("pledge"); - return 1; - } - - Vector lines; - - for (;;) { - char* buffer = nullptr; - ssize_t buflen = 0; - size_t n; - errno = 0; - buflen = getline(&buffer, &n, stdin); - if (buflen == -1 && errno != 0) { - perror("getline"); - exit(1); - } - if (buflen == -1) - break; - lines.append(buffer); - } - - quick_sort(lines, [](auto& a, auto& b) { - return strcmp(a.characters(), b.characters()) < 0; - }); - - for (auto& line : lines) { - fputs(line.characters(), stdout); - } - - return 0; -} diff --git a/Userland/stat.cpp b/Userland/stat.cpp deleted file mode 100644 index 05501ced25..0000000000 --- a/Userland/stat.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2020, Shannon Booth - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int stat(const char* file, bool should_follow_links) -{ - struct stat st; - int rc = should_follow_links ? stat(file, &st) : lstat(file, &st); - if (rc < 0) { - perror("lstat"); - return 1; - } - printf(" File: %s\n", file); - printf(" Inode: %u\n", st.st_ino); - if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) - printf(" Device: %u,%u\n", major(st.st_rdev), minor(st.st_rdev)); - else - printf(" Size: %zd\n", st.st_size); - printf(" Links: %u\n", st.st_nlink); - printf(" Blocks: %u\n", st.st_blocks); - printf(" UID: %u", st.st_uid); - if (auto* pwd = getpwuid(st.st_uid)) { - printf(" (%s)", pwd->pw_name); - } - printf("\n"); - printf(" GID: %u", st.st_gid); - if (auto* grp = getgrgid(st.st_gid)) { - printf(" (%s)", grp->gr_name); - } - printf("\n"); - printf(" Mode: (%o/", st.st_mode); - - if (S_ISDIR(st.st_mode)) - printf("d"); - else if (S_ISLNK(st.st_mode)) - printf("l"); - else if (S_ISBLK(st.st_mode)) - printf("b"); - else if (S_ISCHR(st.st_mode)) - printf("c"); - else if (S_ISFIFO(st.st_mode)) - printf("f"); - else if (S_ISSOCK(st.st_mode)) - printf("s"); - else if (S_ISREG(st.st_mode)) - printf("-"); - else - printf("?"); - - printf("%c%c%c%c%c%c%c%c", - st.st_mode & S_IRUSR ? 'r' : '-', - st.st_mode & S_IWUSR ? 'w' : '-', - st.st_mode & S_ISUID ? 's' : (st.st_mode & S_IXUSR ? 'x' : '-'), - st.st_mode & S_IRGRP ? 'r' : '-', - st.st_mode & S_IWGRP ? 'w' : '-', - st.st_mode & S_ISGID ? 's' : (st.st_mode & S_IXGRP ? 'x' : '-'), - st.st_mode & S_IROTH ? 'r' : '-', - st.st_mode & S_IWOTH ? 'w' : '-'); - - if (st.st_mode & S_ISVTX) - printf("t"); - else - printf("%c", st.st_mode & S_IXOTH ? 'x' : '-'); - - printf(")\n"); - - auto print_time = [](time_t t) { - printf("%s\n", Core::DateTime::from_timestamp(t).to_string().characters()); - }; - - printf("Accessed: "); - print_time(st.st_atime); - printf("Modified: "); - print_time(st.st_mtime); - printf(" Changed: "); - print_time(st.st_ctime); - - return 0; -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool should_follow_links = false; - Vector files; - - auto args_parser = Core::ArgsParser(); - args_parser.add_option(should_follow_links, "Follow links to files", nullptr, 'L'); - args_parser.add_positional_argument(files, "File(s) to stat", "file", Core::ArgsParser::Required::Yes); - args_parser.parse(argc, argv); - - int ret = 0; - for (auto& file : files) - ret |= stat(file, should_follow_links); - - return ret; -} diff --git a/Userland/strace.cpp b/Userland/strace.cpp deleted file mode 100644 index e915e7d3f2..0000000000 --- a/Userland/strace.cpp +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2018-2021, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int g_pid = -1; - -static void handle_sigint(int) -{ - if (g_pid == -1) - return; - - if (ptrace(PT_DETACH, g_pid, 0, 0) == -1) { - perror("detach"); - } -} - -int main(int argc, char** argv) -{ - if (pledge("stdio proc exec ptrace sigaction", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector child_argv; - - const char* output_filename = nullptr; - auto trace_file = Core::File::standard_output(); - - Core::ArgsParser parser; - parser.set_general_help( - "Trace all syscalls and their result."); - parser.add_option(g_pid, "Trace the given PID", "pid", 'p', "pid"); - parser.add_option(output_filename, "Filename to write output to", "output", 'o', "output"); - parser.add_positional_argument(child_argv, "Arguments to exec", "argument", Core::ArgsParser::Required::No); - - parser.parse(argc, argv); - - if (output_filename != nullptr) { - auto open_result = Core::File::open(output_filename, Core::IODevice::OpenMode::WriteOnly); - if (open_result.is_error()) { - outln(stderr, "Failed to open output file: {}", open_result.error()); - return 1; - } - trace_file = open_result.value(); - } - - int status; - if (g_pid == -1) { - if (child_argv.is_empty()) { - outln(stderr, "strace: Expected either a pid or some arguments\n"); - return 1; - } - - child_argv.append(nullptr); - int pid = fork(); - if (pid < 0) { - perror("fork"); - return 1; - } - - if (!pid) { - if (ptrace(PT_TRACE_ME, 0, 0, 0) == -1) { - perror("traceme"); - return 1; - } - int rc = execvp(child_argv.first(), const_cast(child_argv.data())); - if (rc < 0) { - perror("execvp"); - exit(1); - } - ASSERT_NOT_REACHED(); - } - - g_pid = pid; - if (waitpid(pid, &status, WSTOPPED | WEXITED) != pid || !WIFSTOPPED(status)) { - perror("waitpid"); - return 1; - } - } - - struct sigaction sa; - memset(&sa, 0, sizeof(struct sigaction)); - sa.sa_handler = handle_sigint; - sigaction(SIGINT, &sa, nullptr); - - if (ptrace(PT_ATTACH, g_pid, 0, 0) == -1) { - perror("attach"); - return 1; - } - if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { - perror("waitpid"); - return 1; - } - - for (;;) { - if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) { - perror("syscall"); - return 1; - } - if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { - perror("wait_pid"); - return 1; - } - PtraceRegisters regs = {}; - if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) { - perror("getregs"); - return 1; - } - u32 syscall_index = regs.eax; - u32 arg1 = regs.edx; - u32 arg2 = regs.ecx; - u32 arg3 = regs.ebx; - - if (ptrace(PT_SYSCALL, g_pid, 0, 0) == -1) { - perror("syscall"); - return 1; - } - if (waitpid(g_pid, &status, WSTOPPED | WEXITED) != g_pid || !WIFSTOPPED(status)) { - perror("wait_pid"); - return 1; - } - - if (ptrace(PT_GETREGS, g_pid, ®s, 0) == -1) { - perror("getregs"); - return 1; - } - - u32 res = regs.eax; - - trace_file->printf("%s(0x%x, 0x%x, 0x%x)\t=%d\n", - Syscall::to_string( - (Syscall::Function)syscall_index), - arg1, - arg2, - arg3, - res); - } - - return 0; -} diff --git a/Userland/su.cpp b/Userland/su.cpp deleted file mode 100644 index 3377615570..0000000000 --- a/Userland/su.cpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -extern "C" int main(int, char**); - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath tty exec id", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (!isatty(STDIN_FILENO)) { - warnln("{}: standard in is not a terminal", argv[0]); - return 1; - } - - const char* user = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(user, "User to switch to (defaults to user with UID 0)", "user", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (geteuid() != 0) { - warnln("Not running as root :("); - return 1; - } - - auto account_or_error = (user) - ? Core::Account::from_name(user, Core::Account::OpenPasswdFile::No, Core::Account::OpenShadowFile::ReadOnly) - : Core::Account::from_uid(0, Core::Account::OpenPasswdFile::No, Core::Account::OpenShadowFile::ReadOnly); - if (account_or_error.is_error()) { - fprintf(stderr, "Core::Account::from_name: %s\n", account_or_error.error().characters()); - return 1; - } - - if (pledge("stdio tty exec id", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const auto& account = account_or_error.value(); - - if (getuid() != 0 && account.has_password()) { - auto password = Core::get_password(); - if (password.is_error()) { - warnln("{}", password.error()); - return 1; - } - - if (!account.authenticate(password.value().characters())) { - warnln("Incorrect or disabled password."); - return 1; - } - } - - if (pledge("stdio exec id", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (!account.login()) { - perror("Core::Account::login"); - return 1; - } - - execl(account.shell().characters(), account.shell().characters(), nullptr); - perror("execl"); - return 1; -} diff --git a/Userland/sync.cpp b/Userland/sync.cpp deleted file mode 100644 index db0c91630b..0000000000 --- a/Userland/sync.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include - -int main(int, char**) -{ - sync(); - return 0; -} diff --git a/Userland/syscall.cpp b/Userland/syscall.cpp deleted file mode 100644 index fcb7862e3c..0000000000 --- a/Userland/syscall.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#if !defined __ENUMERATE_SYSCALL -# define __ENUMERATE_SYSCALL(x) SC_##x, -#endif - -#define SC_NARG 4 - -Syscall::Function syscall_table[] = { - ENUMERATE_SYSCALLS(__ENUMERATE_SYSCALL) -}; - -FlatPtr arg[SC_NARG]; -char buf[BUFSIZ]; - -FlatPtr parse(char* s); - -int main(int argc, char** argv) -{ - int oflag; - int opt; - while ((opt = getopt(argc, argv, "olh")) != -1) { - switch (opt) { - case 'o': - oflag = 1; - break; - case 'l': - for (auto sc : syscall_table) { - fprintf(stdout, "%s ", Syscall::to_string(sc)); - } - return EXIT_SUCCESS; - case 'h': - fprintf(stderr, "usage: \tsyscall [-o] [-l] [-h] [buf==BUFSIZ buffer]\n"); - fprintf(stderr, "\tsyscall write 1 hello 5\n"); - fprintf(stderr, "\tsyscall -o read 0 buf 5\n"); - fprintf(stderr, "\tsyscall sleep 3\n"); - break; - default: - exit(EXIT_FAILURE); - } - } - - if (optind >= argc) { - fprintf(stderr, "No entry specified\n"); - return -1; - } - - for (int i = 0; i < argc - optind; i++) { - arg[i] = parse(argv[i + optind]); - } - - for (auto sc : syscall_table) { - if (strcmp(Syscall::to_string(sc), (char*)arg[0]) == 0) { - int rc = syscall(sc, arg[1], arg[2], arg[3]); - if (rc == -1) { - perror("syscall"); - } else { - if (oflag) - fwrite(buf, 1, sizeof(buf), stdout); - } - - fprintf(stderr, "Syscall return: %d\n", rc); - return 0; - } - } - - fprintf(stderr, "Invalid syscall entry %s\n", (char*)arg[0]); - return -1; -} - -FlatPtr parse(char* s) -{ - char* t; - FlatPtr l; - - if (strcmp(s, "buf") == 0) { - return (FlatPtr)buf; - } - - l = strtoul(s, &t, 0); - if (t > s && *t == 0) { - return l; - } - - return (FlatPtr)s; -} diff --git a/Userland/sysctl.cpp b/Userland/sysctl.cpp deleted file mode 100644 index 74336b6304..0000000000 --- a/Userland/sysctl.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static String read_var(const String& name) -{ - StringBuilder builder; - builder.append("/proc/sys/"); - builder.append(name); - auto path = builder.to_string(); - auto f = Core::File::construct(path); - if (!f->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "open: %s\n", f->error_string()); - exit(1); - } - const auto& b = f->read_all(); - if (f->error() < 0) { - fprintf(stderr, "read: %s\n", f->error_string()); - exit(1); - } - return String((const char*)b.data(), b.size(), Chomp); -} - -static void write_var(const String& name, const String& value) -{ - StringBuilder builder; - builder.append("/proc/sys/"); - builder.append(name); - auto path = builder.to_string(); - auto f = Core::File::construct(path); - if (!f->open(Core::IODevice::WriteOnly)) { - fprintf(stderr, "open: %s\n", f->error_string()); - exit(1); - } - f->write(value); - if (f->error() < 0) { - fprintf(stderr, "write: %s\n", f->error_string()); - exit(1); - } -} - -static int handle_show_all() -{ - Core::DirIterator di("/proc/sys", Core::DirIterator::SkipDots); - if (di.has_error()) { - fprintf(stderr, "DirIterator: %s\n", di.error_string()); - return 1; - } - - while (di.has_next()) { - String variable_name = di.next_path(); - printf("%s = %s\n", variable_name.characters(), read_var(variable_name).characters()); - } - return 0; -} - -static int handle_var(const String& var) -{ - String spec(var.characters(), Chomp); - auto parts = spec.split('='); - String variable_name = parts[0]; - bool is_write = parts.size() > 1; - - if (!is_write) { - printf("%s = %s\n", variable_name.characters(), read_var(variable_name).characters()); - return 0; - } - - printf("%s = %s", variable_name.characters(), read_var(variable_name).characters()); - write_var(variable_name, parts[1]); - printf(" -> %s\n", read_var(variable_name).characters()); - return 0; -} - -int main(int argc, char** argv) -{ - bool show_all = false; - const char* var = nullptr; - - Core::ArgsParser args_parser; - args_parser.set_general_help( - "Show or modify system-internal values. This requires root, and can crash your system."); - args_parser.add_option(show_all, "Show all variables", nullptr, 'a'); - args_parser.add_positional_argument(var, "Command (var[=value])", "command", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (var == nullptr) { - // Not supplied; assume `-a`. - show_all = true; - } - - if (show_all) { - // Ignore `var`, even if it was supplied. Just like the real procps does. - return handle_show_all(); - } - - return handle_var(var); -} diff --git a/Userland/tail.cpp b/Userland/tail.cpp deleted file mode 100644 index 60fb351f19..0000000000 --- a/Userland/tail.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DEFAULT_LINE_COUNT 10 - -static int tail_from_pos(Core::File& file, off_t startline, bool want_follow) -{ - if (!file.seek(startline + 1)) - return 1; - - while (true) { - const auto& b = file.read(4096); - if (b.is_empty()) { - if (!want_follow) { - break; - } else { - while (!file.can_read()) { - // FIXME: would be nice to have access to can_read_from_fd with an infinite timeout - usleep(100); - } - continue; - } - } - - if (write(STDOUT_FILENO, b.data(), b.size()) < 0) - return 1; - } - - return 0; -} - -static off_t find_seek_pos(Core::File& file, int wanted_lines) -{ - // Rather than reading the whole file, start at the end and work backwards, - // stopping when we've found the number of lines we want. - off_t pos = 0; - if (!file.seek(0, Core::IODevice::SeekMode::FromEndPosition, &pos)) { - fprintf(stderr, "Failed to find end of file: %s\n", file.error_string()); - return 1; - } - - off_t end = pos; - int lines = 0; - - // FIXME: Reading char-by-char is only OK if IODevice's read buffer - // is smart enough to not read char-by-char. Fix it there, or fix it here :) - for (; pos >= 0; pos--) { - file.seek(pos); - const auto& ch = file.read(1); - if (ch.is_empty()) { - // Presumably the file got truncated? - // Keep trying to read backwards... - } else { - if (*ch.data() == '\n' && (end - pos) > 1) { - lines++; - if (lines == wanted_lines) - break; - } - } - } - - return pos; -} - -int main(int argc, char* argv[]) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool follow = false; - int line_count = DEFAULT_LINE_COUNT; - const char* file = nullptr; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Print the end ('tail') of a file."); - args_parser.add_option(follow, "Output data as it is written to the file", "follow", 'f'); - args_parser.add_option(line_count, "Fetch the specified number of lines", "lines", 'n', "number"); - args_parser.add_positional_argument(file, "File path", "file"); - args_parser.parse(argc, argv); - - auto f = Core::File::construct(file); - if (!f->open(Core::IODevice::ReadOnly)) { - fprintf(stderr, "Error opening file %s: %s\n", file, strerror(errno)); - exit(1); - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - auto pos = find_seek_pos(*f, line_count); - return tail_from_pos(*f, pos, follow); -} diff --git a/Userland/tar.cpp b/Userland/tar.cpp deleted file mode 100644 index b828e3d8d1..0000000000 --- a/Userland/tar.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2020, Peter Elliott - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -constexpr size_t buffer_size = 4096; - -int main(int argc, char** argv) -{ - bool create = false; - bool extract = false; - bool list = false; - bool verbose = false; - bool gzip = false; - const char* archive_file = nullptr; - Vector paths; - - Core::ArgsParser args_parser; - args_parser.add_option(create, "Create archive", "create", 'c'); - args_parser.add_option(extract, "Extract archive", "extract", 'x'); - args_parser.add_option(list, "List contents", "list", 't'); - args_parser.add_option(verbose, "Print paths", "verbose", 'v'); - args_parser.add_option(gzip, "compress or uncompress file using gzip", "gzip", 'z'); - args_parser.add_option(archive_file, "Archive file", "file", 'f', "FILE"); - args_parser.add_positional_argument(paths, "Paths", "PATHS", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (create + extract + list != 1) { - warnln("exactly one of -c, -x, and -t can be used"); - return 1; - } - - if (list || extract) { - auto file = Core::File::standard_input(); - - if (archive_file) { - auto maybe_file = Core::File::open(archive_file, Core::IODevice::OpenMode::ReadOnly); - if (maybe_file.is_error()) { - warnln("Core::File::open: {}", maybe_file.error()); - return 1; - } - file = maybe_file.value(); - } - - Core::InputFileStream file_stream(file); - Compress::GzipDecompressor gzip_stream(file_stream); - - InputStream& file_input_stream = file_stream; - InputStream& gzip_input_stream = gzip_stream; - Tar::TarStream tar_stream((gzip) ? gzip_input_stream : file_input_stream); - if (!tar_stream.valid()) { - warnln("the provided file is not a well-formatted ustar file"); - return 1; - } - for (; !tar_stream.finished(); tar_stream.advance()) { - if (list || verbose) - outln("{}", tar_stream.header().file_name()); - - if (extract) { - Tar::TarFileStream file_stream = tar_stream.file_contents(); - - const Tar::Header& header = tar_stream.header(); - switch (header.type_flag()) { - case Tar::NormalFile: - case Tar::AlternateNormalFile: { - int fd = open(String(header.file_name()).characters(), O_CREAT | O_WRONLY, header.mode()); - if (fd < 0) { - perror("open"); - return 1; - } - - Array buffer; - size_t nread; - while ((nread = file_stream.read(buffer)) > 0) { - if (write(fd, buffer.data(), nread) < 0) { - perror("write"); - return 1; - } - } - close(fd); - break; - } - case Tar::Directory: { - if (mkdir(String(header.file_name()).characters(), header.mode())) { - perror("mkdir"); - return 1; - } - break; - } - default: - // FIXME: Implement other file types - ASSERT_NOT_REACHED(); - } - } - } - file_stream.close(); - return 0; - } - - // FIXME: Implement other operations. - ASSERT_NOT_REACHED(); - - return 0; -} diff --git a/Userland/tee.cpp b/Userland/tee.cpp deleted file mode 100644 index 92c58c7031..0000000000 --- a/Userland/tee.cpp +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -static Vector collect_fds(Vector paths, bool append, bool* err) -{ - int oflag; - mode_t mode; - if (append) { - oflag = O_APPEND; - mode = 0; - } else { - oflag = O_CREAT | O_WRONLY | O_TRUNC; - mode = S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR; - } - - Vector fds; - for (const char* path : paths) { - int fd = open(path, oflag, mode); - if (fd < 0) { - perror("failed to open file for writing"); - *err = true; - } else { - fds.append(fd); - } - } - fds.append(STDOUT_FILENO); - return fds; -} - -static void copy_stdin(Vector& fds, bool* err) -{ - for (;;) { - char buf[4096]; - ssize_t nread = read(STDIN_FILENO, buf, sizeof(buf)); - if (nread == 0) - break; - if (nread < 0) { - perror("read() error"); - *err = true; - // a failure to read from stdin should lead to an early exit - return; - } - - Vector broken_fds; - for (size_t i = 0; i < fds.size(); ++i) { - auto fd = fds.at(i); - int twrite = 0; - while (twrite != nread) { - ssize_t nwrite = write(fd, buf + twrite, nread - twrite); - if (nwrite < 0) { - if (errno == EINTR) { - continue; - } else { - perror("write() failed"); - *err = true; - broken_fds.append(fd); - // write failures to a successfully opened fd shall - // prevent further writes, but shall not block writes - // to the other open fds - break; - } - } else { - twrite += nwrite; - } - } - } - - // remove any fds which we can no longer write to for subsequent copies - for (auto to_remove : broken_fds) - fds.remove_first_matching([&](int fd) { return to_remove == fd; }); - } -} - -static void close_fds(Vector& fds) -{ - for (int fd : fds) { - int closed = close(fd); - if (closed < 0) { - perror("failed to close output file"); - } - } -} - -static void int_handler(int) -{ - // pass -} - -int main(int argc, char** argv) -{ - bool append = false; - bool ignore_interrupts = false; - Vector paths; - - Core::ArgsParser args_parser; - args_parser.add_option(append, "Append, don't overwrite", "append", 'a'); - args_parser.add_option(ignore_interrupts, "Ignore SIGINT", "ignore-interrupts", 'i'); - args_parser.add_positional_argument(paths, "Files to copy stdin to", "file", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (ignore_interrupts) { - if (signal(SIGINT, int_handler) == SIG_ERR) - perror("failed to install SIGINT handler"); - } - - bool err_open = false; - bool err_write = false; - auto fds = collect_fds(paths, append, &err_open); - copy_stdin(fds, &err_write); - close_fds(fds); - - return (err_open || err_write) ? 1 : 0; -} diff --git a/Userland/test-bindtodevice.cpp b/Userland/test-bindtodevice.cpp deleted file mode 100644 index 8213e5c0ce..0000000000 --- a/Userland/test-bindtodevice.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -static void test_invalid(int); -static void test_no_route(int); -static void test_valid(int); -static void test_send(int); - -static void test(AK::Function test_fn) -{ - - int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); - if (fd < 0) { - perror("socket"); - return; - } - - test_fn(fd); - - // be a responsible boi - close(fd); -} - -auto main() -> int -{ - test(test_invalid); - test(test_valid); - test(test_no_route); - test(test_send); -} - -void test_invalid(int fd) -{ - // bind to an interface that does not exist - char buf[IFNAMSIZ]; - socklen_t buflen = IFNAMSIZ; - memcpy(buf, "foodev", 7); - - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) { - perror("setsockopt(SO_BINDTODEVICE) :: invalid (Should fail with ENODEV)"); - puts("PASS invalid"); - } else { - puts("FAIL invalid"); - } -} - -void test_valid(int fd) -{ - // bind to an interface that exists - char buf[IFNAMSIZ]; - socklen_t buflen = IFNAMSIZ; - memcpy(buf, "loop0", 6); - - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) { - perror("setsockopt(SO_BINDTODEVICE) :: valid"); - puts("FAIL valid"); - } else { - puts("PASS valid"); - } -} - -void test_no_route(int fd) -{ - // bind to an interface that cannot deliver - char buf[IFNAMSIZ]; - socklen_t buflen = IFNAMSIZ; - memcpy(buf, "loop0", 6); - - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) { - perror("setsockopt(SO_BINDTODEVICE) :: no_route"); - puts("FAIL no_route"); - return; - } - sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); - - sin.sin_addr.s_addr = IPv4Address { 10, 0, 2, 15 }.to_u32(); - sin.sin_port = 8080; - sin.sin_family = AF_INET; - - if (bind(fd, (sockaddr*)&sin, sizeof(sin)) < 0) { - perror("bind() :: no_route"); - puts("FAIL no_route"); - return; - } - - if (sendto(fd, "TEST", 4, 0, (sockaddr*)&sin, sizeof(sin)) < 0) { - perror("sendto() :: no_route (Should fail with EHOSTUNREACH)"); - puts("PASS no_route"); - } else - puts("FAIL no_route"); -} - -void test_send(int fd) -{ - // bind to an interface that cannot deliver - char buf[IFNAMSIZ]; - socklen_t buflen = IFNAMSIZ; - memcpy(buf, "e1k0", 5); - - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, buf, buflen) < 0) { - perror("setsockopt(SO_BINDTODEVICE) :: send"); - puts("FAIL send"); - return; - } - sockaddr_in sin; - memset(&sin, 0, sizeof(sin)); - - sin.sin_addr.s_addr = IPv4Address { 10, 0, 2, 15 }.to_u32(); - sin.sin_port = 8080; - sin.sin_family = AF_INET; - - if (bind(fd, (sockaddr*)&sin, sizeof(sin)) < 0) { - perror("bind() :: send"); - puts("FAIL send"); - return; - } - - if (sendto(fd, "TEST", 4, 0, (sockaddr*)&sin, sizeof(sin)) < 0) { - perror("sendto() :: send"); - puts("FAIL send"); - return; - } - puts("PASS send"); -} diff --git a/Userland/test-compress.cpp b/Userland/test-compress.cpp deleted file mode 100644 index 7e1dbb0d63..0000000000 --- a/Userland/test-compress.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include - -#include -#include -#include -#include -#include - -TEST_CASE(canonical_code_simple) -{ - const Array code { - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, - 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05 - }; - const Array input { - 0x00, 0x42, 0x84, 0xa9, 0xb0, 0x15 - }; - const Array output { - 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x08, 0x0d, 0x15 - }; - - const auto huffman = Compress::CanonicalCode::from_bytes(code).value(); - auto memory_stream = InputMemoryStream { input }; - auto bit_stream = InputBitStream { memory_stream }; - - for (size_t idx = 0; idx < 9; ++idx) - EXPECT_EQ(huffman.read_symbol(bit_stream), output[idx]); -} - -TEST_CASE(canonical_code_complex) -{ - const Array code { - 0x03, 0x02, 0x03, 0x03, 0x02, 0x03 - }; - const Array input { - 0xa1, 0xf3, 0xa1, 0xf3 - }; - const Array output { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05 - }; - - const auto huffman = Compress::CanonicalCode::from_bytes(code).value(); - auto memory_stream = InputMemoryStream { input }; - auto bit_stream = InputBitStream { memory_stream }; - - for (size_t idx = 0; idx < 12; ++idx) - EXPECT_EQ(huffman.read_symbol(bit_stream), output[idx]); -} - -TEST_CASE(deflate_decompress_compressed_block) -{ - const Array compressed { - 0x0B, 0xC9, 0xC8, 0x2C, 0x56, 0x00, 0xA2, 0x44, 0x85, 0xE2, 0xCC, 0xDC, - 0x82, 0x9C, 0x54, 0x85, 0x92, 0xD4, 0x8A, 0x12, 0x85, 0xB4, 0x4C, 0x20, - 0xCB, 0x4A, 0x13, 0x00 - }; - - const u8 uncompressed[] = "This is a simple text file :)"; - - const auto decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == ReadonlyBytes({ uncompressed, sizeof(uncompressed) - 1 })); -} - -TEST_CASE(deflate_decompress_uncompressed_block) -{ - const Array compressed { - 0x01, 0x0d, 0x00, 0xf2, 0xff, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, - 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21 - }; - - const u8 uncompressed[] = "Hello, World!"; - - const auto decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); -} - -TEST_CASE(deflate_decompress_multiple_blocks) -{ - const Array compressed { - 0x00, 0x1f, 0x00, 0xe0, 0xff, 0x54, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, - 0x73, 0x74, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x69, 0x73, 0x20, - 0x75, 0x6e, 0x63, 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, - 0x53, 0x48, 0xcc, 0x4b, 0x51, 0x28, 0xc9, 0x48, 0x55, 0x28, 0x4e, 0x4d, - 0xce, 0x07, 0x32, 0x93, 0x72, 0xf2, 0x93, 0xb3, 0x15, 0x32, 0x8b, 0x15, - 0x92, 0xf3, 0x73, 0x0b, 0x8a, 0x52, 0x8b, 0x8b, 0x53, 0x53, 0xf4, 0x00 - }; - - const u8 uncompressed[] = "The first block is uncompressed and the second block is compressed."; - - const auto decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); -} - -TEST_CASE(deflate_decompress_zeroes) -{ - const Array compressed { - 0xed, 0xc1, 0x01, 0x0d, 0x00, 0x00, 0x00, 0xc2, 0xa0, 0xf7, 0x4f, 0x6d, - 0x0f, 0x07, 0x14, 0x00, 0x00, 0x00, 0xf0, 0x6e - }; - - const Array uncompressed { 0 }; - - const auto decompressed = Compress::DeflateDecompressor::decompress_all(compressed); - EXPECT(uncompressed == decompressed.value().bytes()); -} - -TEST_CASE(zlib_decompress_simple) -{ - const Array compressed { - 0x78, 0x01, 0x01, 0x1D, 0x00, 0xE2, 0xFF, 0x54, 0x68, 0x69, 0x73, 0x20, - 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6D, 0x70, 0x6C, 0x65, 0x20, - 0x74, 0x65, 0x78, 0x74, 0x20, 0x66, 0x69, 0x6C, 0x65, 0x20, 0x3A, 0x29, - 0x99, 0x5E, 0x09, 0xE8 - }; - - const u8 uncompressed[] = "This is a simple text file :)"; - - const auto decompressed = Compress::Zlib::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); -} - -TEST_CASE(gzip_decompress_simple) -{ - const Array compressed { - 0x1f, 0x8b, 0x08, 0x00, 0x77, 0xff, 0x47, 0x5f, 0x02, 0xff, 0x2b, 0xcf, - 0x2f, 0x4a, 0x31, 0x54, 0x48, 0x4c, 0x4a, 0x56, 0x28, 0x07, 0xb2, 0x8c, - 0x00, 0xc2, 0x1d, 0x22, 0x15, 0x0f, 0x00, 0x00, 0x00 - }; - - const u8 uncompressed[] = "word1 abc word2"; - - const auto decompressed = Compress::GzipDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); -} - -TEST_CASE(gzip_decompress_multiple_members) -{ - const Array compressed { - 0x1f, 0x8b, 0x08, 0x00, 0xe0, 0x03, 0x48, 0x5f, 0x02, 0xff, 0x4b, 0x4c, - 0x4a, 0x4e, 0x4c, 0x4a, 0x06, 0x00, 0x4c, 0x99, 0x6e, 0x72, 0x06, 0x00, - 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, 0xe0, 0x03, 0x48, 0x5f, 0x02, 0xff, - 0x4b, 0x4c, 0x4a, 0x4e, 0x4c, 0x4a, 0x06, 0x00, 0x4c, 0x99, 0x6e, 0x72, - 0x06, 0x00, 0x00, 0x00 - }; - - const u8 uncompressed[] = "abcabcabcabc"; - - const auto decompressed = Compress::GzipDecompressor::decompress_all(compressed); - EXPECT(decompressed.value().bytes() == (ReadonlyBytes { uncompressed, sizeof(uncompressed) - 1 })); -} - -TEST_CASE(gzip_decompress_zeroes) -{ - const Array compressed { - 0x1f, 0x8b, 0x08, 0x00, 0x6e, 0x7a, 0x4b, 0x5f, 0x02, 0xff, 0xed, 0xc1, - 0x31, 0x01, 0x00, 0x00, 0x00, 0xc2, 0xa0, 0xf5, 0x4f, 0xed, 0x61, 0x0d, - 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xcd, 0xcd, 0xe8, - 0x7e, 0x00, 0x00, 0x02, 0x00 - }; - - const Array uncompressed = { 0 }; - - const auto decompressed = Compress::GzipDecompressor::decompress_all(compressed); - EXPECT(uncompressed == decompressed.value().bytes()); -} - -TEST_CASE(gzip_decompress_repeat_around_buffer) -{ - const Array compressed { - 0x1f, 0x8b, 0x08, 0x00, 0xc6, 0x74, 0x53, 0x5f, 0x02, 0xff, 0xed, 0xc1, - 0x01, 0x0d, 0x00, 0x00, 0x0c, 0x02, 0xa0, 0xdb, 0xbf, 0xf4, 0x37, 0x6b, - 0x08, 0x24, 0xdb, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xca, - 0xb8, 0x07, 0xcd, 0xe5, 0x38, 0xfa, 0x00, 0x80, 0x00, 0x00 - }; - - Array uncompressed; - uncompressed.span().slice(0x0000, 0x0100).fill(1); - uncompressed.span().slice(0x0100, 0x7e00).fill(0); - uncompressed.span().slice(0x7f00, 0x0100).fill(1); - - const auto decompressed = Compress::GzipDecompressor::decompress_all(compressed); - EXPECT(uncompressed == decompressed.value().bytes()); -} - -TEST_MAIN(Compress) diff --git a/Userland/test-crypto.cpp b/Userland/test-crypto.cpp deleted file mode 100644 index 55d9c69611..0000000000 --- a/Userland/test-crypto.cpp +++ /dev/null @@ -1,2736 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static const char* secret_key = "WellHelloFreinds"; -static const char* suite = nullptr; -static const char* filename = nullptr; -static const char* server = nullptr; -static const char* ca_certs_file = "/etc/ca_certs.ini"; -static int key_bits = 128; -static bool binary = false; -static bool interactive = false; -static bool run_tests = false; -static int port = 443; -static bool in_ci = false; - -static struct timeval start_time { - 0, 0 -}; -static bool g_some_test_failed = false; -static bool encrypting = true; - -constexpr const char* DEFAULT_DIGEST_SUITE { "HMAC-SHA256" }; -constexpr const char* DEFAULT_CHECKSUM_SUITE { "CRC32" }; -constexpr const char* DEFAULT_HASH_SUITE { "SHA256" }; -constexpr const char* DEFAULT_CIPHER_SUITE { "AES_CBC" }; -constexpr const char* DEFAULT_SERVER { "www.google.com" }; - -static Vector s_root_ca_certificates; - -// listAllTests -// Cipher -static int aes_cbc_tests(); -static int aes_ctr_tests(); -static int aes_gcm_tests(); - -// Hash -static int md5_tests(); -static int sha1_tests(); -static int sha256_tests(); -static int sha512_tests(); - -// Authentication -static int hmac_md5_tests(); -static int hmac_sha256_tests(); -static int hmac_sha512_tests(); -static int hmac_sha1_tests(); -static int ghash_tests(); - -// Public-Key -static int rsa_tests(); - -// TLS -static int tls_tests(); - -// Big Integer -static int bigint_tests(); - -// Checksum -static int adler32_tests(); -static int crc32_tests(); - -// stop listing tests - -static void print_buffer(ReadonlyBytes buffer, int split) -{ - for (size_t i = 0; i < buffer.size(); ++i) { - if (split > 0) { - if (i % split == 0 && i) { - printf(" "); - for (size_t j = i - split; j < i; ++j) { - auto ch = buffer[j]; - printf("%c", ch >= 32 && ch <= 127 ? ch : '.'); // silly hack - } - puts(""); - } - } - printf("%02x ", buffer[i]); - } - puts(""); -} - -static Core::EventLoop g_loop; - -static int run(Function fn) -{ - if (interactive) { - auto editor = Line::Editor::construct(); - editor->initialize(); - for (;;) { - auto line_result = editor->get_line("> "); - - if (line_result.is_error()) - break; - auto& line = line_result.value(); - - if (line == ".wait") { - g_loop.exec(); - } else { - fn(line.characters(), line.length()); - g_loop.pump(); - } - } - } else { - if (filename == nullptr) { - puts("must specify a file name"); - return 1; - } - if (!Core::File::exists(filename)) { - puts("File does not exist"); - return 1; - } - auto file = Core::File::open(filename, Core::IODevice::OpenMode::ReadOnly); - if (file.is_error()) { - printf("That's a weird file man...\n"); - return 1; - } - auto buffer = file.value()->read_all(); - fn((const char*)buffer.data(), buffer.size()); - g_loop.exec(); - } - return 0; -} - -static void tls(const char* message, size_t len) -{ - static RefPtr tls; - static ByteBuffer write {}; - if (!tls) { - tls = TLS::TLSv12::construct(nullptr); - tls->set_root_certificates(s_root_ca_certificates); - tls->connect(server ?: DEFAULT_SERVER, port); - tls->on_tls_ready_to_read = [](auto& tls) { - auto buffer = tls.read(); - if (buffer.has_value()) - fprintf(stdout, "%.*s", (int)buffer.value().size(), buffer.value().data()); - }; - tls->on_tls_ready_to_write = [&](auto&) { - if (write.size()) { - tls->write(write); - write.clear(); - } - }; - tls->on_tls_error = [&](auto) { - g_loop.quit(1); - }; - tls->on_tls_finished = [&]() { - g_loop.quit(0); - }; - } - write.append(message, len); - write.append("\r\n", 2); -} - -static void aes_cbc(const char* message, size_t len) -{ - ReadonlyBytes buffer { message, len }; - // FIXME: Take iv as an optional parameter - auto iv = ByteBuffer::create_zeroed(Crypto::Cipher::AESCipher::block_size()); - - if (encrypting) { - Crypto::Cipher::AESCipher::CBCMode cipher( - StringView(secret_key).bytes(), - key_bits, - Crypto::Cipher::Intent::Encryption); - - auto enc = cipher.create_aligned_buffer(buffer.size()); - auto enc_span = enc.bytes(); - cipher.encrypt(buffer, enc_span, iv); - - if (binary) - printf("%.*s", (int)enc_span.size(), enc_span.data()); - else - print_buffer(enc_span, Crypto::Cipher::AESCipher::block_size()); - } else { - Crypto::Cipher::AESCipher::CBCMode cipher( - StringView(secret_key).bytes(), - key_bits, - Crypto::Cipher::Intent::Decryption); - auto dec = cipher.create_aligned_buffer(buffer.size()); - auto dec_span = dec.bytes(); - cipher.decrypt(buffer, dec_span, iv); - printf("%.*s\n", (int)dec_span.size(), dec_span.data()); - } -} - -static void adler32(const char* message, size_t len) -{ - auto checksum = Crypto::Checksum::Adler32({ (const u8*)message, len }); - printf("%#10X\n", checksum.digest()); -} - -static void crc32(const char* message, size_t len) -{ - auto checksum = Crypto::Checksum::CRC32({ (const u8*)message, len }); - printf("%#10X\n", checksum.digest()); -} - -static void md5(const char* message, size_t len) -{ - auto digest = Crypto::Hash::MD5::hash((const u8*)message, len); - if (binary) - printf("%.*s", (int)Crypto::Hash::MD5::digest_size(), digest.data); - else - print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); -} - -static void hmac_md5(const char* message, size_t len) -{ - Crypto::Authentication::HMAC hmac(secret_key); - auto mac = hmac.process((const u8*)message, len); - if (binary) - printf("%.*s", (int)hmac.digest_size(), mac.data); - else - print_buffer({ mac.data, hmac.digest_size() }, -1); -} - -static void sha1(const char* message, size_t len) -{ - auto digest = Crypto::Hash::SHA1::hash((const u8*)message, len); - if (binary) - printf("%.*s", (int)Crypto::Hash::SHA1::digest_size(), digest.data); - else - print_buffer({ digest.data, Crypto::Hash::SHA1::digest_size() }, -1); -} - -static void sha256(const char* message, size_t len) -{ - auto digest = Crypto::Hash::SHA256::hash((const u8*)message, len); - if (binary) - printf("%.*s", (int)Crypto::Hash::SHA256::digest_size(), digest.data); - else - print_buffer({ digest.data, Crypto::Hash::SHA256::digest_size() }, -1); -} - -static void hmac_sha256(const char* message, size_t len) -{ - Crypto::Authentication::HMAC hmac(secret_key); - auto mac = hmac.process((const u8*)message, len); - if (binary) - printf("%.*s", (int)hmac.digest_size(), mac.data); - else - print_buffer({ mac.data, hmac.digest_size() }, -1); -} - -static void sha512(const char* message, size_t len) -{ - auto digest = Crypto::Hash::SHA512::hash((const u8*)message, len); - if (binary) - printf("%.*s", (int)Crypto::Hash::SHA512::digest_size(), digest.data); - else - print_buffer({ digest.data, Crypto::Hash::SHA512::digest_size() }, -1); -} - -static void hmac_sha512(const char* message, size_t len) -{ - Crypto::Authentication::HMAC hmac(secret_key); - auto mac = hmac.process((const u8*)message, len); - if (binary) - printf("%.*s", (int)hmac.digest_size(), mac.data); - else - print_buffer({ mac.data, hmac.digest_size() }, -1); -} - -auto main(int argc, char** argv) -> int -{ - const char* mode = nullptr; - Core::ArgsParser parser; - parser.add_positional_argument(mode, "mode to operate in ('list' to see modes and descriptions)", "mode"); - - parser.add_option(secret_key, "Set the secret key (default key is 'WellHelloFriends')", "secret-key", 'k', "secret key"); - parser.add_option(key_bits, "Size of the key", "key-bits", 'b', "key-bits"); - parser.add_option(filename, "Read from file", "file", 'f', "from file"); - parser.add_option(binary, "Force binary output", "force-binary", 0); - parser.add_option(interactive, "REPL mode", "interactive", 'i'); - parser.add_option(run_tests, "Run tests for the specified suite", "tests", 't'); - parser.add_option(suite, "Set the suite used", "suite-name", 'n', "suite name"); - parser.add_option(server, "Set the server to talk to (only for `tls')", "server-address", 's', "server-address"); - parser.add_option(port, "Set the port to talk to (only for `tls')", "port", 'p', "port"); - parser.add_option(ca_certs_file, "INI file to read root CA certificates from (only for `tls')", "ca-certs-file", 0, "file"); - parser.add_option(in_ci, "CI Test mode", "ci-mode", 'c'); - parser.parse(argc, argv); - - StringView mode_sv { mode }; - if (mode_sv == "list") { - puts("test-crypto modes"); - puts("\tdigest - Access digest (authentication) functions"); - puts("\thash - Access hash functions"); - puts("\tchecksum - Access checksum functions"); - puts("\tencrypt -- Access encryption functions"); - puts("\tdecrypt -- Access decryption functions"); - puts("\ttls -- Connect to a peer over TLS 1.2"); - puts("\tlist -- List all known modes"); - puts("these modes only contain tests"); - puts("\ttest -- Run every test suite"); - puts("\tbigint -- Run big integer test suite"); - puts("\tpk -- Run Public-key system tests"); - return 0; - } - - if (mode_sv == "hash") { - if (suite == nullptr) - suite = DEFAULT_HASH_SUITE; - StringView suite_sv { suite }; - - if (suite_sv == "MD5") { - if (run_tests) - return md5_tests(); - return run(md5); - } - if (suite_sv == "SHA1") { - if (run_tests) - return sha1_tests(); - return run(sha1); - } - if (suite_sv == "SHA256") { - if (run_tests) - return sha256_tests(); - return run(sha256); - } - if (suite_sv == "SHA512") { - if (run_tests) - return sha512_tests(); - return run(sha512); - } - printf("unknown hash function '%s'\n", suite); - return 1; - } - if (mode_sv == "checksum") { - if (suite == nullptr) - suite = DEFAULT_CHECKSUM_SUITE; - StringView suite_sv { suite }; - - if (suite_sv == "CRC32") { - if (run_tests) - return crc32_tests(); - return run(crc32); - } - if (suite_sv == "Adler32") { - if (run_tests) - return adler32_tests(); - return run(adler32); - } - printf("unknown checksum function '%s'\n", suite); - return 1; - } - if (mode_sv == "digest") { - if (suite == nullptr) - suite = DEFAULT_DIGEST_SUITE; - StringView suite_sv { suite }; - - if (suite_sv == "HMAC-MD5") { - if (run_tests) - return hmac_md5_tests(); - return run(hmac_md5); - } - if (suite_sv == "HMAC-SHA256") { - if (run_tests) - return hmac_sha256_tests(); - return run(hmac_sha256); - } - if (suite_sv == "HMAC-SHA512") { - if (run_tests) - return hmac_sha512_tests(); - return run(hmac_sha512); - } - if (suite_sv == "HMAC-SHA1") { - if (run_tests) - return hmac_sha1_tests(); - } - if (suite_sv == "GHash") { - if (run_tests) - return ghash_tests(); - } - printf("unknown hash function '%s'\n", suite); - return 1; - } - if (mode_sv == "pk") { - return rsa_tests(); - } - if (mode_sv == "bigint") { - return bigint_tests(); - } - if (mode_sv == "tls") { - if (!Core::File::exists(ca_certs_file)) { - warnln("Nonexistent CA certs file '{}'", ca_certs_file); - return 1; - } - auto config = Core::ConfigFile::open(ca_certs_file); - for (auto& entity : config->groups()) { - Certificate cert; - cert.subject = entity; - cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity); - cert.country = config->read_entry(entity, "country"); - s_root_ca_certificates.append(move(cert)); - } - if (run_tests) - return tls_tests(); - return run(tls); - } - if (mode_sv == "test") { - encrypting = true; - aes_cbc_tests(); - aes_ctr_tests(); - aes_gcm_tests(); - - encrypting = false; - aes_cbc_tests(); - aes_ctr_tests(); - aes_gcm_tests(); - - md5_tests(); - sha1_tests(); - sha256_tests(); - sha512_tests(); - - hmac_md5_tests(); - hmac_sha256_tests(); - hmac_sha512_tests(); - hmac_sha1_tests(); - - ghash_tests(); - - rsa_tests(); - - if (!in_ci) { - // Do not run these in CI to avoid tests with variables outside our control. - if (!Core::File::exists(ca_certs_file)) { - warnln("Nonexistent CA certs file '{}'", ca_certs_file); - return 1; - } - auto config = Core::ConfigFile::open(ca_certs_file); - for (auto& entity : config->groups()) { - Certificate cert; - cert.subject = entity; - cert.issuer_subject = config->read_entry(entity, "issuer_subject", entity); - cert.country = config->read_entry(entity, "country"); - s_root_ca_certificates.append(move(cert)); - } - tls_tests(); - } - - bigint_tests(); - - return g_some_test_failed ? 1 : 0; - } - encrypting = mode_sv == "encrypt"; - if (encrypting || mode_sv == "decrypt") { - if (suite == nullptr) - suite = DEFAULT_CIPHER_SUITE; - StringView suite_sv { suite }; - - if (StringView(suite) == "AES_CBC") { - if (run_tests) - return aes_cbc_tests(); - - if (!Crypto::Cipher::AESCipher::KeyType::is_valid_key_size(key_bits)) { - printf("Invalid key size for AES: %d\n", key_bits); - return 1; - } - if (strlen(secret_key) != (size_t)key_bits / 8) { - printf("Key must be exactly %d bytes long\n", key_bits / 8); - return 1; - } - return run(aes_cbc); - } - if (StringView(suite) == "AES_GCM") { - if (run_tests) - return aes_gcm_tests(); - - return 1; - } else { - printf("Unknown cipher suite '%s'\n", suite); - return 1; - } - } - printf("Unknown mode '%s', check out the list of modes\n", mode); - return 1; -} - -#define I_TEST(thing) \ - { \ - printf("Testing " #thing "... "); \ - fflush(stdout); \ - gettimeofday(&start_time, nullptr); \ - } -#define PASS \ - { \ - struct timeval end_time { \ - 0, 0 \ - }; \ - gettimeofday(&end_time, nullptr); \ - time_t interval_s = end_time.tv_sec - start_time.tv_sec; \ - suseconds_t interval_us = end_time.tv_usec; \ - if (interval_us < start_time.tv_usec) { \ - interval_s -= 1; \ - interval_us += 1000000; \ - } \ - interval_us -= start_time.tv_usec; \ - printf("PASS %llds %lldus\n", (long long)interval_s, (long long)interval_us); \ - } -#define FAIL(reason) \ - do { \ - printf("FAIL: " #reason "\n"); \ - g_some_test_failed = true; \ - } while (0) - -static ByteBuffer operator""_b(const char* string, size_t length) -{ - return ByteBuffer::copy(string, length); -} - -// tests go after here -// please be reasonable with orders kthx -static void aes_cbc_test_name(); -static void aes_cbc_test_encrypt(); -static void aes_cbc_test_decrypt(); -static void aes_ctr_test_name(); -static void aes_ctr_test_encrypt(); -static void aes_ctr_test_decrypt(); -static void aes_gcm_test_name(); -static void aes_gcm_test_encrypt(); -static void aes_gcm_test_decrypt(); - -static void md5_test_name(); -static void md5_test_hash(); -static void md5_test_consecutive_updates(); - -static void sha1_test_name(); -static void sha1_test_hash(); - -static void sha256_test_name(); -static void sha256_test_hash(); - -static void sha512_test_name(); -static void sha512_test_hash(); - -static void ghash_test_name(); -static void ghash_test_process(); - -static void hmac_md5_test_name(); -static void hmac_md5_test_process(); - -static void hmac_sha256_test_name(); -static void hmac_sha256_test_process(); - -static void hmac_sha512_test_name(); -static void hmac_sha512_test_process(); - -static void hmac_sha1_test_name(); -static void hmac_sha1_test_process(); - -static void rsa_test_encrypt(); -static void rsa_test_der_parse(); -static void rsa_test_encrypt_decrypt(); -static void rsa_emsa_pss_test_create(); -static void bigint_test_number_theory(); // FIXME: we should really move these num theory stuff out - -static void tls_test_client_hello(); - -static void bigint_test_fibo500(); -static void bigint_addition_edgecases(); -static void bigint_subtraction(); -static void bigint_multiplication(); -static void bigint_division(); -static void bigint_base10(); -static void bigint_import_export(); -static void bigint_bitwise(); - -static void bigint_test_signed_fibo500(); -static void bigint_signed_addition_edgecases(); -static void bigint_signed_subtraction(); -static void bigint_signed_multiplication(); -static void bigint_signed_division(); -static void bigint_signed_base10(); -static void bigint_signed_import_export(); -static void bigint_signed_bitwise(); - -static int aes_cbc_tests() -{ - aes_cbc_test_name(); - if (encrypting) { - aes_cbc_test_encrypt(); - } else { - aes_cbc_test_decrypt(); - } - - return g_some_test_failed ? 1 : 0; -} - -static void aes_cbc_test_name() -{ - I_TEST((AES CBC class name)); - Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Encryption); - if (cipher.class_name() != "AES_CBC") - FAIL(Invalid class name); - else - PASS; -} - -static void aes_cbc_test_encrypt() -{ - auto test_it = [](auto& cipher, auto& result) { - auto in = "This is a test! This is another test!"_b; - auto out = cipher.create_aligned_buffer(in.size()); - auto iv = ByteBuffer::create_zeroed(Crypto::Cipher::AESCipher::block_size()); - auto out_span = out.bytes(); - cipher.encrypt(in, out_span, iv); - if (out.size() != sizeof(result)) - FAIL(size mismatch); - else if (memcmp(out_span.data(), result, out_span.size()) != 0) { - FAIL(invalid data); - print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); - } else - PASS; - }; - { - I_TEST((AES CBC with 128 bit key | Encrypt)) - u8 result[] { - 0xb8, 0x06, 0x7c, 0xf2, 0xa9, 0x56, 0x63, 0x58, 0x2d, 0x5c, 0xa1, 0x4b, 0xc5, 0xe3, 0x08, - 0xcf, 0xb5, 0x93, 0xfb, 0x67, 0xb6, 0xf7, 0xaf, 0x45, 0x34, 0x64, 0x70, 0x9e, 0xc9, 0x1a, - 0x8b, 0xd3, 0x70, 0x45, 0xf0, 0x79, 0x65, 0xca, 0xb9, 0x03, 0x88, 0x72, 0x1c, 0xdd, 0xab, - 0x45, 0x6b, 0x1c - }; - Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Encryption); - test_it(cipher, result); - } - { - I_TEST((AES CBC with 192 bit key | Encrypt)) - u8 result[] { - 0xae, 0xd2, 0x70, 0xc4, 0x9c, 0xaa, 0x83, 0x33, 0xd3, 0xd3, 0xac, 0x11, 0x65, 0x35, 0xf7, - 0x19, 0x48, 0x7c, 0x7a, 0x8a, 0x95, 0x64, 0xe7, 0xc6, 0x0a, 0xdf, 0x10, 0x06, 0xdc, 0x90, - 0x68, 0x51, 0x09, 0xd7, 0x3b, 0x48, 0x1b, 0x8a, 0xd3, 0x50, 0x09, 0xba, 0xfc, 0xde, 0x11, - 0xe0, 0x3f, 0xcb - }; - Crypto::Cipher::AESCipher::CBCMode cipher("Well Hello Friends! whf!"_b, 192, Crypto::Cipher::Intent::Encryption); - test_it(cipher, result); - } - { - I_TEST((AES CBC with 256 bit key | Encrypt)) - u8 result[] { - 0x0a, 0x44, 0x4d, 0x62, 0x9e, 0x8b, 0xd8, 0x11, 0x80, 0x48, 0x2a, 0x32, 0x53, 0x61, 0xe7, - 0x59, 0x62, 0x55, 0x9e, 0xf4, 0xe6, 0xad, 0xea, 0xc5, 0x0b, 0xf6, 0xbc, 0x6a, 0xcb, 0x9c, - 0x47, 0x9f, 0xc2, 0x21, 0xe6, 0x19, 0x62, 0xc3, 0x75, 0xca, 0xab, 0x2d, 0x18, 0xa1, 0x54, - 0xd1, 0x41, 0xe6 - }; - Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriendsWellHelloFriends"_b, 256, Crypto::Cipher::Intent::Encryption); - test_it(cipher, result); - } - { - I_TEST((AES CBC with 256 bit key | Encrypt with unsigned key)) - u8 result[] { - 0x18, 0x71, 0x80, 0x4c, 0x28, 0x07, 0x55, 0x3c, 0x05, 0x33, 0x36, 0x3f, 0x19, 0x38, 0x5c, - 0xbe, 0xf8, 0xb8, 0x0e, 0x0e, 0x66, 0x67, 0x63, 0x9c, 0xbf, 0x73, 0xcd, 0x82, 0xf9, 0xcb, - 0x9d, 0x81, 0x56, 0xc6, 0x75, 0x14, 0x8b, 0x79, 0x60, 0xb0, 0xdf, 0xaa, 0x2c, 0x2b, 0xd4, - 0xd6, 0xa0, 0x46 - }; - u8 key[] { 0x0a, 0x8c, 0x5b, 0x0d, 0x8a, 0x68, 0x43, 0xf7, 0xaf, 0xc0, 0xe3, 0x4e, 0x4b, 0x43, 0xaa, 0x28, 0x69, 0x9b, 0x6f, 0xe7, 0x24, 0x82, 0x1c, 0x71, 0x86, 0xf6, 0x2b, 0x87, 0xd6, 0x8b, 0x8f, 0xf1 }; - Crypto::Cipher::AESCipher::CBCMode cipher(ReadonlyBytes { key, sizeof(key) }, 256, Crypto::Cipher::Intent::Encryption); - test_it(cipher, result); - } - // TODO: Test non-CMS padding options -} -static void aes_cbc_test_decrypt() -{ - auto test_it = [](auto& cipher, auto& result, auto result_len) { - auto true_value = "This is a test! This is another test!"; - auto in = ByteBuffer::copy(result, result_len); - auto out = cipher.create_aligned_buffer(in.size()); - auto iv = ByteBuffer::create_zeroed(Crypto::Cipher::AESCipher::block_size()); - auto out_span = out.bytes(); - cipher.decrypt(in, out_span, iv); - if (out_span.size() != strlen(true_value)) { - FAIL(size mismatch); - printf("Expected %zu bytes but got %zu\n", strlen(true_value), out_span.size()); - } else if (memcmp(out_span.data(), true_value, strlen(true_value)) != 0) { - FAIL(invalid data); - print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); - } else - PASS; - }; - { - I_TEST((AES CBC with 128 bit key | Decrypt)) - u8 result[] { - 0xb8, 0x06, 0x7c, 0xf2, 0xa9, 0x56, 0x63, 0x58, 0x2d, 0x5c, 0xa1, 0x4b, 0xc5, 0xe3, 0x08, - 0xcf, 0xb5, 0x93, 0xfb, 0x67, 0xb6, 0xf7, 0xaf, 0x45, 0x34, 0x64, 0x70, 0x9e, 0xc9, 0x1a, - 0x8b, 0xd3, 0x70, 0x45, 0xf0, 0x79, 0x65, 0xca, 0xb9, 0x03, 0x88, 0x72, 0x1c, 0xdd, 0xab, - 0x45, 0x6b, 0x1c - }; - Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Decryption); - test_it(cipher, result, 48); - } - { - I_TEST((AES CBC with 192 bit key | Decrypt)) - u8 result[] { - 0xae, 0xd2, 0x70, 0xc4, 0x9c, 0xaa, 0x83, 0x33, 0xd3, 0xd3, 0xac, 0x11, 0x65, 0x35, 0xf7, - 0x19, 0x48, 0x7c, 0x7a, 0x8a, 0x95, 0x64, 0xe7, 0xc6, 0x0a, 0xdf, 0x10, 0x06, 0xdc, 0x90, - 0x68, 0x51, 0x09, 0xd7, 0x3b, 0x48, 0x1b, 0x8a, 0xd3, 0x50, 0x09, 0xba, 0xfc, 0xde, 0x11, - 0xe0, 0x3f, 0xcb - }; - Crypto::Cipher::AESCipher::CBCMode cipher("Well Hello Friends! whf!"_b, 192, Crypto::Cipher::Intent::Decryption); - test_it(cipher, result, 48); - } - { - I_TEST((AES CBC with 256 bit key | Decrypt)) - u8 result[] { - 0x0a, 0x44, 0x4d, 0x62, 0x9e, 0x8b, 0xd8, 0x11, 0x80, 0x48, 0x2a, 0x32, 0x53, 0x61, 0xe7, - 0x59, 0x62, 0x55, 0x9e, 0xf4, 0xe6, 0xad, 0xea, 0xc5, 0x0b, 0xf6, 0xbc, 0x6a, 0xcb, 0x9c, - 0x47, 0x9f, 0xc2, 0x21, 0xe6, 0x19, 0x62, 0xc3, 0x75, 0xca, 0xab, 0x2d, 0x18, 0xa1, 0x54, - 0xd1, 0x41, 0xe6 - }; - Crypto::Cipher::AESCipher::CBCMode cipher("WellHelloFriendsWellHelloFriends"_b, 256, Crypto::Cipher::Intent::Decryption); - test_it(cipher, result, 48); - } - // TODO: Test non-CMS padding options -} - -static int aes_ctr_tests() -{ - aes_ctr_test_name(); - if (encrypting) { - aes_ctr_test_encrypt(); - } else { - aes_ctr_test_decrypt(); - } - - return g_some_test_failed ? 1 : 0; -} - -static void aes_ctr_test_name() -{ - I_TEST((AES CTR class name)); - Crypto::Cipher::AESCipher::CTRMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Encryption); - if (cipher.class_name() != "AES_CTR") - FAIL(Invalid class name); - else - PASS; -} - -#define AS_BB(x) (ReadonlyBytes { (x), sizeof((x)) / sizeof((x)[0]) }) -static void aes_ctr_test_encrypt() -{ - auto test_it = [](auto key, auto ivec, auto in, auto out_expected) { - // nonce is already included in ivec. - Crypto::Cipher::AESCipher::CTRMode cipher(key, 8 * key.size(), Crypto::Cipher::Intent::Encryption); - ByteBuffer out_actual = ByteBuffer::create_zeroed(in.size()); - Bytes out_span = out_actual.bytes(); - cipher.encrypt(in, out_span, ivec); - if (out_expected.size() != out_actual.size()) { - FAIL(size mismatch); - printf("Expected %zu bytes but got %zu\n", out_expected.size(), out_span.size()); - print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); - } else if (memcmp(out_expected.data(), out_span.data(), out_expected.size()) != 0) { - FAIL(invalid data); - print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); - } else - PASS; - }; - // From RFC 3686, Section 6 - { - // Test Vector #1 - I_TEST((AES CTR 16 octets with 128 bit key | Encrypt)) - u8 key[] { - 0xae, 0x68, 0x52, 0xf8, 0x12, 0x10, 0x67, 0xcc, 0x4b, 0xf7, 0xa5, 0x76, 0x55, 0x77, 0xf3, 0x9e - }; - u8 ivec[] { - 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 - }; - u8 out[] { - 0xe4, 0x09, 0x5d, 0x4f, 0xb7, 0xa7, 0xb3, 0x79, 0x2d, 0x61, 0x75, 0xa3, 0x26, 0x13, 0x11, 0xb8 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - { - // Test Vector #2 - I_TEST((AES CTR 32 octets with 128 bit key | Encrypt)) - u8 key[] { - 0x7e, 0x24, 0x06, 0x78, 0x17, 0xfa, 0xe0, 0xd7, 0x43, 0xd6, 0xce, 0x1f, 0x32, 0x53, 0x91, 0x63 - }; - u8 ivec[] { - 0x00, 0x6c, 0xb6, 0xdb, 0xc0, 0x54, 0x3b, 0x59, 0xda, 0x48, 0xd9, 0x0b, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f - }; - u8 out[] { - 0x51, 0x04, 0xa1, 0x06, 0x16, 0x8a, 0x72, 0xd9, 0x79, 0x0d, 0x41, 0xee, 0x8e, 0xda, 0xd3, 0x88, - 0xeb, 0x2e, 0x1e, 0xfc, 0x46, 0xda, 0x57, 0xc8, 0xfc, 0xe6, 0x30, 0xdf, 0x91, 0x41, 0xbe, 0x28 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - { - // Test Vector #3 - I_TEST((AES CTR 36 octets with 128 bit key | Encrypt)) - u8 key[] { - 0x76, 0x91, 0xbe, 0x03, 0x5e, 0x50, 0x20, 0xa8, 0xac, 0x6e, 0x61, 0x85, 0x29, 0xf9, 0xa0, 0xdc - }; - u8 ivec[] { - 0x00, 0xe0, 0x01, 0x7b, 0x27, 0x77, 0x7f, 0x3f, 0x4a, 0x17, 0x86, 0xf0, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 - }; - u8 out[] { - 0xc1, 0xcf, 0x48, 0xa8, 0x9f, 0x2f, 0xfd, 0xd9, 0xcf, 0x46, 0x52, 0xe9, 0xef, 0xdb, 0x72, 0xd7, 0x45, 0x40, 0xa4, 0x2b, 0xde, 0x6d, 0x78, 0x36, 0xd5, 0x9a, 0x5c, 0xea, 0xae, 0xf3, 0x10, 0x53, 0x25, 0xb2, 0x07, 0x2f - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - { - // Test Vector #4 - I_TEST((AES CTR 16 octets with 192 bit key | Encrypt)) - u8 key[] { - 0x16, 0xaf, 0x5b, 0x14, 0x5f, 0xc9, 0xf5, 0x79, 0xc1, 0x75, 0xf9, 0x3e, 0x3b, 0xfb, 0x0e, 0xed, 0x86, 0x3d, 0x06, 0xcc, 0xfd, 0xb7, 0x85, 0x15 - }; - u8 ivec[] { - 0x00, 0x00, 0x00, 0x48, 0x36, 0x73, 0x3c, 0x14, 0x7d, 0x6d, 0x93, 0xcb, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 - }; - u8 out[] { - 0x4b, 0x55, 0x38, 0x4f, 0xe2, 0x59, 0xc9, 0xc8, 0x4e, 0x79, 0x35, 0xa0, 0x03, 0xcb, 0xe9, 0x28 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - { - // Test Vector #5 - I_TEST((AES CTR 32 octets with 192 bit key | Encrypt)) - u8 key[] { - 0x7c, 0x5c, 0xb2, 0x40, 0x1b, 0x3d, 0xc3, 0x3c, 0x19, 0xe7, 0x34, 0x08, 0x19, 0xe0, 0xf6, 0x9c, 0x67, 0x8c, 0x3d, 0xb8, 0xe6, 0xf6, 0xa9, 0x1a - }; - u8 ivec[] { - 0x00, 0x96, 0xb0, 0x3b, 0x02, 0x0c, 0x6e, 0xad, 0xc2, 0xcb, 0x50, 0x0d, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f - }; - u8 out[] { - 0x45, 0x32, 0x43, 0xfc, 0x60, 0x9b, 0x23, 0x32, 0x7e, 0xdf, 0xaa, 0xfa, 0x71, 0x31, 0xcd, 0x9f, 0x84, 0x90, 0x70, 0x1c, 0x5a, 0xd4, 0xa7, 0x9c, 0xfc, 0x1f, 0xe0, 0xff, 0x42, 0xf4, 0xfb, 0x00 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - { - // Test Vector #6 - I_TEST((AES CTR 36 octets with 192 bit key | Encrypt)) - u8 key[] { - 0x02, 0xbf, 0x39, 0x1e, 0xe8, 0xec, 0xb1, 0x59, 0xb9, 0x59, 0x61, 0x7b, 0x09, 0x65, 0x27, 0x9b, 0xf5, 0x9b, 0x60, 0xa7, 0x86, 0xd3, 0xe0, 0xfe - }; - u8 ivec[] { - 0x00, 0x07, 0xbd, 0xfd, 0x5c, 0xbd, 0x60, 0x27, 0x8d, 0xcc, 0x09, 0x12, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 - }; - u8 out[] { - 0x96, 0x89, 0x3f, 0xc5, 0x5e, 0x5c, 0x72, 0x2f, 0x54, 0x0b, 0x7d, 0xd1, 0xdd, 0xf7, 0xe7, 0x58, 0xd2, 0x88, 0xbc, 0x95, 0xc6, 0x91, 0x65, 0x88, 0x45, 0x36, 0xc8, 0x11, 0x66, 0x2f, 0x21, 0x88, 0xab, 0xee, 0x09, 0x35 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - { - // Test Vector #7 - I_TEST((AES CTR 16 octets with 256 bit key | Encrypt)) - u8 key[] { - 0x77, 0x6b, 0xef, 0xf2, 0x85, 0x1d, 0xb0, 0x6f, 0x4c, 0x8a, 0x05, 0x42, 0xc8, 0x69, 0x6f, 0x6c, 0x6a, 0x81, 0xaf, 0x1e, 0xec, 0x96, 0xb4, 0xd3, 0x7f, 0xc1, 0xd6, 0x89, 0xe6, 0xc1, 0xc1, 0x04 - }; - u8 ivec[] { - 0x00, 0x00, 0x00, 0x60, 0xdb, 0x56, 0x72, 0xc9, 0x7a, 0xa8, 0xf0, 0xb2, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 - }; - u8 out[] { - 0x14, 0x5a, 0xd0, 0x1d, 0xbf, 0x82, 0x4e, 0xc7, 0x56, 0x08, 0x63, 0xdc, 0x71, 0xe3, 0xe0, 0xc0 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - { - // Test Vector #8 - I_TEST((AES CTR 32 octets with 256 bit key | Encrypt)) - u8 key[] { - 0xf6, 0xd6, 0x6d, 0x6b, 0xd5, 0x2d, 0x59, 0xbb, 0x07, 0x96, 0x36, 0x58, 0x79, 0xef, 0xf8, 0x86, 0xc6, 0x6d, 0xd5, 0x1a, 0x5b, 0x6a, 0x99, 0x74, 0x4b, 0x50, 0x59, 0x0c, 0x87, 0xa2, 0x38, 0x84 - }; - u8 ivec[] { - 0x00, 0xfa, 0xac, 0x24, 0xc1, 0x58, 0x5e, 0xf1, 0x5a, 0x43, 0xd8, 0x75, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f - }; - u8 out[] { - 0xf0, 0x5e, 0x23, 0x1b, 0x38, 0x94, 0x61, 0x2c, 0x49, 0xee, 0x00, 0x0b, 0x80, 0x4e, 0xb2, 0xa9, 0xb8, 0x30, 0x6b, 0x50, 0x8f, 0x83, 0x9d, 0x6a, 0x55, 0x30, 0x83, 0x1d, 0x93, 0x44, 0xaf, 0x1c - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - { - // Test Vector #9 - I_TEST((AES CTR 36 octets with 256 bit key | Encrypt)) - u8 key[] { - 0xff, 0x7a, 0x61, 0x7c, 0xe6, 0x91, 0x48, 0xe4, 0xf1, 0x72, 0x6e, 0x2f, 0x43, 0x58, 0x1d, 0xe2, 0xaa, 0x62, 0xd9, 0xf8, 0x05, 0x53, 0x2e, 0xdf, 0xf1, 0xee, 0xd6, 0x87, 0xfb, 0x54, 0x15, 0x3d - }; - u8 ivec[] { - 0x00, 0x1c, 0xc5, 0xb7, 0x51, 0xa5, 0x1d, 0x70, 0xa1, 0xc1, 0x11, 0x48, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 in[] { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 - }; - u8 out[] { - 0xeb, 0x6c, 0x52, 0x82, 0x1d, 0x0b, 0xbb, 0xf7, 0xce, 0x75, 0x94, 0x46, 0x2a, 0xca, 0x4f, 0xaa, 0xb4, 0x07, 0xdf, 0x86, 0x65, 0x69, 0xfd, 0x07, 0xf4, 0x8c, 0xc0, 0xb5, 0x83, 0xd6, 0x07, 0x1f, 0x1e, 0xc0, 0xe6, 0xb8 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - // Manual test case - { - // This test checks whether counter overflow crashes. - I_TEST((AES CTR 36 octets with 256 bit key, high counter | Encrypt)) - u8 key[] { - 0xff, 0x7a, 0x61, 0x7c, 0xe6, 0x91, 0x48, 0xe4, 0xf1, 0x72, 0x6e, 0x2f, 0x43, 0x58, 0x1d, 0xe2, 0xaa, 0x62, 0xd9, 0xf8, 0x05, 0x53, 0x2e, 0xdf, 0xf1, 0xee, 0xd6, 0x87, 0xfb, 0x54, 0x15, 0x3d - }; - u8 ivec[] { - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff - }; - u8 in[] { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23 - }; - u8 out[] { - // Pasted from the output. The actual success condition is - // not crashing when incrementing the counter. - 0x6e, 0x8c, 0xfc, 0x59, 0x08, 0xa8, 0xc0, 0xf1, 0xe6, 0x85, 0x96, 0xe9, 0xc5, 0x40, 0xb6, 0x8b, 0xfe, 0x28, 0x72, 0xe2, 0x24, 0x11, 0x7e, 0x59, 0xef, 0xac, 0x5c, 0xe1, 0x06, 0x89, 0x09, 0xab, 0xf8, 0x90, 0x1c, 0x66 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } -} - -static void aes_ctr_test_decrypt() -{ - auto test_it = [](auto key, auto ivec, auto in, auto out_expected) { - // nonce is already included in ivec. - Crypto::Cipher::AESCipher::CTRMode cipher(key, 8 * key.size(), Crypto::Cipher::Intent::Decryption); - ByteBuffer out_actual = ByteBuffer::create_zeroed(in.size()); - auto out_span = out_actual.bytes(); - cipher.decrypt(in, out_span, ivec); - if (out_expected.size() != out_span.size()) { - FAIL(size mismatch); - printf("Expected %zu bytes but got %zu\n", out_expected.size(), out_span.size()); - print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); - } else if (memcmp(out_expected.data(), out_span.data(), out_expected.size()) != 0) { - FAIL(invalid data); - print_buffer(out_span, Crypto::Cipher::AESCipher::block_size()); - } else - PASS; - }; - // From RFC 3686, Section 6 - { - // Test Vector #1 - I_TEST((AES CTR 16 octets with 128 bit key | Decrypt)) - u8 key[] { - 0xae, 0x68, 0x52, 0xf8, 0x12, 0x10, 0x67, 0xcc, 0x4b, 0xf7, 0xa5, 0x76, 0x55, 0x77, 0xf3, 0x9e - }; - u8 ivec[] { - 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + 1 // See CTR.h - }; - u8 out[] { - 0x53, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6d, 0x73, 0x67 - }; - u8 in[] { - 0xe4, 0x09, 0x5d, 0x4f, 0xb7, 0xa7, 0xb3, 0x79, 0x2d, 0x61, 0x75, 0xa3, 0x26, 0x13, 0x11, 0xb8 - }; - test_it(AS_BB(key), AS_BB(ivec), AS_BB(in), AS_BB(out)); - } - // If encryption works, then decryption works, too. -} - -static int aes_gcm_tests() -{ - aes_gcm_test_name(); - if (encrypting) { - aes_gcm_test_encrypt(); - } else { - aes_gcm_test_decrypt(); - } - - return g_some_test_failed ? 1 : 0; -} - -static void aes_gcm_test_name() -{ - I_TEST((AES GCM class name)); - Crypto::Cipher::AESCipher::GCMMode cipher("WellHelloFriends"_b, 128, Crypto::Cipher::Intent::Encryption); - if (cipher.class_name() != "AES_GCM") - FAIL(Invalid class name); - else - PASS; -} - -static void aes_gcm_test_encrypt() -{ - { - I_TEST((AES GCM Encrypt | Empty)); - Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 result_tag[] { 0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61, 0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, 0x45, 0x5a }; - Bytes out; - auto tag = ByteBuffer::create_uninitialized(16); - cipher.encrypt({}, out, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), {}, tag); - if (memcmp(result_tag, tag.data(), tag.size()) != 0) { - FAIL(Invalid auth tag); - print_buffer(tag, -1); - } else - PASS; - } - { - I_TEST((AES GCM Encrypt | Zeros)); - Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 result_tag[] { 0xab, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd, 0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd, 0xdf }; - u8 result_ct[] { 0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78 }; - auto tag = ByteBuffer::create_uninitialized(16); - auto out = ByteBuffer::create_uninitialized(16); - auto out_bytes = out.bytes(); - cipher.encrypt("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), {}, tag); - if (memcmp(result_ct, out.data(), out.size()) != 0) { - FAIL(Invalid ciphertext); - print_buffer(out, -1); - } else if (memcmp(result_tag, tag.data(), tag.size()) != 0) { - FAIL(Invalid auth tag); - print_buffer(tag, -1); - } else - PASS; - } - { - I_TEST((AES GCM Encrypt | Multiple Blocks With IV)); - Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 result_tag[] { 0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6, 0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4 }; - u8 result_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; - auto tag = ByteBuffer::create_uninitialized(16); - auto out = ByteBuffer::create_uninitialized(64); - auto out_bytes = out.bytes(); - cipher.encrypt( - "\xd9\x31\x32\x25\xf8\x84\x06\xe5\xa5\x59\x09\xc5\xaf\xf5\x26\x9a\x86\xa7\xa9\x53\x15\x34\xf7\xda\x2e\x4c\x30\x3d\x8a\x31\x8a\x72\x1c\x3c\x0c\x95\x95\x68\x09\x53\x2f\xcf\x0e\x24\x49\xa6\xb5\x25\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57\xba\x63\x7b\x39\x1a\xaf\xd2\x55"_b.bytes(), - out_bytes, - "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), - {}, - tag); - if (memcmp(result_ct, out.data(), out.size()) != 0) { - FAIL(Invalid ciphertext); - print_buffer(out, -1); - } else if (memcmp(result_tag, tag.data(), tag.size()) != 0) { - FAIL(Invalid auth tag); - print_buffer(tag, -1); - } else - PASS; - } - { - I_TEST((AES GCM Encrypt | With AAD)); - Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 result_tag[] { 0x93, 0xae, 0x16, 0x97, 0x49, 0xa3, 0xbf, 0x39, 0x4f, 0x61, 0xb7, 0xc1, 0xb1, 0x2, 0x4f, 0x60 }; - u8 result_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; - auto tag = ByteBuffer::create_uninitialized(16); - auto out = ByteBuffer::create_uninitialized(64); - auto out_bytes = out.bytes(); - cipher.encrypt( - "\xd9\x31\x32\x25\xf8\x84\x06\xe5\xa5\x59\x09\xc5\xaf\xf5\x26\x9a\x86\xa7\xa9\x53\x15\x34\xf7\xda\x2e\x4c\x30\x3d\x8a\x31\x8a\x72\x1c\x3c\x0c\x95\x95\x68\x09\x53\x2f\xcf\x0e\x24\x49\xa6\xb5\x25\xb1\x6a\xed\xf5\xaa\x0d\xe6\x57\xba\x63\x7b\x39\x1a\xaf\xd2\x55"_b.bytes(), - out_bytes, - "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), - "\xde\xad\xbe\xef\xfa\xaf\x11\xcc"_b.bytes(), - tag); - if (memcmp(result_ct, out.data(), out.size()) != 0) { - FAIL(Invalid ciphertext); - print_buffer(out, -1); - } else if (memcmp(result_tag, tag.data(), tag.size()) != 0) { - FAIL(Invalid auth tag); - print_buffer(tag, -1); - } else - PASS; - } -} - -static void aes_gcm_test_decrypt() -{ - { - I_TEST((AES GCM Decrypt | Empty)); - Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 input_tag[] { 0x58, 0xe2, 0xfc, 0xce, 0xfa, 0x7e, 0x30, 0x61, 0x36, 0x7f, 0x1d, 0x57, 0xa4, 0xe7, 0x45, 0x5a }; - Bytes out; - auto consistency = cipher.decrypt({}, out, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), {}, { input_tag, 16 }); - if (consistency != Crypto::VerificationConsistency::Consistent) { - FAIL(Verification reported inconsistent); - } else if (out.size() != 0) { - FAIL(Invalid plain text); - } else - PASS; - } - { - I_TEST((AES GCM Decrypt | Zeros)); - Crypto::Cipher::AESCipher::GCMMode cipher("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 input_tag[] { 0xab, 0x6e, 0x47, 0xd4, 0x2c, 0xec, 0x13, 0xbd, 0xf5, 0x3a, 0x67, 0xb2, 0x12, 0x57, 0xbd, 0xdf }; - u8 input_ct[] { 0x03, 0x88, 0xda, 0xce, 0x60, 0xb6, 0xa3, 0x92, 0xf3, 0x28, 0xc2, 0xb9, 0x71, 0xb2, 0xfe, 0x78 }; - u8 result_pt[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - auto out = ByteBuffer::create_uninitialized(16); - auto out_bytes = out.bytes(); - auto consistency = cipher.decrypt({ input_ct, 16 }, out_bytes, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"_b.bytes(), {}, { input_tag, 16 }); - if (consistency != Crypto::VerificationConsistency::Consistent) { - FAIL(Verification reported inconsistent); - } else if (memcmp(result_pt, out.data(), out.size()) != 0) { - FAIL(Invalid plaintext); - print_buffer(out, -1); - } else - PASS; - } - { - I_TEST((AES GCM Decrypt | Multiple Blocks With IV)); - Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 input_tag[] { 0x4d, 0x5c, 0x2a, 0xf3, 0x27, 0xcd, 0x64, 0xa6, 0x2c, 0xf3, 0x5a, 0xbd, 0x2b, 0xa6, 0xfa, 0xb4 }; - u8 input_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; - u8 result_pt[] { 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 }; - auto out = ByteBuffer::create_uninitialized(64); - auto out_bytes = out.bytes(); - - auto consistency = cipher.decrypt( - { input_ct, 64 }, - out_bytes, - "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), - {}, - { input_tag, 16 }); - if (memcmp(result_pt, out.data(), out.size()) != 0) { - FAIL(Invalid plaintext); - print_buffer(out, -1); - } else if (consistency != Crypto::VerificationConsistency::Consistent) { - FAIL(Verification reported inconsistent); - } else - PASS; - } - { - I_TEST((AES GCM Decrypt | With AAD)); - Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 input_tag[] { 0x93, 0xae, 0x16, 0x97, 0x49, 0xa3, 0xbf, 0x39, 0x4f, 0x61, 0xb7, 0xc1, 0xb1, 0x2, 0x4f, 0x60 }; - u8 input_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; - u8 result_pt[] { 0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5, 0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a, 0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda, 0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72, 0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53, 0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25, 0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57, 0xba, 0x63, 0x7b, 0x39, 0x1a, 0xaf, 0xd2, 0x55 }; - auto out = ByteBuffer::create_uninitialized(64); - auto out_bytes = out.bytes(); - auto consistency = cipher.decrypt( - { input_ct, 64 }, - out_bytes, - "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), - "\xde\xad\xbe\xef\xfa\xaf\x11\xcc"_b.bytes(), - { input_tag, 16 }); - if (memcmp(result_pt, out.data(), out.size()) != 0) { - FAIL(Invalid plaintext); - print_buffer(out, -1); - } else if (consistency != Crypto::VerificationConsistency::Consistent) { - FAIL(Verification reported inconsistent); - } else - PASS; - } - { - I_TEST((AES GCM Decrypt | With AAD - Invalid Tag)); - Crypto::Cipher::AESCipher::GCMMode cipher("\xfe\xff\xe9\x92\x86\x65\x73\x1c\x6d\x6a\x8f\x94\x67\x30\x83\x08"_b, 128, Crypto::Cipher::Intent::Encryption); - u8 input_tag[] { 0x94, 0xae, 0x16, 0x97, 0x49, 0xa3, 0xbf, 0x39, 0x4f, 0x61, 0xb7, 0xc1, 0xb1, 0x2, 0x4f, 0x60 }; - u8 input_ct[] { 0x42, 0x83, 0x1e, 0xc2, 0x21, 0x77, 0x74, 0x24, 0x4b, 0x72, 0x21, 0xb7, 0x84, 0xd0, 0xd4, 0x9c, 0xe3, 0xaa, 0x21, 0x2f, 0x2c, 0x02, 0xa4, 0xe0, 0x35, 0xc1, 0x7e, 0x23, 0x29, 0xac, 0xa1, 0x2e, 0x21, 0xd5, 0x14, 0xb2, 0x54, 0x66, 0x93, 0x1c, 0x7d, 0x8f, 0x6a, 0x5a, 0xac, 0x84, 0xaa, 0x05, 0x1b, 0xa3, 0x0b, 0x39, 0x6a, 0x0a, 0xac, 0x97, 0x3d, 0x58, 0xe0, 0x91, 0x47, 0x3f, 0x59, 0x85 }; - auto out = ByteBuffer::create_uninitialized(64); - auto out_bytes = out.bytes(); - auto consistency = cipher.decrypt( - { input_ct, 64 }, - out_bytes, - "\xca\xfe\xba\xbe\xfa\xce\xdb\xad\xde\xca\xf8\x88\x00\x00\x00\x00"_b.bytes(), - "\xde\xad\xbe\xef\xfa\xaf\x11\xcc"_b.bytes(), - { input_tag, 16 }); - - if (consistency != Crypto::VerificationConsistency::Inconsistent) - FAIL(Verification reported consistent); - else - PASS; - } -} - -static int md5_tests() -{ - md5_test_name(); - md5_test_hash(); - md5_test_consecutive_updates(); - return g_some_test_failed ? 1 : 0; -} - -static void md5_test_name() -{ - I_TEST((MD5 class name)); - Crypto::Hash::MD5 md5; - if (md5.class_name() != "MD5") - FAIL(Invalid class name); - else - PASS; -} - -static void md5_test_hash() -{ - { - I_TEST((MD5 Hashing | "Well hello friends")); - u8 result[] { - 0xaf, 0x04, 0x3a, 0x08, 0x94, 0x38, 0x6e, 0x7f, 0xbf, 0x73, 0xe4, 0xaa, 0xf0, 0x8e, 0xee, 0x4c - }; - auto digest = Crypto::Hash::MD5::hash("Well hello friends"); - - if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); - } else { - PASS; - } - } - // RFC tests - { - I_TEST((MD5 Hashing | "")); - u8 result[] { - 0xd4, 0x1d, 0x8c, 0xd9, 0x8f, 0x00, 0xb2, 0x04, 0xe9, 0x80, 0x09, 0x98, 0xec, 0xf8, 0x42, 0x7e - }; - auto digest = Crypto::Hash::MD5::hash(""); - - if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); - } else { - PASS; - } - } - { - I_TEST((MD5 Hashing | "a")); - u8 result[] { - 0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61 - }; - auto digest = Crypto::Hash::MD5::hash("a"); - - if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); - } else { - PASS; - } - } - { - I_TEST((MD5 Hashing | "abcdefghijklmnopqrstuvwxyz")); - u8 result[] { - 0xc3, 0xfc, 0xd3, 0xd7, 0x61, 0x92, 0xe4, 0x00, 0x7d, 0xfb, 0x49, 0x6c, 0xca, 0x67, 0xe1, 0x3b - }; - auto digest = Crypto::Hash::MD5::hash("abcdefghijklmnopqrstuvwxyz"); - - if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); - } else { - PASS; - } - } - { - I_TEST((MD5 Hashing | Long Sequence)); - u8 result[] { - 0x57, 0xed, 0xf4, 0xa2, 0x2b, 0xe3, 0xc9, 0x55, 0xac, 0x49, 0xda, 0x2e, 0x21, 0x07, 0xb6, 0x7a - }; - auto digest = Crypto::Hash::MD5::hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"); - - if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::MD5::digest_size() }, -1); - } else { - PASS; - } - } -} - -static void md5_test_consecutive_updates() -{ - { - I_TEST((MD5 Hashing | Multiple Updates)); - u8 result[] { - 0xaf, 0x04, 0x3a, 0x08, 0x94, 0x38, 0x6e, 0x7f, 0xbf, 0x73, 0xe4, 0xaa, 0xf0, 0x8e, 0xee, 0x4c - }; - Crypto::Hash::MD5 md5; - - md5.update("Well"); - md5.update(" hello "); - md5.update("friends"); - auto digest = md5.digest(); - - if (memcmp(result, digest.data, Crypto::Hash::MD5::digest_size()) != 0) - FAIL(Invalid hash); - else - PASS; - } - { - I_TEST((MD5 Hashing | Reuse)); - Crypto::Hash::MD5 md5; - - md5.update("Well"); - md5.update(" hello "); - md5.update("friends"); - auto digest0 = md5.digest(); - - md5.update("Well"); - md5.update(" hello "); - md5.update("friends"); - auto digest1 = md5.digest(); - - if (memcmp(digest0.data, digest1.data, Crypto::Hash::MD5::digest_size()) != 0) - FAIL(Cannot reuse); - else - PASS; - } -} - -static int hmac_md5_tests() -{ - hmac_md5_test_name(); - hmac_md5_test_process(); - return g_some_test_failed ? 1 : 0; -} - -static int hmac_sha256_tests() -{ - hmac_sha256_test_name(); - hmac_sha256_test_process(); - return g_some_test_failed ? 1 : 0; -} - -static int hmac_sha512_tests() -{ - hmac_sha512_test_name(); - hmac_sha512_test_process(); - return g_some_test_failed ? 1 : 0; -} - -static int hmac_sha1_tests() -{ - hmac_sha1_test_name(); - hmac_sha1_test_process(); - return g_some_test_failed ? 1 : 0; -} - -static void hmac_md5_test_name() -{ - I_TEST((HMAC - MD5 | Class name)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - if (hmac.class_name() != "HMAC-MD5") - FAIL(Invalid class name); - else - PASS; -} - -static void hmac_md5_test_process() -{ - { - I_TEST((HMAC - MD5 | Basic)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - u8 result[] { - 0x3b, 0x5b, 0xde, 0x30, 0x3a, 0x54, 0x7b, 0xbb, 0x09, 0xfe, 0x78, 0x89, 0xbc, 0x9f, 0x22, 0xa3 - }; - auto mac = hmac.process("Some bogus data"); - if (memcmp(result, mac.data, hmac.digest_size()) != 0) { - FAIL(Invalid mac); - print_buffer({ mac.data, hmac.digest_size() }, -1); - } else - PASS; - } - { - I_TEST((HMAC - MD5 | Reuse)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - - auto mac_0 = hmac.process("Some bogus data"); - auto mac_1 = hmac.process("Some bogus data"); - - if (memcmp(mac_0.data, mac_1.data, hmac.digest_size()) != 0) { - FAIL(Cannot reuse); - } else - PASS; - } -} - -static int ghash_tests() -{ - ghash_test_name(); - ghash_test_process(); - return g_some_test_failed ? 1 : 0; -} - -static void ghash_test_name() -{ - I_TEST((GHash class name)); - Crypto::Authentication::GHash ghash("WellHelloFriends"); - if (ghash.class_name() != "GHash") - FAIL(Invalid class name); - else - PASS; -} - -static void hmac_sha1_test_name() -{ - I_TEST((HMAC - SHA1 | Class name)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - if (hmac.class_name() != "HMAC-SHA1") - FAIL(Invalid class name); - else - PASS; -} - -static void hmac_sha1_test_process() -{ - { - I_TEST((HMAC - SHA1 | Basic)); - u8 key[] { 0xc8, 0x52, 0xe5, 0x4a, 0x2c, 0x03, 0x2b, 0xc9, 0x63, 0xd3, 0xc2, 0x79, 0x0f, 0x76, 0x43, 0xef, 0x36, 0xc3, 0x7a, 0xca }; - Crypto::Authentication::HMAC hmac(ReadonlyBytes { key, sizeof(key) }); - u8 result[] { - 0x2c, 0x57, 0x32, 0x61, 0x3b, 0xa7, 0x84, 0x87, 0x0e, 0x4f, 0x42, 0x07, 0x2f, 0xf0, 0xe7, 0x41, 0xd7, 0x15, 0xf4, 0x56 - }; - u8 value[] { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x03, 0x03, 0x00, 0x10, 0x14, 0x00, 0x00, 0x0c, 0xa1, 0x91, 0x1a, 0x20, 0x59, 0xb5, 0x45, 0xa9, 0xb4, 0xad, 0x75, 0x3e - }; - auto mac = hmac.process(value, 29); - if (memcmp(result, mac.data, hmac.digest_size()) != 0) { - FAIL(Invalid mac); - print_buffer({ mac.data, hmac.digest_size() }, -1); - } else - PASS; - } - { - I_TEST((HMAC - SHA1 | Reuse)); - u8 key[] { 0xc8, 0x52, 0xe5, 0x4a, 0x2c, 0x03, 0x2b, 0xc9, 0x63, 0xd3, 0xc2, 0x79, 0x0f, 0x76, 0x43, 0xef, 0x36, 0xc3, 0x7a, 0xca }; - Crypto::Authentication::HMAC hmac(ReadonlyBytes { key, sizeof(key) }); - u8 result[] { - 0x2c, 0x57, 0x32, 0x61, 0x3b, 0xa7, 0x84, 0x87, 0x0e, 0x4f, 0x42, 0x07, 0x2f, 0xf0, 0xe7, 0x41, 0xd7, 0x15, 0xf4, 0x56 - }; - u8 value[] { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x03, 0x03, 0x00, 0x10, 0x14, 0x00, 0x00, 0x0c, 0xa1, 0x91, 0x1a, 0x20, 0x59, 0xb5, 0x45, 0xa9, 0xb4, 0xad, 0x75, 0x3e - }; - hmac.update(value, 8); - hmac.update(value + 8, 5); - hmac.update(value + 13, 16); - auto mac = hmac.digest(); - if (memcmp(result, mac.data, hmac.digest_size()) != 0) { - FAIL(Invalid mac); - print_buffer({ mac.data, hmac.digest_size() }, -1); - } else - PASS; - } -} - -static void ghash_test_process() -{ - { - I_TEST((GHash | Galois Field Multiply)); - u32 x[4] { 0x42831ec2, 0x21777424, 0x4b7221b7, 0x84d0d49c }, - y[4] { 0xb83b5337, 0x08bf535d, 0x0aa6e529, 0x80d53b78 }, z[4] { 0, 0, 0, 0 }; - static constexpr u32 result[4] { 0x59ed3f2b, 0xb1a0aaa0, 0x7c9f56c6, 0xa504647b }; - - Crypto::Authentication::galois_multiply(z, x, y); - if (memcmp(result, z, 4 * sizeof(u32)) != 0) { - FAIL(Invalid multiply value); - print_buffer({ z, 4 * sizeof(u32) }, -1); - print_buffer({ result, 4 * sizeof(u32) }, -1); - } else - PASS; - } - { - I_TEST((GHash | Galois Field Multiply #2)); - u32 x[4] { 59300558, 1622582162, 4079534777, 1907555960 }, - y[4] { 1726565332, 4018809915, 2286746201, 3392416558 }, z[4]; - constexpr static u32 result[4] { 1580123974, 2440061576, 746958952, 1398005431 }; - - Crypto::Authentication::galois_multiply(z, x, y); - if (memcmp(result, z, 4 * sizeof(u32)) != 0) { - FAIL(Invalid multiply value); - print_buffer({ z, 4 * sizeof(u32) }, -1); - print_buffer({ result, 4 * sizeof(u32) }, -1); - } else - PASS; - } - // TODO: Add some GHash tests? - // Kinda hard, as there are no vectors and existing tools don't have an interface to it. -} - -static int sha1_tests() -{ - sha1_test_name(); - sha1_test_hash(); - return g_some_test_failed ? 1 : 0; -} - -static void sha1_test_name() -{ - I_TEST((SHA1 class name)); - Crypto::Hash::SHA1 sha; - if (sha.class_name() != "SHA1") { - FAIL(Invalid class name); - printf("%s\n", sha.class_name().characters()); - } else - PASS; -} - -static void sha1_test_hash() -{ - { - I_TEST((SHA256 Hashing | "")); - u8 result[] { - 0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09 - }; - auto digest = Crypto::Hash::SHA1::hash(""); - if (memcmp(result, digest.data, Crypto::Hash::SHA1::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::SHA1::digest_size() }, -1); - } else - PASS; - } - { - I_TEST((SHA256 Hashing | Long String)); - u8 result[] { - 0x12, 0x15, 0x1f, 0xb1, 0x04, 0x44, 0x93, 0xcc, 0xed, 0x54, 0xa6, 0xb8, 0x7e, 0x93, 0x37, 0x7b, 0xb2, 0x13, 0x39, 0xdb - }; - auto digest = Crypto::Hash::SHA1::hash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - if (memcmp(result, digest.data, Crypto::Hash::SHA1::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::SHA1::digest_size() }, -1); - } else - PASS; - } - { - I_TEST((SHA256 Hashing | Successive Updates)); - u8 result[] { - 0xd6, 0x6e, 0xce, 0xd1, 0xf4, 0x08, 0xc6, 0xd8, 0x35, 0xab, 0xf0, 0xc9, 0x05, 0x26, 0xa4, 0xb2, 0xb8, 0xa3, 0x7c, 0xd3 - }; - auto hasher = Crypto::Hash::SHA1 {}; - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaaaaaaaa"); - hasher.update("aaaaaaaaa"); - auto digest = hasher.digest(); - if (memcmp(result, digest.data, Crypto::Hash::SHA1::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::SHA1::digest_size() }, -1); - } else - PASS; - } -} - -static int sha256_tests() -{ - sha256_test_name(); - sha256_test_hash(); - return g_some_test_failed ? 1 : 0; -} - -static void sha256_test_name() -{ - I_TEST((SHA256 class name)); - Crypto::Hash::SHA256 sha; - if (sha.class_name() != "SHA256") { - FAIL(Invalid class name); - printf("%s\n", sha.class_name().characters()); - } else - PASS; -} - -static void sha256_test_hash() -{ - { - I_TEST((SHA256 Hashing | "Well hello friends")); - u8 result[] { - 0x9a, 0xcd, 0x50, 0xf9, 0xa2, 0xaf, 0x37, 0xe4, 0x71, 0xf7, 0x61, 0xc3, 0xfe, 0x7b, 0x8d, 0xea, 0x56, 0x17, 0xe5, 0x1d, 0xac, 0x80, 0x2f, 0xe6, 0xc1, 0x77, 0xb7, 0x4a, 0xbf, 0x0a, 0xbb, 0x5a - }; - auto digest = Crypto::Hash::SHA256::hash("Well hello friends"); - if (memcmp(result, digest.data, Crypto::Hash::SHA256::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::SHA256::digest_size() }, -1); - } else - PASS; - } - { - I_TEST((SHA256 Hashing | "")); - u8 result[] { - 0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24, 0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55 - }; - auto digest = Crypto::Hash::SHA256::hash(""); - if (memcmp(result, digest.data, Crypto::Hash::SHA256::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::SHA256::digest_size() }, -1); - } else - PASS; - } -} - -static void hmac_sha256_test_name() -{ - I_TEST((HMAC - SHA256 | Class name)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - if (hmac.class_name() != "HMAC-SHA256") - FAIL(Invalid class name); - else - PASS; -} - -static void hmac_sha256_test_process() -{ - { - I_TEST((HMAC - SHA256 | Basic)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - u8 result[] { - 0x1a, 0xf2, 0x20, 0x62, 0xde, 0x3b, 0x84, 0x65, 0xc1, 0x25, 0x23, 0x99, 0x76, 0x15, 0x1b, 0xec, 0x15, 0x21, 0x82, 0x1f, 0x23, 0xca, 0x11, 0x66, 0xdd, 0x8c, 0x6e, 0xf1, 0x81, 0x3b, 0x7f, 0x1b - }; - auto mac = hmac.process("Some bogus data"); - if (memcmp(result, mac.data, hmac.digest_size()) != 0) { - FAIL(Invalid mac); - print_buffer({ mac.data, hmac.digest_size() }, -1); - } else - PASS; - } - { - I_TEST((HMAC - SHA256 | DataSize > FinalBlockDataSize)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - u8 result[] = { - 0x9b, 0xa3, 0x9e, 0xf3, 0xb4, 0x30, 0x5f, 0x6f, 0x67, 0xd0, 0xa8, 0xb0, 0xf0, 0xcb, 0x12, 0xf5, 0x85, 0xe2, 0x19, 0xba, 0x0c, 0x8b, 0xe5, 0x43, 0xf0, 0x93, 0x39, 0xa8, 0xa3, 0x07, 0xf1, 0x95 - }; - auto mac = hmac.process("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - if (memcmp(result, mac.data, hmac.digest_size()) != 0) { - FAIL(Invalid mac); - print_buffer({ mac.data, hmac.digest_size() }, -1); - } else - PASS; - } - { - I_TEST((HMAC - SHA256 | DataSize == BlockSize)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - u8 result[] = { - 0x1d, 0x90, 0xce, 0x68, 0x45, 0x0b, 0xba, 0xd6, 0xbe, 0x1c, 0xb2, 0x3a, 0xea, 0x7f, 0xac, 0x4b, 0x68, 0x08, 0xa4, 0x77, 0x81, 0x2a, 0xad, 0x5d, 0x05, 0xe2, 0x15, 0xe8, 0xf4, 0xcb, 0x06, 0xaf - }; - auto mac = hmac.process("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - if (memcmp(result, mac.data, hmac.digest_size()) != 0) { - FAIL(Invalid mac); - print_buffer({ mac.data, hmac.digest_size() }, -1); - } else - PASS; - } - { - I_TEST((HMAC - SHA256 | Reuse)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - - auto mac_0 = hmac.process("Some bogus data"); - auto mac_1 = hmac.process("Some bogus data"); - - if (memcmp(mac_0.data, mac_1.data, hmac.digest_size()) != 0) { - FAIL(Cannot reuse); - } else - PASS; - } -} - -static int sha512_tests() -{ - sha512_test_name(); - sha512_test_hash(); - return g_some_test_failed ? 1 : 0; -} - -static void sha512_test_name() -{ - I_TEST((SHA512 class name)); - Crypto::Hash::SHA512 sha; - if (sha.class_name() != "SHA512") { - FAIL(Invalid class name); - printf("%s\n", sha.class_name().characters()); - } else - PASS; -} - -static void sha512_test_hash() -{ - { - I_TEST((SHA512 Hashing | "Well hello friends")); - u8 result[] { - 0x00, 0xfe, 0x68, 0x09, 0x71, 0x0e, 0xcb, 0x2b, 0xe9, 0x58, 0x00, 0x13, 0x69, 0x6a, 0x9e, 0x9e, 0xbd, 0x09, 0x1b, 0xfe, 0x14, 0xc9, 0x13, 0x82, 0xc7, 0x40, 0x34, 0xfe, 0xca, 0xe6, 0x87, 0xcb, 0x26, 0x36, 0x92, 0xe6, 0x34, 0x94, 0x3a, 0x11, 0xe5, 0xbb, 0xb5, 0xeb, 0x8e, 0x70, 0xef, 0x64, 0xca, 0xf7, 0x21, 0xb1, 0xde, 0xf2, 0x34, 0x85, 0x6f, 0xa8, 0x56, 0xd8, 0x23, 0xa1, 0x3b, 0x29 - }; - auto digest = Crypto::Hash::SHA512::hash("Well hello friends"); - if (memcmp(result, digest.data, Crypto::Hash::SHA512::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::SHA512::digest_size() }, -1); - } else - PASS; - } - { - I_TEST((SHA512 Hashing | "")); - u8 result[] { - 0xcf, 0x83, 0xe1, 0x35, 0x7e, 0xef, 0xb8, 0xbd, 0xf1, 0x54, 0x28, 0x50, 0xd6, 0x6d, 0x80, 0x07, 0xd6, 0x20, 0xe4, 0x05, 0x0b, 0x57, 0x15, 0xdc, 0x83, 0xf4, 0xa9, 0x21, 0xd3, 0x6c, 0xe9, 0xce, 0x47, 0xd0, 0xd1, 0x3c, 0x5d, 0x85, 0xf2, 0xb0, 0xff, 0x83, 0x18, 0xd2, 0x87, 0x7e, 0xec, 0x2f, 0x63, 0xb9, 0x31, 0xbd, 0x47, 0x41, 0x7a, 0x81, 0xa5, 0x38, 0x32, 0x7a, 0xf9, 0x27, 0xda, 0x3e - }; - auto digest = Crypto::Hash::SHA512::hash(""); - if (memcmp(result, digest.data, Crypto::Hash::SHA512::digest_size()) != 0) { - FAIL(Invalid hash); - print_buffer({ digest.data, Crypto::Hash::SHA512::digest_size() }, -1); - } else - PASS; - } -} - -static void hmac_sha512_test_name() -{ - I_TEST((HMAC - SHA512 | Class name)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - if (hmac.class_name() != "HMAC-SHA512") - FAIL(Invalid class name); - else - PASS; -} - -static void hmac_sha512_test_process() -{ - { - I_TEST((HMAC - SHA512 | Basic)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - u8 result[] { - 0xeb, 0xa8, 0x34, 0x11, 0xfd, 0x5b, 0x46, 0x5b, 0xef, 0xbb, 0x67, 0x5e, 0x7d, 0xc2, 0x7c, 0x2c, 0x6b, 0xe1, 0xcf, 0xe6, 0xc7, 0xe4, 0x7d, 0xeb, 0xca, 0x97, 0xb7, 0x4c, 0xd3, 0x4d, 0x6f, 0x08, 0x9f, 0x0d, 0x3a, 0xf1, 0xcb, 0x00, 0x79, 0x78, 0x2f, 0x05, 0x8e, 0xeb, 0x94, 0x48, 0x0d, 0x50, 0x64, 0x3b, 0xca, 0x70, 0xe2, 0x69, 0x38, 0x4f, 0xe4, 0xb0, 0x49, 0x0f, 0xc5, 0x4c, 0x7a, 0xa7 - }; - auto mac = hmac.process("Some bogus data"); - if (memcmp(result, mac.data, hmac.digest_size()) != 0) { - FAIL(Invalid mac); - print_buffer({ mac.data, hmac.digest_size() }, -1); - } else - PASS; - } - { - I_TEST((HMAC - SHA512 | Reuse)); - Crypto::Authentication::HMAC hmac("Well Hello Friends"); - - auto mac_0 = hmac.process("Some bogus data"); - auto mac_1 = hmac.process("Some bogus data"); - - if (memcmp(mac_0.data, mac_1.data, hmac.digest_size()) != 0) { - FAIL(Cannot reuse); - } else - PASS; - } -} - -static int rsa_tests() -{ - rsa_test_encrypt(); - rsa_test_der_parse(); - bigint_test_number_theory(); - rsa_test_encrypt_decrypt(); - rsa_emsa_pss_test_create(); - return g_some_test_failed ? 1 : 0; -} - -static void rsa_test_encrypt() -{ - { - I_TEST((RSA RAW | Encryption)); - ByteBuffer data { "hellohellohellohellohellohellohellohellohellohellohellohello123-"_b }; - u8 result[] { 0x6f, 0x7b, 0xe2, 0xd3, 0x95, 0xf8, 0x8d, 0x87, 0x6d, 0x10, 0x5e, 0xc3, 0xcd, 0xf7, 0xbb, 0xa6, 0x62, 0x8e, 0x45, 0xa0, 0xf1, 0xe5, 0x0f, 0xdf, 0x69, 0xcb, 0xb6, 0xd5, 0x42, 0x06, 0x7d, 0x72, 0xa9, 0x5e, 0xae, 0xbf, 0xbf, 0x0f, 0xe0, 0xeb, 0x31, 0x31, 0xca, 0x8a, 0x81, 0x1e, 0xb9, 0xec, 0x6d, 0xcc, 0xb8, 0xa4, 0xac, 0xa3, 0x31, 0x05, 0xa9, 0xac, 0xc9, 0xd3, 0xe6, 0x2a, 0x18, 0xfe }; - Crypto::PK::RSA rsa( - "8126832723025844890518845777858816391166654950553329127845898924164623511718747856014227624997335860970996746552094406240834082304784428582653994490504519"_bigint, - "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint, - "65537"_bigint); - u8 buffer[rsa.output_size()]; - auto buf = Bytes { buffer, sizeof(buffer) }; - rsa.encrypt(data, buf); - if (memcmp(result, buf.data(), buf.size())) { - FAIL(Invalid encryption result); - print_buffer(buf, 16); - } else { - PASS; - } - } - { - I_TEST((RSA PKCS #1 1.5 | Encryption)); - ByteBuffer data { "hellohellohellohellohellohellohellohellohello123-"_b }; - Crypto::PK::RSA_PKCS1_EME rsa( - "8126832723025844890518845777858816391166654950553329127845898924164623511718747856014227624997335860970996746552094406240834082304784428582653994490504519"_bigint, - "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint, - "65537"_bigint); - u8 buffer[rsa.output_size()]; - auto buf = Bytes { buffer, sizeof(buffer) }; - rsa.encrypt(data, buf); - rsa.decrypt(buf, buf); - - if (memcmp(buf.data(), "hellohellohellohellohellohellohellohellohello123-", 49)) - FAIL(Invalid encryption); - else { - dbg() << "out size " << buf.size() << " values: " << StringView { (char*)buf.data(), buf.size() }; - - PASS; - } - } -} - -static void bigint_test_number_theory() -{ - { - I_TEST((Number Theory | Modular Inverse)); - if (Crypto::NumberTheory::ModularInverse(7, 87) == 25) { - PASS; - } else { - FAIL(Invalid result); - } - } - { - struct { - Crypto::UnsignedBigInteger base; - Crypto::UnsignedBigInteger exp; - Crypto::UnsignedBigInteger mod; - Crypto::UnsignedBigInteger expected; - } mod_pow_tests[] = { - { "2988348162058574136915891421498819466320163312926952423791023078876139"_bigint, "2351399303373464486466122544523690094744975233415544072992656881240319"_bigint, "10000"_bigint, "3059"_bigint }, - { "24231"_bigint, "12448"_bigint, "14679"_bigint, "4428"_bigint }, - { "1005404"_bigint, "8352654"_bigint, "8161408"_bigint, "2605696"_bigint }, - { "3665005778"_bigint, "3244425589"_bigint, "565668506"_bigint, "524766494"_bigint }, - { "10662083169959689657"_bigint, "11605678468317533000"_bigint, "1896834583057209739"_bigint, "1292743154593945858"_bigint }, - { "99667739213529524852296932424683448520"_bigint, "123394910770101395416306279070921784207"_bigint, "238026722756504133786938677233768788719"_bigint, "197165477545023317459748215952393063201"_bigint }, - { "49368547511968178788919424448914214709244872098814465088945281575062739912239"_bigint, "25201856190991298572337188495596990852134236115562183449699512394891190792064"_bigint, "45950460777961491021589776911422805972195170308651734432277141467904883064645"_bigint, "39917885806532796066922509794537889114718612292469285403012781055544152450051"_bigint }, - { "48399385336454791246880286907257136254351739111892925951016159217090949616810"_bigint, "5758661760571644379364752528081901787573279669668889744323710906207949658569"_bigint, "32812120644405991429173950312949738783216437173380339653152625840449006970808"_bigint, "7948464125034399875323770213514649646309423451213282653637296324080400293584"_bigint }, - }; - - for (auto test_case : mod_pow_tests) { - I_TEST((Number Theory | Modular Power)); - auto actual = Crypto::NumberTheory::ModularPower( - test_case.base, test_case.exp, test_case.mod); - - if (actual == test_case.expected) { - PASS; - } else { - FAIL(Wrong result); - printf("b: %s\ne: %s\nm: %s\nexpect: %s\nactual: %s\n", - test_case.base.to_base10().characters(), test_case.exp.to_base10().characters(), test_case.mod.to_base10().characters(), test_case.expected.to_base10().characters(), actual.to_base10().characters()); - } - } - } - { - struct { - Crypto::UnsignedBigInteger candidate; - bool expected_result; - } primality_tests[] = { - { "1180591620717411303424"_bigint, false }, // 2**70 - { "620448401733239439360000"_bigint, false }, // 25! - { "953962166440690129601298432"_bigint, false }, // 12**25 - { "620448401733239439360000"_bigint, false }, // 25! - { "147926426347074375"_bigint, false }, // 35! / 2**32 - { "340282366920938429742726440690708343523"_bigint, false }, // 2 factors near 2^64 - { "73"_bigint, true }, - { "6967"_bigint, true }, - { "787649"_bigint, true }, - { "73513949"_bigint, true }, - { "6691236901"_bigint, true }, - { "741387182759"_bigint, true }, - { "67466615915827"_bigint, true }, - { "9554317039214687"_bigint, true }, - { "533344522150170391"_bigint, true }, - { "18446744073709551557"_bigint, true }, // just below 2**64 - }; - - for (auto test_case : primality_tests) { - I_TEST((Number Theory | Primality)); - bool actual_result = Crypto::NumberTheory::is_probably_prime(test_case.candidate); - if (test_case.expected_result == actual_result) { - PASS; - } else { - FAIL(Wrong primality guess); - printf("The number %s is %sa prime, but the test said it is %sa prime!\n", - test_case.candidate.to_base10().characters(), test_case.expected_result ? "" : "not ", actual_result ? "" : "not "); - } - } - } - { - struct { - Crypto::UnsignedBigInteger min; - Crypto::UnsignedBigInteger max; - } primality_tests[] = { - { "1"_bigint, "1000000"_bigint }, - { "10000000000"_bigint, "20000000000"_bigint }, - { "1000"_bigint, "200000000000000000"_bigint }, - { "200000000000000000"_bigint, "200000000000010000"_bigint }, - }; - - for (auto test_case : primality_tests) { - I_TEST((Number Theory | Random numbers)); - auto actual_result = Crypto::NumberTheory::random_number(test_case.min, test_case.max); - if (actual_result < test_case.min) { - FAIL(Too small); - printf("The generated number %s is smaller than the requested minimum %s. (max = %s)\n", actual_result.to_base10().characters(), test_case.min.to_base10().characters(), test_case.max.to_base10().characters()); - } else if (!(actual_result < test_case.max)) { - FAIL(Too large); - printf("The generated number %s is larger-or-equal to the requested maximum %s. (min = %s)\n", actual_result.to_base10().characters(), test_case.max.to_base10().characters(), test_case.min.to_base10().characters()); - } else { - PASS; - } - } - } - { - I_TEST((Number Theory | Random distribution)); - auto actual_result = Crypto::NumberTheory::random_number( - "1"_bigint, - "100000000000000000000000000000"_bigint); // 10**29 - if (actual_result < "100000000000000000000"_bigint) { // 10**20 - FAIL(Too small); - printf("The generated number %s is extremely small. This *can* happen by pure chance, but should happen only once in a billion times. So it's probably an error.\n", actual_result.to_base10().characters()); - } else if ("99999999900000000000000000000"_bigint < actual_result) { // 10**29 - 10**20 - FAIL(Too large); - printf("The generated number %s is extremely large. This *can* happen by pure chance, but should happen only once in a billion times. So it's probably an error.\n", actual_result.to_base10().characters()); - } else { - PASS; - } - } -} - -static void rsa_emsa_pss_test_create() -{ - { - // This is a template validity test - I_TEST((RSA EMSA_PSS | Construction)); - Crypto::PK::RSA rsa; - Crypto::PK::RSA_EMSA_PSS rsa_esma_pss(rsa); - PASS; - } -} - -static void rsa_test_der_parse() -{ - I_TEST((RSA | ASN1 DER / PEM encoded Key import)); - auto privkey = R"(-----BEGIN RSA PRIVATE KEY----- -MIIBOgIBAAJBAJsrIYHxs1YL9tpfodaWs1lJoMdF4kgFisUFSj6nvBhJUlmBh607AlgTaX0E -DGPYycXYGZ2n6rqmms5lpDXBpUcCAwEAAQJAUNpPkmtEHDENxsoQBUXvXDYeXdePSiIBJhpU -joNOYoR5R9z5oX2cpcyykQ58FC2vKKg+x8N6xczG7qO95tw5UQIhAN354CP/FA+uTeJ6KJ+i -zCBCl58CjNCzO0s5HTc56el5AiEAsvPKXo5/9gS/S4UzDRP6abq7GreixTfjR8LXidk3FL8C -IQCTjYI861Y+hjMnlORkGSdvWlTHUj6gjEOh4TlWeJzQoQIgAxMZOQKtxCZUuxFwzRq4xLRG -nrDlBQpuxz7bwSyQO7UCIHrYMnDohgNbwtA5ZpW3H1cKKQQvueWm6sxW9P5sUrZ3 ------END RSA PRIVATE KEY-----)"; - - Crypto::PK::RSA rsa(privkey); - if (rsa.public_key().public_exponent() == 65537) { - if (rsa.private_key().private_exponent() == "4234603516465654167360850580101327813936403862038934287300450163438938741499875303761385527882335478349599685406941909381269804396099893549838642251053393"_bigint) { - PASS; - } else - FAIL(Invalid private exponent); - } else { - FAIL(Invalid public exponent); - } -} - -static void rsa_test_encrypt_decrypt() -{ - I_TEST((RSA | Encrypt)); - dbgln(" creating rsa object"); - Crypto::PK::RSA rsa( - "9527497237087650398000977129550904920919162360737979403539302312977329868395261515707123424679295515888026193056908173564681660256268221509339074678416049"_bigint, - "39542231845947188736992321577701849924317746648774438832456325878966594812143638244746284968851807975097653255909707366086606867657273809465195392910913"_bigint, - "65537"_bigint); - dbg() << "Output size: " << rsa.output_size(); - - u8 enc_buffer[rsa.output_size()]; - u8 dec_buffer[rsa.output_size()]; - - auto enc = Bytes { enc_buffer, rsa.output_size() }; - auto dec = Bytes { dec_buffer, rsa.output_size() }; - - enc.overwrite(0, "WellHelloFriendsWellHelloFriendsWellHelloFriendsWellHelloFriends", 64); - - rsa.encrypt(enc, dec); - rsa.decrypt(dec, enc); - - dbg() << "enc size " << enc.size() << " dec size " << dec.size(); - - if (memcmp(enc.data(), "WellHelloFriendsWellHelloFriendsWellHelloFriendsWellHelloFriends", 64) != 0) { - FAIL(Could not encrypt then decrypt); - } else { - PASS; - } -} - -static int tls_tests() -{ - tls_test_client_hello(); - return g_some_test_failed ? 1 : 0; -} - -static void tls_test_client_hello() -{ - I_TEST((TLS | Connect and Data Transfer)); - Core::EventLoop loop; - RefPtr tls = TLS::TLSv12::construct(nullptr); - tls->set_root_certificates(s_root_ca_certificates); - bool sent_request = false; - ByteBuffer contents = ByteBuffer::create_uninitialized(0); - tls->on_tls_ready_to_write = [&](TLS::TLSv12& tls) { - if (sent_request) - return; - sent_request = true; - if (!tls.write("GET / HTTP/1.1\r\nHost: "_b)) { - FAIL(write(0) failed); - loop.quit(0); - } - auto* the_server = server ?: DEFAULT_SERVER; - if (!tls.write(StringView(the_server).bytes())) { - FAIL(write(1) failed); - loop.quit(0); - } - if (!tls.write("\r\nConnection : close\r\n\r\n"_b)) { - FAIL(write(2) failed); - loop.quit(0); - } - }; - tls->on_tls_ready_to_read = [&](TLS::TLSv12& tls) { - auto data = tls.read(); - if (!data.has_value()) { - FAIL(No data received); - loop.quit(1); - } else { - // print_buffer(data.value(), 16); - contents.append(data.value().data(), data.value().size()); - } - }; - tls->on_tls_finished = [&] { - PASS; - loop.quit(0); - }; - tls->on_tls_error = [&](TLS::AlertDescription) { - FAIL(Connection failure); - loop.quit(1); - }; - if (!tls->connect(server ?: DEFAULT_SERVER, port)) { - FAIL(connect() failed); - return; - } - loop.exec(); -} - -static int adler32_tests() -{ - auto do_test = [](ReadonlyBytes input, u32 expected_result) { - I_TEST((CRC32)); - - auto pass = Crypto::Checksum::Adler32(input).digest() == expected_result; - - if (pass) { - PASS; - } else { - FAIL(Incorrect Result); - } - }; - - do_test(String("").bytes(), 0x1); - do_test(String("a").bytes(), 0x00620062); - do_test(String("abc").bytes(), 0x024d0127); - do_test(String("message digest").bytes(), 0x29750586); - do_test(String("abcdefghijklmnopqrstuvwxyz").bytes(), 0x90860b20); - - return g_some_test_failed ? 1 : 0; -} - -static int crc32_tests() -{ - auto do_test = [](ReadonlyBytes input, u32 expected_result) { - I_TEST((Adler32)); - - auto pass = Crypto::Checksum::CRC32(input).digest() == expected_result; - - if (pass) { - PASS; - } else { - FAIL(Incorrect Result); - } - }; - - do_test(String("").bytes(), 0x0); - do_test(String("The quick brown fox jumps over the lazy dog").bytes(), 0x414FA339); - do_test(String("various CRC algorithms input data").bytes(), 0x9BD366AE); - - return g_some_test_failed ? 1 : 0; -} - -static int bigint_tests() -{ - bigint_test_fibo500(); - bigint_addition_edgecases(); - bigint_subtraction(); - bigint_multiplication(); - bigint_division(); - bigint_base10(); - bigint_import_export(); - bigint_bitwise(); - - bigint_test_signed_fibo500(); - bigint_signed_addition_edgecases(); - bigint_signed_subtraction(); - bigint_signed_multiplication(); - bigint_signed_division(); - bigint_signed_base10(); - bigint_signed_import_export(); - bigint_signed_bitwise(); - - return g_some_test_failed ? 1 : 0; -} - -static Crypto::UnsignedBigInteger bigint_fibonacci(size_t n) -{ - Crypto::UnsignedBigInteger num1(0); - Crypto::UnsignedBigInteger num2(1); - for (size_t i = 0; i < n; ++i) { - Crypto::UnsignedBigInteger t = num1.plus(num2); - num2 = num1; - num1 = t; - } - return num1; -} - -static Crypto::SignedBigInteger bigint_signed_fibonacci(size_t n) -{ - Crypto::SignedBigInteger num1(0); - Crypto::SignedBigInteger num2(1); - for (size_t i = 0; i < n; ++i) { - Crypto::SignedBigInteger t = num1.plus(num2); - num2 = num1; - num1 = t; - } - return num1; -} -static void bigint_test_fibo500() -{ - { - I_TEST((BigInteger | Fibonacci500)); - bool pass = (bigint_fibonacci(500).words() == AK::Vector { 315178285, 505575602, 1883328078, 125027121, 3649625763, 347570207, 74535262, 3832543808, 2472133297, 1600064941, 65273441 }); - - if (pass) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_addition_edgecases() -{ - { - I_TEST((BigInteger | Edge Cases)); - Crypto::UnsignedBigInteger num1; - Crypto::UnsignedBigInteger num2(70); - Crypto::UnsignedBigInteger num3 = num1.plus(num2); - bool pass = (num3 == num2); - pass &= (num1 == Crypto::UnsignedBigInteger(0)); - - if (pass) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Borrow with zero)); - Crypto::UnsignedBigInteger num1({ UINT32_MAX - 3, UINT32_MAX }); - Crypto::UnsignedBigInteger num2({ UINT32_MAX - 2, 0 }); - if (num1.plus(num2).words() == Vector { 4294967289, 0, 1 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_subtraction() -{ - { - I_TEST((BigInteger | Simple Subtraction 1)); - Crypto::UnsignedBigInteger num1(80); - Crypto::UnsignedBigInteger num2(70); - - if (num1.minus(num2) == Crypto::UnsignedBigInteger(10)) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Simple Subtraction 2)); - Crypto::UnsignedBigInteger num1(50); - Crypto::UnsignedBigInteger num2(70); - - if (num1.minus(num2).is_invalid()) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Subtraction with borrow)); - Crypto::UnsignedBigInteger num1(UINT32_MAX); - Crypto::UnsignedBigInteger num2(1); - Crypto::UnsignedBigInteger num3 = num1.plus(num2); - Crypto::UnsignedBigInteger result = num3.minus(num2); - if (result == num1) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Subtraction with large numbers)); - Crypto::UnsignedBigInteger num1 = bigint_fibonacci(343); - Crypto::UnsignedBigInteger num2 = bigint_fibonacci(218); - Crypto::UnsignedBigInteger result = num1.minus(num2); - if ((result.plus(num2) == num1) - && (result.words() == Vector { 811430588, 2958904896, 1130908877, 2830569969, 3243275482, 3047460725, 774025231, 7990 })) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Subtraction with large numbers 2)); - Crypto::UnsignedBigInteger num1(Vector { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 }); - Crypto::UnsignedBigInteger num2(Vector { 4196414175, 1117247942, 1123294122, 191895498, 3347106536, 16 }); - Crypto::UnsignedBigInteger result = num1.minus(num2); - // this test only verifies that we don't crash on an assertion - PASS; - } - { - I_TEST((BigInteger | Subtraction Regression 1)); - auto num = Crypto::UnsignedBigInteger { 1 }.shift_left(256); - if (num.minus(1).words() == Vector { 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 4294967295, 0 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_multiplication() -{ - { - I_TEST((BigInteger | Simple Multiplication)); - Crypto::UnsignedBigInteger num1(8); - Crypto::UnsignedBigInteger num2(251); - Crypto::UnsignedBigInteger result = num1.multiplied_by(num2); - if (result.words() == Vector { 2008 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Multiplications with big numbers 1)); - Crypto::UnsignedBigInteger num1 = bigint_fibonacci(200); - Crypto::UnsignedBigInteger num2(12345678); - Crypto::UnsignedBigInteger result = num1.multiplied_by(num2); - if (result.words() == Vector { 669961318, 143970113, 4028714974, 3164551305, 1589380278, 2 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Multiplications with big numbers 2)); - Crypto::UnsignedBigInteger num1 = bigint_fibonacci(200); - Crypto::UnsignedBigInteger num2 = bigint_fibonacci(341); - Crypto::UnsignedBigInteger result = num1.multiplied_by(num2); - if (result.words() == Vector { 3017415433, 2741793511, 1957755698, 3731653885, 3154681877, 785762127, 3200178098, 4260616581, 529754471, 3632684436, 1073347813, 2516430 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} -static void bigint_division() -{ - { - I_TEST((BigInteger | Simple Division)); - Crypto::UnsignedBigInteger num1(27194); - Crypto::UnsignedBigInteger num2(251); - auto result = num1.divided_by(num2); - Crypto::UnsignedDivisionResult expected = { Crypto::UnsignedBigInteger(108), Crypto::UnsignedBigInteger(86) }; - if (result.quotient == expected.quotient && result.remainder == expected.remainder) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Division with big numbers)); - Crypto::UnsignedBigInteger num1 = bigint_fibonacci(386); - Crypto::UnsignedBigInteger num2 = bigint_fibonacci(238); - auto result = num1.divided_by(num2); - Crypto::UnsignedDivisionResult expected = { - Crypto::UnsignedBigInteger(Vector { 2300984486, 2637503534, 2022805584, 107 }), - Crypto::UnsignedBigInteger(Vector { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 }) - }; - if (result.quotient == expected.quotient && result.remainder == expected.remainder) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | Combined test)); - auto num1 = bigint_fibonacci(497); - auto num2 = bigint_fibonacci(238); - auto div_result = num1.divided_by(num2); - if (div_result.quotient.multiplied_by(num2).plus(div_result.remainder) == num1) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_base10() -{ - { - I_TEST((BigInteger | From String)); - auto result = Crypto::UnsignedBigInteger::from_base10("57195071295721390579057195715793"); - if (result.words() == Vector { 3806301393, 954919431, 3879607298, 721 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((BigInteger | To String)); - auto result = Crypto::UnsignedBigInteger { Vector { 3806301393, 954919431, 3879607298, 721 } }.to_base10(); - if (result == "57195071295721390579057195715793") { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_import_export() -{ - { - I_TEST((BigInteger | BigEndian Decode / Encode roundtrip)); - u8 random_bytes[128]; - u8 target_buffer[128]; - AK::fill_with_random(random_bytes, 128); - auto encoded = Crypto::UnsignedBigInteger::import_data(random_bytes, 128); - encoded.export_data({ target_buffer, 128 }); - if (memcmp(target_buffer, random_bytes, 128) != 0) - FAIL(Could not roundtrip); - else - PASS; - } - { - I_TEST((BigInteger | BigEndian Encode / Decode roundtrip)); - u8 target_buffer[128]; - auto encoded = "12345678901234567890"_bigint; - auto size = encoded.export_data({ target_buffer, 128 }); - auto decoded = Crypto::UnsignedBigInteger::import_data(target_buffer, size); - if (encoded != decoded) - FAIL(Could not roundtrip); - else - PASS; - } - { - I_TEST((BigInteger | BigEndian Import)); - auto number = Crypto::UnsignedBigInteger::import_data("hello"); - if (number == "448378203247"_bigint) { - PASS; - } else { - FAIL(Invalid value); - } - } - { - I_TEST((BigInteger | BigEndian Export)); - auto number = "448378203247"_bigint; - char exported[8] { 0 }; - auto exported_length = number.export_data({ exported, 8 }, true); - if (exported_length == 5 && memcmp(exported + 3, "hello", 5) == 0) { - PASS; - } else { - FAIL(Invalid value); - print_buffer({ exported - exported_length + 8, exported_length }, -1); - } - } -} - -static void bigint_bitwise() -{ - { - I_TEST((BigInteger | Basic bitwise or)); - auto num1 = "1234567"_bigint; - auto num2 = "1234567"_bigint; - if (num1.bitwise_or(num2) == num1) { - PASS; - } else { - FAIL(Invalid value); - } - } - { - I_TEST((BigInteger | Bitwise or handles different lengths)); - auto num1 = "1234567"_bigint; - auto num2 = "123456789012345678901234567890"_bigint; - auto expected = "123456789012345678901234622167"_bigint; - auto result = num1.bitwise_or(num2); - if (result == expected) { - PASS; - } else { - FAIL(Invalid value); - } - } - { - I_TEST((BigInteger | Basic bitwise and)); - auto num1 = "1234567"_bigint; - auto num2 = "1234561"_bigint; - if (num1.bitwise_and(num2) == "1234561"_bigint) { - PASS; - } else { - FAIL(Invalid value); - } - } - { - I_TEST((BigInteger | Bitwise and handles different lengths)); - auto num1 = "1234567"_bigint; - auto num2 = "123456789012345678901234567890"_bigint; - if (num1.bitwise_and(num2) == "1180290"_bigint) { - PASS; - } else { - FAIL(Invalid value); - } - } - { - I_TEST((BigInteger | Basic bitwise xor)); - auto num1 = "1234567"_bigint; - auto num2 = "1234561"_bigint; - if (num1.bitwise_xor(num2) == 6) { - PASS; - } else { - FAIL(Invalid value); - } - } - { - I_TEST((BigInteger | Bitwise xor handles different lengths)); - auto num1 = "1234567"_bigint; - auto num2 = "123456789012345678901234567890"_bigint; - if (num1.bitwise_xor(num2) == "123456789012345678901233441877"_bigint) { - PASS; - } else { - FAIL(Invalid value); - } - } -} - -static void bigint_test_signed_fibo500() -{ - { - I_TEST((Signed BigInteger | Fibonacci500)); - bool pass = (bigint_signed_fibonacci(500).unsigned_value().words() == AK::Vector { 315178285, 505575602, 1883328078, 125027121, 3649625763, 347570207, 74535262, 3832543808, 2472133297, 1600064941, 65273441 }); - - if (pass) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_signed_addition_edgecases() -{ - { - I_TEST((Signed BigInteger | Borrow with zero)); - Crypto::SignedBigInteger num1 { Crypto::UnsignedBigInteger { { UINT32_MAX - 3, UINT32_MAX } }, false }; - Crypto::SignedBigInteger num2 { Crypto::UnsignedBigInteger { UINT32_MAX - 2 }, false }; - if (num1.plus(num2).unsigned_value().words() == Vector { 4294967289, 0, 1 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Addition to other sign)); - Crypto::SignedBigInteger num1 = INT32_MAX; - Crypto::SignedBigInteger num2 = num1; - num2.negate(); - if (num1.plus(num2) == Crypto::SignedBigInteger { 0 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_signed_subtraction() -{ - { - I_TEST((Signed BigInteger | Simple Subtraction 1)); - Crypto::SignedBigInteger num1(80); - Crypto::SignedBigInteger num2(70); - - if (num1.minus(num2) == Crypto::SignedBigInteger(10)) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Simple Subtraction 2)); - Crypto::SignedBigInteger num1(50); - Crypto::SignedBigInteger num2(70); - - if (num1.minus(num2) == Crypto::SignedBigInteger { -20 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Subtraction with borrow)); - Crypto::SignedBigInteger num1(Crypto::UnsignedBigInteger { UINT32_MAX }); - Crypto::SignedBigInteger num2(1); - Crypto::SignedBigInteger num3 = num1.plus(num2); - Crypto::SignedBigInteger result = num2.minus(num3); - num1.negate(); - if (result == num1) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Subtraction with large numbers)); - Crypto::SignedBigInteger num1 = bigint_signed_fibonacci(343); - Crypto::SignedBigInteger num2 = bigint_signed_fibonacci(218); - Crypto::SignedBigInteger result = num2.minus(num1); - auto expected = Crypto::UnsignedBigInteger { Vector { 811430588, 2958904896, 1130908877, 2830569969, 3243275482, 3047460725, 774025231, 7990 } }; - if ((result.plus(num1) == num2) - && (result.unsigned_value() == expected)) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Subtraction with large numbers 2)); - Crypto::SignedBigInteger num1(Crypto::UnsignedBigInteger { Vector { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 } }); - Crypto::SignedBigInteger num2(Crypto::UnsignedBigInteger { Vector { 4196414175, 1117247942, 1123294122, 191895498, 3347106536, 16 } }); - Crypto::SignedBigInteger result = num1.minus(num2); - // this test only verifies that we don't crash on an assertion - PASS; - } -} - -static void bigint_signed_multiplication() -{ - { - I_TEST((Signed BigInteger | Simple Multiplication)); - Crypto::SignedBigInteger num1(8); - Crypto::SignedBigInteger num2(-251); - Crypto::SignedBigInteger result = num1.multiplied_by(num2); - if (result == Crypto::SignedBigInteger { -2008 }) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Multiplications with big numbers 1)); - Crypto::SignedBigInteger num1 = bigint_signed_fibonacci(200); - Crypto::SignedBigInteger num2(-12345678); - Crypto::SignedBigInteger result = num1.multiplied_by(num2); - if (result.unsigned_value().words() == Vector { 669961318, 143970113, 4028714974, 3164551305, 1589380278, 2 } && result.is_negative()) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Multiplications with big numbers 2)); - Crypto::SignedBigInteger num1 = bigint_signed_fibonacci(200); - Crypto::SignedBigInteger num2 = bigint_signed_fibonacci(341); - num1.negate(); - Crypto::SignedBigInteger result = num1.multiplied_by(num2); - if (result.unsigned_value().words() == Vector { 3017415433, 2741793511, 1957755698, 3731653885, 3154681877, 785762127, 3200178098, 4260616581, 529754471, 3632684436, 1073347813, 2516430 } && result.is_negative()) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} -static void bigint_signed_division() -{ - { - I_TEST((Signed BigInteger | Simple Division)); - Crypto::SignedBigInteger num1(27194); - Crypto::SignedBigInteger num2(-251); - auto result = num1.divided_by(num2); - Crypto::SignedDivisionResult expected = { Crypto::SignedBigInteger(-108), Crypto::SignedBigInteger(86) }; - if (result.quotient == expected.quotient && result.remainder == expected.remainder) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Division with big numbers)); - Crypto::SignedBigInteger num1 = bigint_signed_fibonacci(386); - Crypto::SignedBigInteger num2 = bigint_signed_fibonacci(238); - num1.negate(); - auto result = num1.divided_by(num2); - Crypto::SignedDivisionResult expected = { - Crypto::SignedBigInteger(Crypto::UnsignedBigInteger { Vector { 2300984486, 2637503534, 2022805584, 107 } }, true), - Crypto::SignedBigInteger(Crypto::UnsignedBigInteger { Vector { 1483061863, 446680044, 1123294122, 191895498, 3347106536, 16, 0, 0, 0 } }, true) - }; - if (result.quotient == expected.quotient && result.remainder == expected.remainder) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | Combined test)); - auto num1 = bigint_signed_fibonacci(497); - auto num2 = bigint_signed_fibonacci(238); - num1.negate(); - auto div_result = num1.divided_by(num2); - if (div_result.quotient.multiplied_by(num2).plus(div_result.remainder) == num1) { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_signed_base10() -{ - { - I_TEST((Signed BigInteger | From String)); - auto result = Crypto::SignedBigInteger::from_base10("-57195071295721390579057195715793"); - if (result.unsigned_value().words() == Vector { 3806301393, 954919431, 3879607298, 721 } && result.is_negative()) { - PASS; - } else { - FAIL(Incorrect Result); - } - } - { - I_TEST((Signed BigInteger | To String)); - auto result = Crypto::SignedBigInteger { Crypto::UnsignedBigInteger { Vector { 3806301393, 954919431, 3879607298, 721 } }, true }.to_base10(); - if (result == "-57195071295721390579057195715793") { - PASS; - } else { - FAIL(Incorrect Result); - } - } -} - -static void bigint_signed_import_export() -{ - { - I_TEST((Signed BigInteger | BigEndian Decode / Encode roundtrip)); - u8 random_bytes[129]; - u8 target_buffer[129]; - random_bytes[0] = 1; - AK::fill_with_random(random_bytes + 1, 128); - auto encoded = Crypto::SignedBigInteger::import_data(random_bytes, 129); - encoded.export_data({ target_buffer, 129 }); - if (memcmp(target_buffer, random_bytes, 129) != 0) - FAIL(Could not roundtrip); - else - PASS; - } - { - I_TEST((Signed BigInteger | BigEndian Encode / Decode roundtrip)); - u8 target_buffer[128]; - auto encoded = "-12345678901234567890"_sbigint; - auto size = encoded.export_data({ target_buffer, 128 }); - auto decoded = Crypto::SignedBigInteger::import_data(target_buffer, size); - if (encoded != decoded) - FAIL(Could not roundtrip); - else - PASS; - } -} - -static void bigint_signed_bitwise() -{ - { - I_TEST((Signed BigInteger | Bitwise or handles sign)); - auto num1 = "-1234567"_sbigint; - auto num2 = "1234567"_sbigint; - if (num1.bitwise_or(num2) == num1) { - PASS; - } else { - FAIL(Invalid value); - } - } -} diff --git a/Userland/test-gfx-font.cpp b/Userland/test-gfx-font.cpp deleted file mode 100644 index e1bf939bb5..0000000000 --- a/Userland/test-gfx-font.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include - -static void test_fontdatabase_get_by_name() -{ - const char* name = "Liza 10 400"; - auto& font_database = Gfx::FontDatabase::the(); - assert(!font_database.get_by_name(name)->name().is_null()); -} - -static void test_fontdatabase_for_each_font() -{ - auto& font_database = Gfx::FontDatabase::the(); - font_database.for_each_font([&](const Gfx::Font& font) { - assert(!font.name().is_null()); - assert(!font.qualified_name().is_null()); - assert(!font.family().is_null()); - assert(font.glyph_count() > 0); - }); -} - -static void test_default_font() -{ - assert(!Gfx::FontDatabase::default_font().name().is_null()); -} - -static void test_default_fixed_width_font() -{ - assert(!Gfx::FontDatabase::default_font().name().is_null()); -} - -static void test_default_bold_fixed_width_font() -{ - assert(!Gfx::FontDatabase::default_font().name().is_null()); -} - -static void test_default_bold_font() -{ - assert(!Gfx::FontDatabase::default_font().name().is_null()); -} - -static void test_clone() -{ - u8 glyph_height = 1; - u8 glyph_width = 1; - auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); - - auto new_font = font->clone(); - assert(!new_font->name().is_null()); - assert(!new_font->qualified_name().is_null()); - assert(!new_font->family().is_null()); - assert(new_font->glyph_count() > 0); -} - -static void test_set_name() -{ - u8 glyph_height = 1; - u8 glyph_width = 1; - auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); - - const char* name = "my newly created font"; - font->set_name(name); - - assert(!font->qualified_name().is_null()); - assert(!font->qualified_name().contains(name)); -} - -static void test_set_type() -{ - u8 glyph_height = 1; - u8 glyph_width = 1; - auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); - - auto type = Gfx::FontTypes::Default; - font->set_type(type); - - assert(font->type() == type); -} - -static void test_width() -{ - u8 glyph_height = 1; - u8 glyph_width = 1; - auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); - - assert(font->width("A") == glyph_width); -} - -static void test_glyph_or_emoji_width() -{ - u8 glyph_height = 1; - u8 glyph_width = 1; - auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); - font->set_type(Gfx::FontTypes::Default); - - assert(font->glyph_or_emoji_width(0)); -} - -static void test_load_from_file() -{ - auto font = Gfx::BitmapFont::load_from_file("/res/fonts/PebbletonBold14.font"); - assert(!font->name().is_null()); -} - -static void test_write_to_file() -{ - u8 glyph_height = 1; - u8 glyph_width = 1; - auto font = Gfx::BitmapFont::create(glyph_height, glyph_width, true, Gfx::FontTypes::Default); - - char path[] = "/tmp/new.font.XXXXXX"; - assert(mkstemp(path) != -1); - assert(font->write_to_file(path)); - unlink(path); -} - -int main(int, char**) -{ -#define RUNTEST(x) \ - { \ - printf("Running " #x " ...\n"); \ - x(); \ - printf("Success!\n"); \ - } - RUNTEST(test_fontdatabase_get_by_name); - RUNTEST(test_fontdatabase_for_each_font); - RUNTEST(test_default_font); - RUNTEST(test_default_fixed_width_font); - RUNTEST(test_default_bold_fixed_width_font); - RUNTEST(test_default_bold_font); - RUNTEST(test_clone); - RUNTEST(test_set_name); - RUNTEST(test_set_type); - RUNTEST(test_width); - RUNTEST(test_glyph_or_emoji_width); - RUNTEST(test_load_from_file); - RUNTEST(test_write_to_file); - printf("PASS\n"); - - return 0; -} diff --git a/Userland/test-js.cpp b/Userland/test-js.cpp deleted file mode 100644 index 9245d06492..0000000000 --- a/Userland/test-js.cpp +++ /dev/null @@ -1,765 +0,0 @@ -/* - * Copyright (c) 2020, Matthew Olsson - * Copyright (c) 2020, Linus Groh - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TOP_LEVEL_TEST_NAME "__$$TOP_LEVEL$$__" - -RefPtr vm; - -static bool collect_on_every_allocation = false; -static String currently_running_test; - -enum class TestResult { - Pass, - Fail, - Skip, -}; - -struct JSTest { - String name; - TestResult result; - String details; -}; - -struct JSSuite { - String name; - // A failed test takes precedence over a skipped test, which both have - // precedence over a passed test - TestResult most_severe_test_result { TestResult::Pass }; - Vector tests {}; -}; - -struct ParserError { - JS::Parser::Error error; - String hint; -}; - -struct JSFileResult { - String name; - Optional error {}; - double time_taken { 0 }; - // A failed test takes precedence over a skipped test, which both have - // precedence over a passed test - TestResult most_severe_test_result { TestResult::Pass }; - Vector suites {}; - Vector logged_messages {}; -}; - -struct JSTestRunnerCounts { - int tests_failed { 0 }; - int tests_passed { 0 }; - int tests_skipped { 0 }; - int suites_failed { 0 }; - int suites_passed { 0 }; - int files_total { 0 }; -}; - -class TestRunnerGlobalObject : public JS::GlobalObject { -public: - TestRunnerGlobalObject(); - virtual ~TestRunnerGlobalObject() override; - - virtual void initialize() override; - -private: - virtual const char* class_name() const override { return "TestRunnerGlobalObject"; } - - JS_DECLARE_NATIVE_FUNCTION(is_strict_mode); - JS_DECLARE_NATIVE_FUNCTION(can_parse_source); -}; - -class TestRunner { -public: - static TestRunner* the() - { - return s_the; - } - - TestRunner(String test_root, bool print_times) - : m_test_root(move(test_root)) - , m_print_times(print_times) - { - ASSERT(!s_the); - s_the = this; - } - - void run(); - - const JSTestRunnerCounts& counts() const { return m_counts; } - -protected: - static TestRunner* s_the; - - virtual Vector get_test_paths() const; - virtual JSFileResult run_file_test(const String& test_path); - void print_file_result(const JSFileResult& file_result) const; - void print_test_results() const; - - String m_test_root; - bool m_print_times; - - double m_total_elapsed_time_in_ms { 0 }; - JSTestRunnerCounts m_counts; - - RefPtr m_test_program; -}; - -TestRunner* TestRunner::s_the = nullptr; - -TestRunnerGlobalObject::TestRunnerGlobalObject() -{ -} - -TestRunnerGlobalObject::~TestRunnerGlobalObject() -{ -} - -void TestRunnerGlobalObject::initialize() -{ - JS::GlobalObject::initialize(); - static FlyString global_property_name { "global" }; - static FlyString is_strict_mode_property_name { "isStrictMode" }; - static FlyString can_parse_source_property_name { "canParseSource" }; - define_property(global_property_name, this, JS::Attribute::Enumerable); - define_native_function(is_strict_mode_property_name, is_strict_mode); - define_native_function(can_parse_source_property_name, can_parse_source); -} - -JS_DEFINE_NATIVE_FUNCTION(TestRunnerGlobalObject::is_strict_mode) -{ - return JS::Value(vm.in_strict_mode()); -} - -JS_DEFINE_NATIVE_FUNCTION(TestRunnerGlobalObject::can_parse_source) -{ - auto source = vm.argument(0).to_string(global_object); - if (vm.exception()) - return {}; - auto parser = JS::Parser(JS::Lexer(source)); - parser.parse_program(); - return JS::Value(!parser.has_errors()); -} - -static void cleanup_and_exit() -{ - // Clear the taskbar progress. -#ifdef __serenity__ - warn("\033]9;-1;\033\\"); -#endif - exit(1); -} - -static void handle_sigabrt(int) -{ - dbgln("test-js: SIGABRT received, cleaning up."); - cleanup_and_exit(); -} - -static double get_time_in_ms() -{ - struct timeval tv1; - auto return_code = gettimeofday(&tv1, nullptr); - ASSERT(return_code >= 0); - return static_cast(tv1.tv_sec) * 1000.0 + static_cast(tv1.tv_usec) / 1000.0; -} - -template -static void iterate_directory_recursively(const String& directory_path, Callback callback) -{ - Core::DirIterator directory_iterator(directory_path, Core::DirIterator::Flags::SkipDots); - - while (directory_iterator.has_next()) { - auto file_path = String::formatted("{}/{}", directory_path, directory_iterator.next_path()); - if (Core::File::is_directory(file_path)) { - iterate_directory_recursively(file_path, callback); - } else { - callback(move(file_path)); - } - } -} - -Vector TestRunner::get_test_paths() const -{ - Vector paths; - iterate_directory_recursively(m_test_root, [&](const String& file_path) { - if (!file_path.ends_with("test-common.js")) - paths.append(file_path); - }); - quick_sort(paths); - return paths; -} - -void TestRunner::run() -{ - size_t progress_counter = 0; - auto test_paths = get_test_paths(); - for (auto& path : test_paths) { - ++progress_counter; - print_file_result(run_file_test(path)); -#ifdef __serenity__ - warn("\033]9;{};{};\033\\", progress_counter, test_paths.size()); -#endif - } - -#ifdef __serenity__ - warn("\033]9;-1;\033\\"); -#endif - - print_test_results(); -} - -static Result, ParserError> parse_file(const String& file_path) -{ - auto file = Core::File::construct(file_path); - auto result = file->open(Core::IODevice::ReadOnly); - if (!result) { - warnln("Failed to open the following file: \"{}\"", file_path); - cleanup_and_exit(); - } - - auto contents = file->read_all(); - String test_file_string(reinterpret_cast(contents.data()), contents.size()); - file->close(); - - auto parser = JS::Parser(JS::Lexer(test_file_string)); - auto program = parser.parse_program(); - - if (parser.has_errors()) { - auto error = parser.errors()[0]; - return Result, ParserError>(ParserError { error, error.source_location_hint(test_file_string) }); - } - - return Result, ParserError>(program); -} - -static Optional get_test_results(JS::Interpreter& interpreter) -{ - auto result = vm->get_variable("__TestResults__", interpreter.global_object()); - auto json_string = JS::JSONObject::stringify_impl(interpreter.global_object(), result, JS::js_undefined(), JS::js_undefined()); - - auto json = JsonValue::from_string(json_string); - if (!json.has_value()) - return {}; - - return json.value(); -} - -JSFileResult TestRunner::run_file_test(const String& test_path) -{ - currently_running_test = test_path; - - double start_time = get_time_in_ms(); - auto interpreter = JS::Interpreter::create(*vm); - - // FIXME: This is a hack while we're refactoring Interpreter/VM stuff. - JS::VM::InterpreterExecutionScope scope(*interpreter); - - interpreter->heap().set_should_collect_on_every_allocation(collect_on_every_allocation); - - if (!m_test_program) { - auto result = parse_file(String::formatted("{}/test-common.js", m_test_root)); - if (result.is_error()) { - warnln("Unable to parse test-common.js"); - warnln("{}", result.error().error.to_string()); - warnln("{}", result.error().hint); - cleanup_and_exit(); - } - m_test_program = result.value(); - } - - interpreter->run(interpreter->global_object(), *m_test_program); - - auto file_program = parse_file(test_path); - if (file_program.is_error()) - return { test_path, file_program.error() }; - interpreter->run(interpreter->global_object(), *file_program.value()); - - auto test_json = get_test_results(*interpreter); - if (!test_json.has_value()) { - warnln("Received malformed JSON from test \"{}\"", test_path); - cleanup_and_exit(); - } - - JSFileResult file_result { test_path.substring(m_test_root.length() + 1, test_path.length() - m_test_root.length() - 1) }; - - // Collect logged messages - auto& arr = interpreter->vm().get_variable("__UserOutput__", interpreter->global_object()).as_array(); - for (auto& entry : arr.indexed_properties()) { - auto message = entry.value_and_attributes(&interpreter->global_object()).value; - file_result.logged_messages.append(message.to_string_without_side_effects()); - } - - test_json.value().as_object().for_each_member([&](const String& suite_name, const JsonValue& suite_value) { - JSSuite suite { suite_name }; - - ASSERT(suite_value.is_object()); - - suite_value.as_object().for_each_member([&](const String& test_name, const JsonValue& test_value) { - JSTest test { test_name, TestResult::Fail, "" }; - - ASSERT(test_value.is_object()); - ASSERT(test_value.as_object().has("result")); - - auto result = test_value.as_object().get("result"); - ASSERT(result.is_string()); - auto result_string = result.as_string(); - if (result_string == "pass") { - test.result = TestResult::Pass; - m_counts.tests_passed++; - } else if (result_string == "fail") { - test.result = TestResult::Fail; - m_counts.tests_failed++; - suite.most_severe_test_result = TestResult::Fail; - ASSERT(test_value.as_object().has("details")); - auto details = test_value.as_object().get("details"); - ASSERT(result.is_string()); - test.details = details.as_string(); - } else { - test.result = TestResult::Skip; - if (suite.most_severe_test_result == TestResult::Pass) - suite.most_severe_test_result = TestResult::Skip; - m_counts.tests_skipped++; - } - - suite.tests.append(test); - }); - - if (suite.most_severe_test_result == TestResult::Fail) { - m_counts.suites_failed++; - file_result.most_severe_test_result = TestResult::Fail; - } else { - if (suite.most_severe_test_result == TestResult::Skip && file_result.most_severe_test_result == TestResult::Pass) - file_result.most_severe_test_result = TestResult::Skip; - m_counts.suites_passed++; - } - - file_result.suites.append(suite); - }); - - m_counts.files_total++; - - file_result.time_taken = get_time_in_ms() - start_time; - m_total_elapsed_time_in_ms += file_result.time_taken; - - return file_result; -} - -enum Modifier { - BG_RED, - BG_GREEN, - FG_RED, - FG_GREEN, - FG_ORANGE, - FG_GRAY, - FG_BLACK, - FG_BOLD, - ITALIC, - CLEAR, -}; - -static void print_modifiers(Vector modifiers) -{ - for (auto& modifier : modifiers) { - auto code = [&] { - switch (modifier) { - case BG_RED: - return "\033[48;2;255;0;102m"; - case BG_GREEN: - return "\033[48;2;102;255;0m"; - case FG_RED: - return "\033[38;2;255;0;102m"; - case FG_GREEN: - return "\033[38;2;102;255;0m"; - case FG_ORANGE: - return "\033[38;2;255;102;0m"; - case FG_GRAY: - return "\033[38;2;135;139;148m"; - case FG_BLACK: - return "\033[30m"; - case FG_BOLD: - return "\033[1m"; - case ITALIC: - return "\033[3m"; - case CLEAR: - return "\033[0m"; - } - ASSERT_NOT_REACHED(); - }(); - out("{}", code); - } -} - -void TestRunner::print_file_result(const JSFileResult& file_result) const -{ - if (file_result.most_severe_test_result == TestResult::Fail || file_result.error.has_value()) { - print_modifiers({ BG_RED, FG_BLACK, FG_BOLD }); - out(" FAIL "); - print_modifiers({ CLEAR }); - } else { - if (m_print_times || file_result.most_severe_test_result != TestResult::Pass) { - print_modifiers({ BG_GREEN, FG_BLACK, FG_BOLD }); - out(" PASS "); - print_modifiers({ CLEAR }); - } else { - return; - } - } - - out(" {}", file_result.name); - - if (m_print_times) { - print_modifiers({ CLEAR, ITALIC, FG_GRAY }); - if (file_result.time_taken < 1000) { - outln(" ({}ms)", static_cast(file_result.time_taken)); - } else { - outln(" ({:3}s)", file_result.time_taken / 1000.0); - } - print_modifiers({ CLEAR }); - } else { - outln(); - } - - if (!file_result.logged_messages.is_empty()) { - print_modifiers({ FG_GRAY, FG_BOLD }); -#ifdef __serenity__ - outln(" ℹ Console output:"); -#else - // This emoji has a second invisible byte after it. The one above does not - outln(" ℹ️ Console output:"); -#endif - print_modifiers({ CLEAR, FG_GRAY }); - for (auto& message : file_result.logged_messages) - outln(" {}", message); - } - - if (file_result.error.has_value()) { - auto test_error = file_result.error.value(); - - print_modifiers({ FG_RED }); -#ifdef __serenity__ - outln(" ❌ The file failed to parse"); -#else - // No invisible byte here, but the spacing still needs to be altered on the host - outln(" ❌ The file failed to parse"); -#endif - outln(); - print_modifiers({ FG_GRAY }); - for (auto& message : test_error.hint.split('\n', true)) { - outln(" {}", message); - } - print_modifiers({ FG_RED }); - outln(" {}", test_error.error.to_string()); - outln(); - return; - } - - if (file_result.most_severe_test_result != TestResult::Pass) { - for (auto& suite : file_result.suites) { - if (suite.most_severe_test_result == TestResult::Pass) - continue; - - bool failed = suite.most_severe_test_result == TestResult::Fail; - - print_modifiers({ FG_GRAY, FG_BOLD }); - - if (failed) { -#ifdef __serenity__ - out(" ❌ Suite: "); -#else - // No invisible byte here, but the spacing still needs to be altered on the host - out(" ❌ Suite: "); -#endif - } else { -#ifdef __serenity__ - out(" ⚠ Suite: "); -#else - // This emoji has a second invisible byte after it. The one above does not - out(" ⚠️ Suite: "); -#endif - } - - print_modifiers({ CLEAR, FG_GRAY }); - - if (suite.name == TOP_LEVEL_TEST_NAME) { - outln(""); - } else { - outln("{}", suite.name); - } - print_modifiers({ CLEAR }); - - for (auto& test : suite.tests) { - if (test.result == TestResult::Pass) - continue; - - print_modifiers({ FG_GRAY, FG_BOLD }); - out(" Test: "); - if (test.result == TestResult::Fail) { - print_modifiers({ CLEAR, FG_RED }); - outln("{} (failed):", test.name); - outln(" {}", test.details); - } else { - print_modifiers({ CLEAR, FG_ORANGE }); - outln("{} (skipped)", test.name); - } - print_modifiers({ CLEAR }); - } - } - } -} - -void TestRunner::print_test_results() const -{ - out("\nTest Suites: "); - if (m_counts.suites_failed) { - print_modifiers({ FG_RED }); - out("{} failed, ", m_counts.suites_failed); - print_modifiers({ CLEAR }); - } - if (m_counts.suites_passed) { - print_modifiers({ FG_GREEN }); - out("{} passed, ", m_counts.suites_passed); - print_modifiers({ CLEAR }); - } - outln("{} total", m_counts.suites_failed + m_counts.suites_passed); - - out("Tests: "); - if (m_counts.tests_failed) { - print_modifiers({ FG_RED }); - out("{} failed, ", m_counts.tests_failed); - print_modifiers({ CLEAR }); - } - if (m_counts.tests_skipped) { - print_modifiers({ FG_ORANGE }); - out("{} skipped, ", m_counts.tests_skipped); - print_modifiers({ CLEAR }); - } - if (m_counts.tests_passed) { - print_modifiers({ FG_GREEN }); - out("{} passed, ", m_counts.tests_passed); - print_modifiers({ CLEAR }); - } - outln("{} total", m_counts.tests_failed + m_counts.tests_skipped + m_counts.tests_passed); - - outln("Files: {} total", m_counts.files_total); - - out("Time: "); - if (m_total_elapsed_time_in_ms < 1000.0) { - outln("{}ms", static_cast(m_total_elapsed_time_in_ms)); - } else { - outln("{:>.3}s", m_total_elapsed_time_in_ms / 1000.0); - } - outln(); -} - -class Test262ParserTestRunner final : public TestRunner { -public: - using TestRunner::TestRunner; - -private: - virtual Vector get_test_paths() const override; - virtual JSFileResult run_file_test(const String& test_path) override; -}; - -Vector Test262ParserTestRunner::get_test_paths() const -{ - Vector paths; - iterate_directory_recursively(m_test_root, [&](const String& file_path) { - auto dirname = LexicalPath(file_path).dirname(); - if (dirname.ends_with("early") || dirname.ends_with("fail") || dirname.ends_with("pass") || dirname.ends_with("pass-explicit")) - paths.append(file_path); - }); - quick_sort(paths); - return paths; -} - -JSFileResult Test262ParserTestRunner::run_file_test(const String& test_path) -{ - currently_running_test = test_path; - - auto dirname = LexicalPath(test_path).dirname(); - bool expecting_file_to_parse; - if (dirname.ends_with("early") || dirname.ends_with("fail")) { - expecting_file_to_parse = false; - } else if (dirname.ends_with("pass") || dirname.ends_with("pass-explicit")) { - expecting_file_to_parse = true; - } else { - ASSERT_NOT_REACHED(); - } - - auto start_time = get_time_in_ms(); - String details = ""; - TestResult test_result; - if (test_path.ends_with(".module.js")) { - test_result = TestResult::Skip; - m_counts.tests_skipped++; - m_counts.suites_passed++; - } else { - auto parse_result = parse_file(test_path); - if (expecting_file_to_parse) { - if (!parse_result.is_error()) { - test_result = TestResult::Pass; - } else { - test_result = TestResult::Fail; - details = parse_result.error().error.to_string(); - } - } else { - if (parse_result.is_error()) { - test_result = TestResult::Pass; - } else { - test_result = TestResult::Fail; - details = "File was expected to produce a parser error but didn't"; - } - } - } - - // test262-parser-tests doesn't have "suites" and "tests" in the usual sense, it just has files - // and an expectation whether they should parse or not. We add one suite with one test nonetheless: - // - // - This makes interpreting skipped test easier as their file is shown as "PASS" - // - That way we can show additional information such as "file parsed but shouldn't have" or - // parser errors for files that should parse respectively - - JSTest test { expecting_file_to_parse ? "file should parse" : "file should not parse", test_result, details }; - JSSuite suite { "Parse file", test_result, { test } }; - JSFileResult file_result { - test_path.substring(m_test_root.length() + 1, test_path.length() - m_test_root.length() - 1), - {}, - get_time_in_ms() - start_time, - test_result, - { suite } - }; - - if (test_result == TestResult::Fail) { - m_counts.tests_failed++; - m_counts.suites_failed++; - } else { - m_counts.tests_passed++; - m_counts.suites_passed++; - } - m_counts.files_total++; - m_total_elapsed_time_in_ms += file_result.time_taken; - - return file_result; -} - -int main(int argc, char** argv) -{ - struct sigaction act; - memset(&act, 0, sizeof(act)); - act.sa_flags = SA_NOCLDWAIT; - act.sa_handler = handle_sigabrt; - int rc = sigaction(SIGABRT, &act, nullptr); - if (rc < 0) { - perror("sigaction"); - return 1; - } - -#ifdef SIGINFO - signal(SIGINFO, [](int) { - static char buffer[4096]; - auto& counts = TestRunner::the()->counts(); - int len = snprintf(buffer, sizeof(buffer), "Pass: %d, Fail: %d, Skip: %d\nCurrent test: %s\n", counts.tests_passed, counts.tests_failed, counts.tests_skipped, currently_running_test.characters()); - write(STDOUT_FILENO, buffer, len); - }); -#endif - - bool print_times = false; - bool test262_parser_tests = false; - const char* specified_test_root = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(print_times, "Show duration of each test", "show-time", 't'); - args_parser.add_option(collect_on_every_allocation, "Collect garbage after every allocation", "collect-often", 'g'); - args_parser.add_option(test262_parser_tests, "Run test262 parser tests", "test262-parser-tests", 0); - args_parser.add_positional_argument(specified_test_root, "Tests root directory", "path", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (test262_parser_tests) { - if (collect_on_every_allocation) { - warnln("--collect-often and --test262-parser-tests options must not be used together"); - return 1; - } - if (!specified_test_root) { - warnln("Test root is required with --test262-parser-tests"); - return 1; - } - } - - if (getenv("DISABLE_DBG_OUTPUT")) { - DebugLogStream::set_enabled(false); - } - - String test_root; - - if (specified_test_root) { - test_root = String { specified_test_root }; - } else { -#ifdef __serenity__ - test_root = "/home/anon/js-tests"; -#else - char* serenity_root = getenv("SERENITY_ROOT"); - if (!serenity_root) { - warnln("No test root given, test-js requires the SERENITY_ROOT environment variable to be set"); - return 1; - } - test_root = String::formatted("{}/Libraries/LibJS/Tests", serenity_root); -#endif - } - if (!Core::File::is_directory(test_root)) { - warnln("Test root is not a directory: {}", test_root); - return 1; - } - - vm = JS::VM::create(); - - if (test262_parser_tests) - Test262ParserTestRunner(test_root, print_times).run(); - else - TestRunner(test_root, print_times).run(); - - vm = nullptr; - - return TestRunner::the()->counts().tests_failed > 0 ? 1 : 0; -} diff --git a/Userland/test-pthread.cpp b/Userland/test-pthread.cpp deleted file mode 100644 index 36914f6634..0000000000 --- a/Userland/test-pthread.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2020, Sergey Bugaev - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -static void test_once() -{ - constexpr size_t threads_count = 10; - - static Vector v; - v.clear(); - pthread_once_t once = PTHREAD_ONCE_INIT; - NonnullRefPtrVector threads; - - for (size_t i = 0; i < threads_count; i++) { - threads.append(LibThread::Thread::construct([&] { - return pthread_once(&once, [] { - v.append(35); - sleep(1); - }); - })); - threads.last().start(); - } - for (auto& thread : threads) - [[maybe_unused]] auto res = thread.join(); - - ASSERT(v.size() == 1); -} - -int main() -{ - test_once(); - return 0; -} diff --git a/Userland/test-unveil.cpp b/Userland/test-unveil.cpp deleted file mode 100644 index df632517d1..0000000000 --- a/Userland/test-unveil.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2020, the SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - Vector paths_to_test; - const char* permissions = "r"; - bool should_sleep = false; - - Core::ArgsParser parser; - parser.add_option(permissions, "Apply these permissions going forward", "permissions", 'p', "unveil-permissions"); - parser.add_option(should_sleep, "Sleep after processing all arguments", "sleep", 's'); - parser.add_option(Core::ArgsParser::Option { - .requires_argument = true, - .help_string = "Add a path to the unveil list", - .long_name = "unveil", - .short_name = 'u', - .value_name = "path", - .accept_value = [&](auto* s) { - StringView path { s }; - if (path.is_empty()) - return false; - if (unveil(s, permissions) < 0) { - perror("unveil"); - return false; - } - return true; - } }); - parser.add_option(Core::ArgsParser::Option { - .requires_argument = false, - .help_string = "Lock the veil", - .long_name = "lock", - .short_name = 'l', - .accept_value = [&](auto*) { - if (unveil(nullptr, nullptr) < 0) { - perror("unveil(nullptr, nullptr)"); - return false; - } - return true; - } }); - parser.add_positional_argument(Core::ArgsParser::Arg { - .help_string = "Test a path against the veil", - .name = "path", - .min_values = 0, - .max_values = INT_MAX, - .accept_value = [&](auto* s) { - if (access(s, X_OK) == 0) - warnln("'{}' - ok", s); - else - warnln("'{}' - fail: {}", s, strerror(errno)); - return true; - } }); - - parser.parse(argc, argv); - if (should_sleep) - sleep(INT_MAX); - return 0; -} diff --git a/Userland/test-web.cpp b/Userland/test-web.cpp deleted file mode 100644 index e4172ddd03..0000000000 --- a/Userland/test-web.cpp +++ /dev/null @@ -1,709 +0,0 @@ -/* - * Copyright (c) 2020, The SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TOP_LEVEL_TEST_NAME "__$$TOP_LEVEL$$__" - -enum class TestResult { - Pass, - Fail, - Skip, -}; - -struct JSTest { - String name; - TestResult result; - String details; -}; - -struct JSSuite { - String name; - // A failed test takes precedence over a skipped test, which both have - // precedence over a passed test - TestResult most_severe_test_result { TestResult::Pass }; - Vector tests {}; -}; - -struct ParserError { - JS::Parser::Error error; - String hint; -}; - -struct JSFileResult { - String name; - Optional error {}; - double time_taken { 0 }; - // A failed test takes precedence over a skipped test, which both have - // precedence over a passed test - TestResult most_severe_test_result { TestResult::Pass }; - Vector suites {}; - Vector logged_messages {}; -}; - -struct JSTestRunnerCounts { - int tests_failed { 0 }; - int tests_passed { 0 }; - int tests_skipped { 0 }; - int suites_failed { 0 }; - int suites_passed { 0 }; - int files_total { 0 }; -}; - -Function g_on_page_change; - -class TestRunnerObject final : public JS::Object { - JS_OBJECT(TestRunnerObject, JS::Object); - -public: - explicit TestRunnerObject(JS::GlobalObject&); - virtual void initialize(JS::GlobalObject&) override; - virtual ~TestRunnerObject() override; - -private: - JS_DECLARE_NATIVE_FUNCTION(change_page); -}; - -TestRunnerObject::TestRunnerObject(JS::GlobalObject& global_object) - : Object(*global_object.object_prototype()) -{ -} - -void TestRunnerObject::initialize(JS::GlobalObject& global_object) -{ - Object::initialize(global_object); - define_native_function("changePage", change_page, 1); -} - -TestRunnerObject::~TestRunnerObject() -{ -} - -JS_DEFINE_NATIVE_FUNCTION(TestRunnerObject::change_page) -{ - auto url = vm.argument(0).to_string(global_object); - if (vm.exception()) - return {}; - - if (g_on_page_change) - g_on_page_change(url); - - return JS::js_undefined(); -} - -class TestRunner { -public: - TestRunner(String web_test_root, String js_test_root, Web::InProcessWebView& page_view, bool print_times) - : m_web_test_root(move(web_test_root)) - , m_js_test_root(move(js_test_root)) - , m_print_times(print_times) - , m_page_view(page_view) - { - } - - void run(); - -private: - JSFileResult run_file_test(const String& test_path); - void print_file_result(const JSFileResult& file_result) const; - void print_test_results() const; - - String m_web_test_root; - String m_js_test_root; - bool m_print_times; - - double m_total_elapsed_time_in_ms { 0 }; - JSTestRunnerCounts m_counts; - - RefPtr m_page_view; - - RefPtr m_js_test_common; - RefPtr m_web_test_common; -}; - -static void cleanup_and_exit() -{ - // Clear the taskbar progress. -#ifdef __serenity__ - fprintf(stderr, "\033]9;-1;\033\\"); -#endif - exit(1); -} - -#if 0 -static void handle_sigabrt(int) -{ - dbgln("test-web: SIGABRT received, cleaning up."); - cleanup_and_exit(); -} -#endif - -static double get_time_in_ms() -{ - struct timeval tv1; - auto return_code = gettimeofday(&tv1, nullptr); - ASSERT(return_code >= 0); - return static_cast(tv1.tv_sec) * 1000.0 + static_cast(tv1.tv_usec) / 1000.0; -} - -template -void iterate_directory_recursively(const String& directory_path, Callback callback) -{ - Core::DirIterator directory_iterator(directory_path, Core::DirIterator::Flags::SkipDots); - - while (directory_iterator.has_next()) { - auto file_path = String::format("%s/%s", directory_path.characters(), directory_iterator.next_path().characters()); - if (Core::File::is_directory(file_path)) { - iterate_directory_recursively(file_path, callback); - } else { - callback(move(file_path)); - } - } -} - -static Vector get_test_paths(const String& test_root) -{ - Vector paths; - - iterate_directory_recursively(test_root, [&](const String& file_path) { - if (!file_path.ends_with("test-common.js") && !file_path.ends_with(".html") && !file_path.ends_with(".ts")) - paths.append(file_path); - }); - - quick_sort(paths); - - return paths; -} - -void TestRunner::run() -{ - size_t progress_counter = 0; - auto test_paths = get_test_paths(m_web_test_root); - - g_on_page_change = [this](auto& page_to_load) { - if (!page_to_load.is_valid()) { - printf("Invalid page URL (%s) on page change", page_to_load.to_string().characters()); - cleanup_and_exit(); - } - - ASSERT(m_page_view->document()); - - // We want to keep the same document since the interpreter is tied to the document, - // and we don't want to lose the test state. So, we just clear the document and - // give a new parser the existing document to work on. - m_page_view->document()->remove_all_children(); - - Web::ResourceLoader::the().load_sync( - page_to_load, - [&](auto data, auto&) { - Web::HTML::HTMLDocumentParser parser(*m_page_view->document(), data, "utf-8"); - parser.run(page_to_load); - }, - [page_to_load](auto error) { - printf("Failed to load test page: %s (%s)", page_to_load.to_string().characters(), error.characters()); - cleanup_and_exit(); - }); - }; - - for (auto& path : test_paths) { - ++progress_counter; - print_file_result(run_file_test(path)); -#ifdef __serenity__ - fprintf(stderr, "\033]9;%zu;%zu;\033\\", progress_counter, test_paths.size()); -#endif - } - -#ifdef __serenity__ - fprintf(stderr, "\033]9;-1;\033\\"); -#endif - - print_test_results(); -} - -static Result, ParserError> parse_file(const String& file_path) -{ - auto file = Core::File::construct(file_path); - auto result = file->open(Core::IODevice::ReadOnly); - if (!result) { - printf("Failed to open the following file: \"%s\"\n", file_path.characters()); - cleanup_and_exit(); - } - - auto contents = file->read_all(); - String test_file_string(reinterpret_cast(contents.data()), contents.size()); - file->close(); - - auto parser = JS::Parser(JS::Lexer(test_file_string)); - auto program = parser.parse_program(); - - if (parser.has_errors()) { - auto error = parser.errors()[0]; - return Result, ParserError>(ParserError { error, error.source_location_hint(test_file_string) }); - } - - return Result, ParserError>(program); -} - -static Optional get_test_results(JS::Interpreter& interpreter) -{ - auto result = interpreter.vm().get_variable("__TestResults__", interpreter.global_object()); - auto json_string = JS::JSONObject::stringify_impl(interpreter.global_object(), result, JS::js_undefined(), JS::js_undefined()); - - auto json = JsonValue::from_string(json_string); - if (!json.has_value()) - return {}; - - return json.value(); -} - -JSFileResult TestRunner::run_file_test(const String& test_path) -{ - double start_time = get_time_in_ms(); - ASSERT(m_page_view->document()); - auto& old_interpreter = m_page_view->document()->interpreter(); - - // FIXME: This is a hack while we're refactoring Interpreter/VM stuff. - JS::VM::InterpreterExecutionScope scope(old_interpreter); - - if (!m_js_test_common) { - auto result = parse_file(String::format("%s/test-common.js", m_js_test_root.characters())); - if (result.is_error()) { - printf("Unable to parse %s/test-common.js\n", m_js_test_root.characters()); - printf("%s\n", result.error().error.to_string().characters()); - printf("%s\n", result.error().hint.characters()); - cleanup_and_exit(); - } - m_js_test_common = result.value(); - } - - if (!m_web_test_common) { - auto result = parse_file(String::format("%s/test-common.js", m_web_test_root.characters())); - if (result.is_error()) { - printf("Unable to parse %s/test-common.js\n", m_web_test_root.characters()); - printf("%s\n", result.error().error.to_string().characters()); - printf("%s\n", result.error().hint.characters()); - cleanup_and_exit(); - } - m_web_test_common = result.value(); - } - - auto file_program = parse_file(test_path); - if (file_program.is_error()) - return { test_path, file_program.error() }; - - // Setup the test on the current page to get "__PageToLoad__". - old_interpreter.run(old_interpreter.global_object(), *m_web_test_common); - old_interpreter.run(old_interpreter.global_object(), *file_program.value()); - auto page_to_load = URL(old_interpreter.vm().get_variable("__PageToLoad__", old_interpreter.global_object()).as_string().string()); - if (!page_to_load.is_valid()) { - printf("Invalid page URL for %s", test_path.characters()); - cleanup_and_exit(); - } - - JSFileResult file_result; - - Web::ResourceLoader::the().load_sync( - page_to_load, - [&](auto data, auto&) { - // Create a new parser and immediately get its document to replace the old interpreter. - auto document = Web::DOM::Document::create(); - Web::HTML::HTMLDocumentParser parser(document, data, "utf-8"); - auto& new_interpreter = parser.document().interpreter(); - - // Setup the test environment and call "__BeforeInitialPageLoad__" - new_interpreter.global_object().define_property( - "libweb_tester", - new_interpreter.heap().allocate(new_interpreter.global_object(), new_interpreter.global_object()), - JS::Attribute::Enumerable | JS::Attribute::Configurable); - new_interpreter.run(new_interpreter.global_object(), *m_js_test_common); - new_interpreter.run(new_interpreter.global_object(), *m_web_test_common); - new_interpreter.run(new_interpreter.global_object(), *file_program.value()); - - auto& before_initial_page_load = new_interpreter.vm().get_variable("__BeforeInitialPageLoad__", new_interpreter.global_object()).as_function(); - [[maybe_unused]] auto rc_before = new_interpreter.vm().call(before_initial_page_load, JS::js_undefined()); - if (new_interpreter.exception()) - new_interpreter.vm().clear_exception(); - - // Now parse the HTML page. - parser.run(page_to_load); - m_page_view->set_document(&parser.document()); - - // Finally run the test by calling "__AfterInitialPageLoad__" - auto& after_initial_page_load = new_interpreter.vm().get_variable("__AfterInitialPageLoad__", new_interpreter.global_object()).as_function(); - [[maybe_unused]] auto rc_after = new_interpreter.vm().call(after_initial_page_load, JS::js_undefined()); - if (new_interpreter.exception()) - new_interpreter.vm().clear_exception(); - - auto test_json = get_test_results(new_interpreter); - if (!test_json.has_value()) { - printf("Received malformed JSON from test \"%s\"\n", test_path.characters()); - cleanup_and_exit(); - } - - file_result = { test_path.substring(m_web_test_root.length() + 1, test_path.length() - m_web_test_root.length() - 1) }; - - // Collect logged messages - auto& arr = new_interpreter.vm().get_variable("__UserOutput__", new_interpreter.global_object()).as_array(); - for (auto& entry : arr.indexed_properties()) { - auto message = entry.value_and_attributes(&new_interpreter.global_object()).value; - file_result.logged_messages.append(message.to_string_without_side_effects()); - } - - test_json.value().as_object().for_each_member([&](const String& suite_name, const JsonValue& suite_value) { - JSSuite suite { suite_name }; - - ASSERT(suite_value.is_object()); - - suite_value.as_object().for_each_member([&](const String& test_name, const JsonValue& test_value) { - JSTest test { test_name, TestResult::Fail, "" }; - - ASSERT(test_value.is_object()); - ASSERT(test_value.as_object().has("result")); - - auto result = test_value.as_object().get("result"); - ASSERT(result.is_string()); - auto result_string = result.as_string(); - if (result_string == "pass") { - test.result = TestResult::Pass; - m_counts.tests_passed++; - } else if (result_string == "fail") { - test.result = TestResult::Fail; - m_counts.tests_failed++; - suite.most_severe_test_result = TestResult::Fail; - ASSERT(test_value.as_object().has("details")); - auto details = test_value.as_object().get("details"); - ASSERT(result.is_string()); - test.details = details.as_string(); - } else { - test.result = TestResult::Skip; - if (suite.most_severe_test_result == TestResult::Pass) - suite.most_severe_test_result = TestResult::Skip; - m_counts.tests_skipped++; - } - - suite.tests.append(test); - }); - - if (suite.most_severe_test_result == TestResult::Fail) { - m_counts.suites_failed++; - file_result.most_severe_test_result = TestResult::Fail; - } else { - if (suite.most_severe_test_result == TestResult::Skip && file_result.most_severe_test_result == TestResult::Pass) - file_result.most_severe_test_result = TestResult::Skip; - m_counts.suites_passed++; - } - - file_result.suites.append(suite); - }); - - m_counts.files_total++; - - file_result.time_taken = get_time_in_ms() - start_time; - m_total_elapsed_time_in_ms += file_result.time_taken; - }, - [page_to_load](auto error) { - printf("Failed to load test page: %s (%s)", page_to_load.to_string().characters(), error.characters()); - cleanup_and_exit(); - }); - - return file_result; -} - -enum Modifier { - BG_RED, - BG_GREEN, - FG_RED, - FG_GREEN, - FG_ORANGE, - FG_GRAY, - FG_BLACK, - FG_BOLD, - ITALIC, - CLEAR, -}; - -static void print_modifiers(Vector modifiers) -{ - for (auto& modifier : modifiers) { - auto code = [&]() -> String { - switch (modifier) { - case BG_RED: - return "\033[48;2;255;0;102m"; - case BG_GREEN: - return "\033[48;2;102;255;0m"; - case FG_RED: - return "\033[38;2;255;0;102m"; - case FG_GREEN: - return "\033[38;2;102;255;0m"; - case FG_ORANGE: - return "\033[38;2;255;102;0m"; - case FG_GRAY: - return "\033[38;2;135;139;148m"; - case FG_BLACK: - return "\033[30m"; - case FG_BOLD: - return "\033[1m"; - case ITALIC: - return "\033[3m"; - case CLEAR: - return "\033[0m"; - } - ASSERT_NOT_REACHED(); - }; - printf("%s", code().characters()); - } -} - -void TestRunner::print_file_result(const JSFileResult& file_result) const -{ - if (file_result.most_severe_test_result == TestResult::Fail || file_result.error.has_value()) { - print_modifiers({ BG_RED, FG_BLACK, FG_BOLD }); - printf(" FAIL "); - print_modifiers({ CLEAR }); - } else { - if (m_print_times || file_result.most_severe_test_result != TestResult::Pass) { - print_modifiers({ BG_GREEN, FG_BLACK, FG_BOLD }); - printf(" PASS "); - print_modifiers({ CLEAR }); - } else { - return; - } - } - - printf(" %s", file_result.name.characters()); - - if (m_print_times) { - print_modifiers({ CLEAR, ITALIC, FG_GRAY }); - if (file_result.time_taken < 1000) { - printf(" (%dms)\n", static_cast(file_result.time_taken)); - } else { - printf(" (%.3fs)\n", file_result.time_taken / 1000.0); - } - print_modifiers({ CLEAR }); - } else { - printf("\n"); - } - - if (!file_result.logged_messages.is_empty()) { - print_modifiers({ FG_GRAY, FG_BOLD }); -#ifdef __serenity__ - printf(" ℹ Console output:\n"); -#else - // This emoji has a second invisible byte after it. The one above does not - printf(" ℹ️ Console output:\n"); -#endif - print_modifiers({ CLEAR, FG_GRAY }); - for (auto& message : file_result.logged_messages) - printf(" %s\n", message.characters()); - } - - if (file_result.error.has_value()) { - auto test_error = file_result.error.value(); - - print_modifiers({ FG_RED }); -#ifdef __serenity__ - printf(" ❌ The file failed to parse\n\n"); -#else - // No invisible byte here, but the spacing still needs to be altered on the host - printf(" ❌ The file failed to parse\n\n"); -#endif - print_modifiers({ FG_GRAY }); - for (auto& message : test_error.hint.split('\n', true)) { - printf(" %s\n", message.characters()); - } - print_modifiers({ FG_RED }); - printf(" %s\n\n", test_error.error.to_string().characters()); - - return; - } - - if (file_result.most_severe_test_result != TestResult::Pass) { - for (auto& suite : file_result.suites) { - if (suite.most_severe_test_result == TestResult::Pass) - continue; - - bool failed = suite.most_severe_test_result == TestResult::Fail; - - print_modifiers({ FG_GRAY, FG_BOLD }); - - if (failed) { -#ifdef __serenity__ - printf(" ❌ Suite: "); -#else - // No invisible byte here, but the spacing still needs to be altered on the host - printf(" ❌ Suite: "); -#endif - } else { -#ifdef __serenity__ - printf(" ⚠ Suite: "); -#else - // This emoji has a second invisible byte after it. The one above does not - printf(" ⚠️ Suite: "); -#endif - } - - print_modifiers({ CLEAR, FG_GRAY }); - - if (suite.name == TOP_LEVEL_TEST_NAME) { - printf("\n"); - } else { - printf("%s\n", suite.name.characters()); - } - print_modifiers({ CLEAR }); - - for (auto& test : suite.tests) { - if (test.result == TestResult::Pass) - continue; - - print_modifiers({ FG_GRAY, FG_BOLD }); - printf(" Test: "); - if (test.result == TestResult::Fail) { - print_modifiers({ CLEAR, FG_RED }); - printf("%s (failed):\n", test.name.characters()); - printf(" %s\n", test.details.characters()); - } else { - print_modifiers({ CLEAR, FG_ORANGE }); - printf("%s (skipped)\n", test.name.characters()); - } - print_modifiers({ CLEAR }); - } - } - } -} - -void TestRunner::print_test_results() const -{ - printf("\nTest Suites: "); - if (m_counts.suites_failed) { - print_modifiers({ FG_RED }); - printf("%d failed, ", m_counts.suites_failed); - print_modifiers({ CLEAR }); - } - if (m_counts.suites_passed) { - print_modifiers({ FG_GREEN }); - printf("%d passed, ", m_counts.suites_passed); - print_modifiers({ CLEAR }); - } - printf("%d total\n", m_counts.suites_failed + m_counts.suites_passed); - - printf("Tests: "); - if (m_counts.tests_failed) { - print_modifiers({ FG_RED }); - printf("%d failed, ", m_counts.tests_failed); - print_modifiers({ CLEAR }); - } - if (m_counts.tests_skipped) { - print_modifiers({ FG_ORANGE }); - printf("%d skipped, ", m_counts.tests_skipped); - print_modifiers({ CLEAR }); - } - if (m_counts.tests_passed) { - print_modifiers({ FG_GREEN }); - printf("%d passed, ", m_counts.tests_passed); - print_modifiers({ CLEAR }); - } - printf("%d total\n", m_counts.tests_failed + m_counts.tests_passed); - - printf("Files: %d total\n", m_counts.files_total); - - printf("Time: "); - if (m_total_elapsed_time_in_ms < 1000.0) { - printf("%dms\n\n", static_cast(m_total_elapsed_time_in_ms)); - } else { - printf("%-.3fs\n\n", m_total_elapsed_time_in_ms / 1000.0); - } -} - -int main(int argc, char** argv) -{ - bool print_times = false; - bool show_window = false; - -#if 0 - struct sigaction act; - memset(&act, 0, sizeof(act)); - act.sa_flags = SA_NOCLDWAIT; - act.sa_handler = handle_sigabrt; - int rc = sigaction(SIGABRT, &act, nullptr); - if (rc < 0) { - perror("sigaction"); - return 1; - } -#endif - - Core::ArgsParser args_parser; - args_parser.add_option(print_times, "Show duration of each test", "show-time", 't'); - args_parser.add_option(show_window, "Show window while running tests", "window", 'w'); - args_parser.parse(argc, argv); - - auto app = GUI::Application::construct(argc, argv); - auto window = GUI::Window::construct(); - auto& main_widget = window->set_main_widget(); - main_widget.set_fill_with_background_color(true); - main_widget.set_layout(); - auto& view = main_widget.add(); - - view.set_document(Web::DOM::Document::create()); - - if (show_window) { - window->set_title("LibWeb Test Window"); - window->resize(640, 480); - window->show(); - } - -#ifdef __serenity__ - TestRunner("/home/anon/web-tests", "/home/anon/js-tests", view, print_times).run(); -#else - char* serenity_root = getenv("SERENITY_ROOT"); - if (!serenity_root) { - printf("test-web requires the SERENITY_ROOT environment variable to be set"); - return 1; - } - TestRunner(String::format("%s/Libraries/LibWeb/Tests", serenity_root), String::format("%s/Libraries/LibJS/Tests", serenity_root), view, print_times).run(); -#endif - return 0; -} diff --git a/Userland/test.cpp b/Userland/test.cpp deleted file mode 100644 index a81c84933c..0000000000 --- a/Userland/test.cpp +++ /dev/null @@ -1,537 +0,0 @@ -/* - * Copyright (c) 2020, The SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -bool g_there_was_an_error = false; - -[[noreturn]] static void fatal_error(const char* format, ...) -{ - fputs("\033[31m", stderr); - - va_list ap; - va_start(ap, format); - vfprintf(stderr, format, ap); - va_end(ap); - - fputs("\033[0m\n", stderr); - exit(126); -} - -class Condition { -public: - virtual ~Condition() { } - virtual bool check() const = 0; -}; - -class And : public Condition { -public: - And(NonnullOwnPtr lhs, NonnullOwnPtr rhs) - : m_lhs(move(lhs)) - , m_rhs(move(rhs)) - { - } - -private: - virtual bool check() const override - { - return m_lhs->check() && m_rhs->check(); - } - - NonnullOwnPtr m_lhs; - NonnullOwnPtr m_rhs; -}; - -class Or : public Condition { -public: - Or(NonnullOwnPtr lhs, NonnullOwnPtr rhs) - : m_lhs(move(lhs)) - , m_rhs(move(rhs)) - { - } - -private: - virtual bool check() const override - { - return m_lhs->check() || m_rhs->check(); - } - - NonnullOwnPtr m_lhs; - NonnullOwnPtr m_rhs; -}; - -class Not : public Condition { -public: - Not(NonnullOwnPtr cond) - : m_cond(move(cond)) - { - } - -private: - virtual bool check() const override - { - return !m_cond->check(); - } - - NonnullOwnPtr m_cond; -}; - -class FileIsOfKind : public Condition { -public: - enum Kind { - BlockDevice, - CharacterDevice, - Directory, - FIFO, - Regular, - Socket, - SymbolicLink, - }; - FileIsOfKind(StringView path, Kind kind) - : m_path(path) - , m_kind(kind) - { - } - -private: - virtual bool check() const override - { - struct stat statbuf; - int rc; - - if (m_kind == SymbolicLink) - rc = stat(m_path.characters(), &statbuf); - else - rc = lstat(m_path.characters(), &statbuf); - - if (rc < 0) { - if (errno != ENOENT) { - perror(m_path.characters()); - g_there_was_an_error = true; - } - return false; - } - - switch (m_kind) { - case BlockDevice: - return S_ISBLK(statbuf.st_mode); - case CharacterDevice: - return S_ISCHR(statbuf.st_mode); - case Directory: - return S_ISDIR(statbuf.st_mode); - case FIFO: - return S_ISFIFO(statbuf.st_mode); - case Regular: - return S_ISREG(statbuf.st_mode); - case Socket: - return S_ISSOCK(statbuf.st_mode); - case SymbolicLink: - return S_ISLNK(statbuf.st_mode); - default: - ASSERT_NOT_REACHED(); - } - } - - String m_path; - Kind m_kind { Regular }; -}; - -class UserHasPermission : public Condition { -public: - enum Permission { - Any, - Read, - Write, - Execute, - }; - UserHasPermission(StringView path, Permission kind) - : m_path(path) - , m_kind(kind) - { - } - -private: - virtual bool check() const override - { - switch (m_kind) { - case Read: - return access(m_path.characters(), R_OK) == 0; - case Write: - return access(m_path.characters(), W_OK) == 0; - case Execute: - return access(m_path.characters(), X_OK) == 0; - case Any: - return access(m_path.characters(), F_OK) == 0; - default: - ASSERT_NOT_REACHED(); - } - } - - String m_path; - Permission m_kind { Read }; -}; - -class StringCompare : public Condition { -public: - enum Mode { - Equal, - NotEqual, - }; - - StringCompare(StringView lhs, StringView rhs, Mode mode) - : m_lhs(move(lhs)) - , m_rhs(move(rhs)) - , m_mode(mode) - { - } - -private: - virtual bool check() const override - { - if (m_mode == Equal) - return m_lhs == m_rhs; - return m_lhs != m_rhs; - } - - StringView m_lhs; - StringView m_rhs; - Mode m_mode { Equal }; -}; - -class NumericCompare : public Condition { -public: - enum Mode { - Equal, - Greater, - GreaterOrEqual, - Less, - LessOrEqual, - NotEqual, - }; - - NumericCompare(String lhs, String rhs, Mode mode) - : m_mode(mode) - { - auto lhs_option = lhs.trim_whitespace().to_int(); - auto rhs_option = rhs.trim_whitespace().to_int(); - - if (!lhs_option.has_value()) - fatal_error("expected integer expression: '%s'", lhs.characters()); - - if (!rhs_option.has_value()) - fatal_error("expected integer expression: '%s'", rhs.characters()); - - m_lhs = lhs_option.value(); - m_rhs = rhs_option.value(); - } - -private: - virtual bool check() const override - { - switch (m_mode) { - case Equal: - return m_lhs == m_rhs; - case Greater: - return m_lhs > m_rhs; - case GreaterOrEqual: - return m_lhs >= m_rhs; - case Less: - return m_lhs < m_rhs; - case LessOrEqual: - return m_lhs <= m_rhs; - case NotEqual: - return m_lhs != m_rhs; - default: - ASSERT_NOT_REACHED(); - } - } - - int m_lhs { 0 }; - int m_rhs { 0 }; - Mode m_mode { Equal }; -}; - -class FileCompare : public Condition { -public: - enum Mode { - Same, - ModificationTimestampGreater, - ModificationTimestampLess, - }; - - FileCompare(String lhs, String rhs, Mode mode) - : m_lhs(move(lhs)) - , m_rhs(move(rhs)) - , m_mode(mode) - { - } - -private: - virtual bool check() const override - { - struct stat statbuf_l; - int rc = stat(m_lhs.characters(), &statbuf_l); - - if (rc < 0) { - perror(m_lhs.characters()); - g_there_was_an_error = true; - return false; - } - - struct stat statbuf_r; - rc = stat(m_rhs.characters(), &statbuf_r); - - if (rc < 0) { - perror(m_rhs.characters()); - g_there_was_an_error = true; - return false; - } - - switch (m_mode) { - case Same: - return statbuf_l.st_dev == statbuf_r.st_dev && statbuf_l.st_ino == statbuf_r.st_ino; - case ModificationTimestampLess: - return statbuf_l.st_mtime < statbuf_r.st_mtime; - case ModificationTimestampGreater: - return statbuf_l.st_mtime > statbuf_r.st_mtime; - default: - ASSERT_NOT_REACHED(); - } - } - - String m_lhs; - String m_rhs; - Mode m_mode { Same }; -}; - -static OwnPtr parse_complex_expression(char* argv[]); - -static bool should_treat_expression_as_single_string(const StringView& arg_after) -{ - return arg_after.is_null() || arg_after == "-a" || arg_after == "-o"; -} - -static OwnPtr parse_simple_expression(char* argv[]) -{ - StringView arg = argv[optind]; - if (arg.is_null()) { - return {}; - } - - if (arg == "(") { - optind++; - auto command = parse_complex_expression(argv); - if (command && argv[optind] && StringView(argv[++optind]) == ")") - return command; - fatal_error("Unmatched \033[1m("); - } - - if (arg == "!") { - if (should_treat_expression_as_single_string(argv[optind])) - return make(move(arg), "", StringCompare::NotEqual); - auto command = parse_complex_expression(argv); - if (!command) - fatal_error("Expected an expression after \033[1m!"); - return make(command.release_nonnull()); - } - - // Try to read a unary op. - if (arg.starts_with('-') && arg.length() == 2) { - optind++; - if (should_treat_expression_as_single_string(argv[optind])) { - --optind; - return make(move(arg), "", StringCompare::NotEqual); - } - - StringView value = argv[optind]; - switch (arg[1]) { - case 'b': - return make(value, FileIsOfKind::BlockDevice); - case 'c': - return make(value, FileIsOfKind::CharacterDevice); - case 'd': - return make(value, FileIsOfKind::Directory); - case 'f': - return make(value, FileIsOfKind::Regular); - case 'h': - case 'L': - return make(value, FileIsOfKind::SymbolicLink); - case 'p': - return make(value, FileIsOfKind::FIFO); - case 'S': - return make(value, FileIsOfKind::Socket); - case 'r': - return make(value, UserHasPermission::Read); - case 'w': - return make(value, UserHasPermission::Write); - case 'x': - return make(value, UserHasPermission::Execute); - case 'e': - return make(value, UserHasPermission::Any); - case 'o': - case 'a': - // '-a' and '-o' are boolean ops, which are part of a complex expression - // so we have nothing to parse, simply return to caller. - --optind; - return {}; - case 'n': - return make("", value, StringCompare::NotEqual); - case 'z': - return make("", value, StringCompare::Equal); - case 'g': - case 'G': - case 'k': - case 'N': - case 'O': - case 's': - fatal_error("Unsupported operator \033[1m%s", argv[optind]); - default: - --optind; - break; - } - } - - // Try to read a binary op, this is either a op , op , or op . - auto lhs = arg; - arg = argv[++optind]; - - if (arg == "=") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, StringCompare::Equal); - } else if (arg == "!=") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, StringCompare::NotEqual); - } else if (arg == "-eq") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, NumericCompare::Equal); - } else if (arg == "-ge") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, NumericCompare::GreaterOrEqual); - } else if (arg == "-gt") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, NumericCompare::Greater); - } else if (arg == "-le") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, NumericCompare::LessOrEqual); - } else if (arg == "-lt") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, NumericCompare::Less); - } else if (arg == "-ne") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, NumericCompare::NotEqual); - } else if (arg == "-ef") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, FileCompare::Same); - } else if (arg == "-nt") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, FileCompare::ModificationTimestampGreater); - } else if (arg == "-ot") { - StringView rhs = argv[++optind]; - return make(lhs, rhs, FileCompare::ModificationTimestampLess); - } else if (arg == "-o" || arg == "-a") { - // '-a' and '-o' are boolean ops, which are part of a complex expression - // put them back and return with lhs as string compare. - --optind; - return make("", lhs, StringCompare::NotEqual); - } else { - --optind; - return make("", lhs, StringCompare::NotEqual); - } -} - -static OwnPtr parse_complex_expression(char* argv[]) -{ - auto command = parse_simple_expression(argv); - - while (argv[optind] && argv[optind + 1]) { - if (!command && argv[optind]) - fatal_error("expected an expression"); - - StringView arg = argv[++optind]; - - enum { - AndOp, - OrOp, - } binary_operation { AndOp }; - - if (arg == "-a") { - optind++; - binary_operation = AndOp; - } else if (arg == "-o") { - optind++; - binary_operation = OrOp; - } else { - // Ooops, looked too far. - optind--; - return command; - } - auto rhs = parse_complex_expression(argv); - if (!rhs) - fatal_error("Missing right-hand side"); - - if (binary_operation == AndOp) - command = make(command.release_nonnull(), rhs.release_nonnull()); - else - command = make(command.release_nonnull(), rhs.release_nonnull()); - } - - return command; -} - -int main(int argc, char* argv[]) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 126; - } - - if (LexicalPath { argv[0] }.basename() == "[") { - --argc; - if (StringView { argv[argc] } != "]") - fatal_error("test invoked as '[' requires a closing bracket ']'"); - argv[argc] = nullptr; - } - - // Exit false when no arguments are given. - if (argc == 1) - return 1; - - auto condition = parse_complex_expression(argv); - if (optind != argc - 1) - fatal_error("Too many arguments"); - auto result = condition ? condition->check() : false; - - if (g_there_was_an_error) - return 126; - return result ? 0 : 1; -} diff --git a/Userland/test_efault.cpp b/Userland/test_efault.cpp deleted file mode 100644 index b063fcfb78..0000000000 --- a/Userland/test_efault.cpp +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -#define EXPECT_OK(syscall, address, size) \ - do { \ - rc = syscall(fd, (void*)(address), (size_t)(size)); \ - if (rc < 0) { \ - fprintf(stderr, "Expected success: " #syscall "(%p, %zu), got rc=%d, errno=%d\n", (void*)(address), (size_t)(size), rc, errno); \ - } \ - } while (0) - -#define EXPECT_EFAULT(syscall, address, size) \ - do { \ - rc = syscall(fd, (void*)(address), (size_t)(size)); \ - if (rc >= 0 || errno != EFAULT) { \ - fprintf(stderr, "Expected EFAULT: " #syscall "(%p, %zu), got rc=%d, errno=%d\n", (void*)(address), (size_t)(size), rc, errno); \ - } \ - } while (0) - -#define EXPECT_EFAULT_NO_FD(syscall, address, size) \ - do { \ - rc = syscall((address), (size_t)(size)); \ - if (rc >= 0 || errno != EFAULT) { \ - fprintf(stderr, "Expected EFAULT: " #syscall "(%p, %zu), got rc=%d, errno=%d\n", (void*)(address), (size_t)(size), rc, errno); \ - } \ - } while (0) - -int main(int, char**) -{ - int fd = open("/dev/zero", O_RDONLY); - int rc; - - // Test a one-page mapping (4KB) - u8* one_page = (u8*)mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - ASSERT(one_page); - EXPECT_OK(read, one_page, 4096); - EXPECT_EFAULT(read, one_page, 4097); - EXPECT_EFAULT(read, one_page - 1, 4096); - - // Test a two-page mapping (8KB) - u8* two_page = (u8*)mmap(nullptr, 8192, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); - ASSERT(two_page); - - EXPECT_OK(read, two_page, 4096); - EXPECT_OK(read, two_page + 4096, 4096); - EXPECT_OK(read, two_page, 8192); - EXPECT_OK(read, two_page + 4095, 4097); - EXPECT_OK(read, two_page + 1, 8191); - EXPECT_EFAULT(read, two_page, 8193); - EXPECT_EFAULT(read, two_page - 1, 1); - - // Check validation of pages between the first and last address. - ptrdiff_t distance = two_page - one_page; - EXPECT_EFAULT(read, one_page, (u32)distance + 1024); - - // Test every kernel page just because. - for (u64 kernel_address = 0xc0000000; kernel_address <= 0xffffffff; kernel_address += PAGE_SIZE) { - EXPECT_EFAULT(read, (void*)kernel_address, 1); - } - - char buffer[4096]; - EXPECT_EFAULT_NO_FD(dbgputstr, buffer, 0xffffff00); - - // Test the page just below where the kernel VM begins. - u8* jerk_page = (u8*)mmap((void*)(0xc0000000 - PAGE_SIZE), PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0); - ASSERT(jerk_page == (void*)(0xc0000000 - PAGE_SIZE)); - - EXPECT_OK(read, jerk_page, 4096); - EXPECT_EFAULT(read, jerk_page, 4097); - - // Test something that would wrap around the 2^32 mark. - EXPECT_EFAULT(read, jerk_page, 0x50000000); - - return 0; -} diff --git a/Userland/test_env.cpp b/Userland/test_env.cpp deleted file mode 100644 index ce5467ecf9..0000000000 --- a/Userland/test_env.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2018-2020, Ben Wiederhake - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include - -static void assert_env(const char* name, const char* value) -{ - char* result = getenv(name); - if (!result) { - perror("getenv"); - printf("(When reading value for '%s'; we expected '%s'.)\n", name, value); - ASSERT(false); - } - if (strcmp(result, value) != 0) { - printf("Expected '%s', got '%s' instead.\n", value, result); - ASSERT(false); - } -} - -static void test_getenv_preexisting() -{ - assert_env("HOME", "/home/anon"); -} - -static void test_puttenv() -{ - char* to_put = strdup("PUTENVTEST=HELLOPUTENV"); - int rc = putenv(to_put); - if (rc) { - perror("putenv"); - ASSERT(false); - } - assert_env("PUTENVTEST", "HELLOPUTENV"); - // Do not free `to_put`! -} - -static void test_settenv() -{ - int rc = setenv("SETENVTEST", "HELLO SETENV!", 0); - if (rc) { - perror("setenv"); - ASSERT(false); - } - // This used to trigger a very silly bug! :) - assert_env("SETENVTEST", "HELLO SETENV!"); - - rc = setenv("SETENVTEST", "How are you today?", 0); - if (rc) { - perror("setenv"); - ASSERT(false); - } - assert_env("SETENVTEST", "HELLO SETENV!"); - - rc = setenv("SETENVTEST", "Goodbye, friend!", 1); - if (rc) { - perror("setenv"); - ASSERT(false); - } - assert_env("SETENVTEST", "Goodbye, friend!"); -} - -static void test_settenv_overwrite_empty() -{ - int rc = setenv("EMPTYTEST", "Forcefully overwrite non-existing envvar", 1); - if (rc) { - perror("setenv"); - ASSERT(false); - } - assert_env("EMPTYTEST", "Forcefully overwrite non-existing envvar"); -} - -int main(int, char**) -{ -#define RUNTEST(x) \ - { \ - printf("Running " #x " ...\n"); \ - x(); \ - printf("Success!\n"); \ - } - RUNTEST(test_getenv_preexisting); - RUNTEST(test_puttenv); - RUNTEST(test_settenv); - RUNTEST(test_settenv_overwrite_empty); - printf("PASS\n"); - - return 0; -} diff --git a/Userland/test_io.cpp b/Userland/test_io.cpp deleted file mode 100644 index 316840a8e5..0000000000 --- a/Userland/test_io.cpp +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define EXPECT_ERROR_2(err, syscall, arg1, arg2) \ - do { \ - rc = syscall(arg1, arg2); \ - if (rc >= 0 || errno != err) { \ - fprintf(stderr, __FILE__ ":%d: Expected " #err ": " #syscall "(%p, %p), got rc=%d, errno=%d\n", __LINE__, (const void*)(arg1), (const void*)arg2, rc, errno); \ - } \ - } while (0) - -#define EXPECT_ERROR_3(err, syscall, arg1, arg2, arg3) \ - do { \ - rc = syscall(arg1, arg2, arg3); \ - if (rc >= 0 || errno != err) { \ - fprintf(stderr, __FILE__ ":%d: Expected " #err ": " #syscall "(%p, %p, %p), got rc=%d, errno=%d\n", __LINE__, (const void*)(arg1), (const void*)(arg2), (const void*)(arg3), rc, errno); \ - } \ - } while (0) - -static void test_read_from_directory() -{ - char buffer[BUFSIZ]; - int fd = open("/", O_DIRECTORY | O_RDONLY); - ASSERT(fd >= 0); - int rc; - EXPECT_ERROR_3(EISDIR, read, fd, buffer, sizeof(buffer)); - rc = close(fd); - ASSERT(rc == 0); -} - -static void test_write_to_directory() -{ - char str[] = "oh frick"; - int fd = open("/", O_DIRECTORY | O_RDONLY); - if (fd < 0) - perror("open"); - ASSERT(fd >= 0); - int rc; - EXPECT_ERROR_3(EBADF, write, fd, str, sizeof(str)); - rc = close(fd); - ASSERT(rc == 0); -} - -static void test_read_from_writeonly() -{ - char buffer[BUFSIZ]; - int fd = open("/tmp/xxxx123", O_CREAT | O_WRONLY); - ASSERT(fd >= 0); - int rc; - EXPECT_ERROR_3(EBADF, read, fd, buffer, sizeof(buffer)); - rc = close(fd); - ASSERT(rc == 0); -} - -static void test_write_to_readonly() -{ - char str[] = "hello"; - int fd = open("/tmp/abcd123", O_CREAT | O_RDONLY); - ASSERT(fd >= 0); - int rc; - EXPECT_ERROR_3(EBADF, write, fd, str, sizeof(str)); - rc = close(fd); - ASSERT(rc == 0); -} - -static void test_read_past_eof() -{ - char buffer[BUFSIZ]; - int fd = open("/home/anon/myfile.txt", O_RDONLY); - if (fd < 0) - perror("open"); - ASSERT(fd >= 0); - int rc; - rc = lseek(fd, 9999, SEEK_SET); - if (rc < 0) - perror("lseek"); - rc = read(fd, buffer, sizeof(buffer)); - if (rc < 0) - perror("read"); - if (rc > 0) - fprintf(stderr, "read %d bytes past EOF\n", rc); - rc = close(fd); - ASSERT(rc == 0); -} - -static void test_ftruncate_readonly() -{ - int fd = open("/tmp/trunctest", O_RDONLY | O_CREAT, 0666); - ASSERT(fd >= 0); - int rc; - EXPECT_ERROR_2(EBADF, ftruncate, fd, 0); - close(fd); -} - -static void test_ftruncate_negative() -{ - int fd = open("/tmp/trunctest", O_RDWR | O_CREAT, 0666); - ASSERT(fd >= 0); - int rc; - EXPECT_ERROR_2(EINVAL, ftruncate, fd, -1); - close(fd); -} - -static void test_mmap_directory() -{ - int fd = open("/tmp", O_RDONLY | O_DIRECTORY); - ASSERT(fd >= 0); - auto* ptr = mmap(nullptr, 4096, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0); - if (ptr != MAP_FAILED) { - fprintf(stderr, "Boo! mmap() of a directory succeeded!\n"); - return; - } - if (errno != ENODEV) { - fprintf(stderr, "Boo! mmap() of a directory gave errno=%d instead of ENODEV!\n", errno); - return; - } - close(fd); -} - -static void test_tmpfs_read_past_end() -{ - int fd = open("/tmp/x", O_RDWR | O_CREAT | O_TRUNC, 0600); - ASSERT(fd >= 0); - - int rc = ftruncate(fd, 1); - ASSERT(rc == 0); - - rc = lseek(fd, 4096, SEEK_SET); - ASSERT(rc == 4096); - - char buffer[16]; - int nread = read(fd, buffer, sizeof(buffer)); - if (nread != 0) { - fprintf(stderr, "Expected 0-length read past end of file in /tmp\n"); - } - close(fd); -} - -static void test_procfs_read_past_end() -{ - int fd = open("/proc/uptime", O_RDONLY); - ASSERT(fd >= 0); - - int rc = lseek(fd, 4096, SEEK_SET); - ASSERT(rc == 4096); - - char buffer[16]; - int nread = read(fd, buffer, sizeof(buffer)); - if (nread != 0) { - fprintf(stderr, "Expected 0-length read past end of file in /proc\n"); - } - close(fd); -} - -static void test_open_create_device() -{ - int fd = open("/tmp/fakedevice", (O_RDWR | O_CREAT), (S_IFCHR | 0600)); - ASSERT(fd >= 0); - - struct stat st; - if (fstat(fd, &st) < 0) { - perror("stat"); - ASSERT_NOT_REACHED(); - } - - if (st.st_mode != 0100600) { - fprintf(stderr, "Expected mode 0100600 after attempt to create a device node with open(O_CREAT), mode=%o\n", st.st_mode); - } - unlink("/tmp/fakedevice"); - close(fd); -} - -static void test_unlink_symlink() -{ - int rc = symlink("/proc/2/foo", "/tmp/linky"); - if (rc < 0) { - perror("symlink"); - ASSERT_NOT_REACHED(); - } - - auto target = Core::File::read_link("/tmp/linky"); - ASSERT(target == "/proc/2/foo"); - - rc = unlink("/tmp/linky"); - if (rc < 0) { - perror("unlink"); - fprintf(stderr, "Expected unlink() of a symlink into an unreadable directory to succeed!\n"); - } -} - -static void test_eoverflow() -{ - int fd = open("/tmp/x", O_RDWR); - ASSERT(fd >= 0); - - int rc = lseek(fd, INT32_MAX, SEEK_SET); - ASSERT(rc == INT32_MAX); - - char buffer[16]; - rc = read(fd, buffer, sizeof(buffer)); - if (rc >= 0 || errno != EOVERFLOW) { - fprintf(stderr, "Expected EOVERFLOW when trying to read past INT32_MAX\n"); - } - rc = write(fd, buffer, sizeof(buffer)); - if (rc >= 0 || errno != EOVERFLOW) { - fprintf(stderr, "Expected EOVERFLOW when trying to write past INT32_MAX\n"); - } - close(fd); -} - -static void test_rmdir_while_inside_dir() -{ - int rc = mkdir("/home/anon/testdir", 0700); - ASSERT(rc == 0); - - rc = chdir("/home/anon/testdir"); - ASSERT(rc == 0); - - rc = rmdir("/home/anon/testdir"); - ASSERT(rc == 0); - - int fd = open("x", O_CREAT | O_RDWR, 0600); - if (fd >= 0 || errno != ENOENT) { - fprintf(stderr, "Expected ENOENT when trying to create a file inside a deleted directory. Got %d with errno=%d\n", fd, errno); - } - - rc = chdir("/home/anon"); - ASSERT(rc == 0); -} - -static void test_writev() -{ - int pipefds[2]; - pipe(pipefds); - - iovec iov[2]; - iov[0].iov_base = const_cast((const void*)"Hello"); - iov[0].iov_len = 5; - iov[1].iov_base = const_cast((const void*)"Friends"); - iov[1].iov_len = 7; - int nwritten = writev(pipefds[1], iov, 2); - if (nwritten < 0) { - perror("writev"); - ASSERT_NOT_REACHED(); - } - if (nwritten != 12) { - fprintf(stderr, "Didn't write 12 bytes to pipe with writev\n"); - ASSERT_NOT_REACHED(); - } - - char buffer[32]; - int nread = read(pipefds[0], buffer, sizeof(buffer)); - if (nread != 12 || memcmp(buffer, "HelloFriends", 12)) { - fprintf(stderr, "Didn't read the expected data from pipe after writev\n"); - ASSERT_NOT_REACHED(); - } - - close(pipefds[0]); - close(pipefds[1]); -} - -static void test_rmdir_root() -{ - int rc = rmdir("/"); - if (rc != -1 || errno != EBUSY) { - warnln("rmdir(/) didn't fail with EBUSY"); - ASSERT_NOT_REACHED(); - } -} - -int main() -{ - int rc; - EXPECT_ERROR_2(ENOTDIR, open, "/dev/zero", (O_DIRECTORY | O_RDONLY)); - EXPECT_ERROR_2(EINVAL, open, "/dev/zero", (O_DIRECTORY | O_CREAT | O_RDWR)); - EXPECT_ERROR_2(EEXIST, open, "/dev/zero", (O_CREAT | O_EXCL | O_RDWR)); - EXPECT_ERROR_2(EINVAL, open, "/tmp/abcdef", (O_DIRECTORY | O_CREAT | O_RDWR)); - EXPECT_ERROR_2(EACCES, open, "/proc/all", (O_RDWR)); - EXPECT_ERROR_2(ENOENT, open, "/boof/baaf/nonexistent", (O_CREAT | O_RDWR)); - EXPECT_ERROR_2(EISDIR, open, "/tmp", (O_DIRECTORY | O_RDWR)); - - test_read_from_directory(); - test_write_to_directory(); - test_read_from_writeonly(); - test_write_to_readonly(); - test_read_past_eof(); - test_ftruncate_readonly(); - test_ftruncate_negative(); - test_mmap_directory(); - test_tmpfs_read_past_end(); - test_procfs_read_past_end(); - test_open_create_device(); - test_unlink_symlink(); - test_eoverflow(); - test_rmdir_while_inside_dir(); - test_writev(); - test_rmdir_root(); - - EXPECT_ERROR_2(EPERM, link, "/", "/home/anon/lolroot"); - - return 0; -} diff --git a/Userland/top.cpp b/Userland/top.cpp deleted file mode 100644 index c53dfb88f0..0000000000 --- a/Userland/top.cpp +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct ThreadData { - int tid; - pid_t pid; - pid_t pgid; - pid_t pgp; - pid_t sid; - uid_t uid; - gid_t gid; - pid_t ppid; - unsigned nfds; - String name; - String tty; - size_t amount_virtual; - size_t amount_resident; - size_t amount_shared; - unsigned syscall_count; - unsigned inode_faults; - unsigned zero_faults; - unsigned cow_faults; - unsigned times_scheduled; - - unsigned times_scheduled_since_prev { 0 }; - unsigned cpu_percent { 0 }; - unsigned cpu_percent_decimal { 0 }; - - u32 priority; - String username; - String state; -}; - -struct PidAndTid { - bool operator==(const PidAndTid& other) const - { - return pid == other.pid && tid == other.tid; - } - pid_t pid; - int tid; -}; - -namespace AK { -template<> -struct Traits : public GenericTraits { - static unsigned hash(const PidAndTid& value) { return pair_int_hash(value.pid, value.tid); } -}; -} - -struct Snapshot { - HashMap map; - u32 sum_times_scheduled { 0 }; -}; - -static Snapshot get_snapshot() -{ - auto all_processes = Core::ProcessStatisticsReader::get_all(); - if (!all_processes.has_value()) - return {}; - - Snapshot snapshot; - for (auto& it : all_processes.value()) { - auto& stats = it.value; - for (auto& thread : stats.threads) { - snapshot.sum_times_scheduled += thread.times_scheduled; - ThreadData thread_data; - thread_data.tid = thread.tid; - thread_data.pid = stats.pid; - thread_data.pgid = stats.pgid; - thread_data.pgp = stats.pgp; - thread_data.sid = stats.sid; - thread_data.uid = stats.uid; - thread_data.gid = stats.gid; - thread_data.ppid = stats.ppid; - thread_data.nfds = stats.nfds; - thread_data.name = stats.name; - thread_data.tty = stats.tty; - thread_data.amount_virtual = stats.amount_virtual; - thread_data.amount_resident = stats.amount_resident; - thread_data.amount_shared = stats.amount_shared; - thread_data.syscall_count = thread.syscall_count; - thread_data.inode_faults = thread.inode_faults; - thread_data.zero_faults = thread.zero_faults; - thread_data.cow_faults = thread.cow_faults; - thread_data.times_scheduled = thread.times_scheduled; - thread_data.priority = thread.priority; - thread_data.state = thread.state; - thread_data.username = stats.username; - - snapshot.map.set({ stats.pid, thread.tid }, move(thread_data)); - } - } - - return snapshot; -} - -static bool g_window_size_changed = true; -static struct winsize g_window_size; - -int main(int, char**) -{ - if (pledge("stdio rpath tty sigaction ", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/proc/all", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/etc/passwd", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - signal(SIGWINCH, [](int) { - g_window_size_changed = true; - }); - - if (pledge("stdio rpath tty", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector threads; - auto prev = get_snapshot(); - usleep(10000); - for (;;) { - if (g_window_size_changed) { - int rc = ioctl(STDOUT_FILENO, TIOCGWINSZ, &g_window_size); - if (rc < 0) { - perror("ioctl(TIOCGWINSZ)"); - return 1; - } - g_window_size_changed = false; - } - - auto current = get_snapshot(); - auto sum_diff = current.sum_times_scheduled - prev.sum_times_scheduled; - - printf("\033[3J\033[H\033[2J"); - printf("\033[47;30m%6s %3s %3s %-9s %-10s %6s %6s %4s %s\033[K\033[0m\n", - "PID", - "TID", - "PRI", - "USER", - "STATE", - "VIRT", - "PHYS", - "%CPU", - "NAME"); - for (auto& it : current.map) { - auto pid_and_tid = it.key; - if (pid_and_tid.pid == 0) - continue; - u32 times_scheduled_now = it.value.times_scheduled; - auto jt = prev.map.find(pid_and_tid); - if (jt == prev.map.end()) - continue; - u32 times_scheduled_before = (*jt).value.times_scheduled; - u32 times_scheduled_diff = times_scheduled_now - times_scheduled_before; - it.value.times_scheduled_since_prev = times_scheduled_diff; - it.value.cpu_percent = ((times_scheduled_diff * 100) / sum_diff); - it.value.cpu_percent_decimal = (((times_scheduled_diff * 1000) / sum_diff) % 10); - threads.append(&it.value); - } - - quick_sort(threads, [](auto* p1, auto* p2) { - return p2->times_scheduled_since_prev < p1->times_scheduled_since_prev; - }); - - int row = 0; - for (auto* thread : threads) { - int nprinted = printf("%6d %3d %2u %-9s %-10s %6zu %6zu %2u.%1u ", - thread->pid, - thread->tid, - thread->priority, - thread->username.characters(), - thread->state.characters(), - thread->amount_virtual / 1024, - thread->amount_resident / 1024, - thread->cpu_percent, - thread->cpu_percent_decimal); - - int remaining = g_window_size.ws_col - nprinted; - fwrite(thread->name.characters(), 1, max(0, min(remaining, (int)thread->name.length())), stdout); - putchar('\n'); - - if (++row >= (g_window_size.ws_row - 2)) - break; - } - threads.clear_with_capacity(); - prev = move(current); - sleep(1); - } - return 0; -} diff --git a/Userland/touch.cpp b/Userland/touch.cpp deleted file mode 100644 index ef6c16162c..0000000000 --- a/Userland/touch.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static bool file_exists(const char* path) -{ - struct stat st; - int rc = stat(path, &st); - if (rc < 0) { - if (errno == ENOENT) - return false; - } - if (rc == 0) { - return true; - } - perror("stat"); - exit(1); -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath cpath fattr", nullptr)) { - perror("pledge"); - return 1; - } - - Vector paths; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Create a file, or update its mtime (time of last modification)."); - args_parser.add_positional_argument(paths, "Files to touch", "path", Core::ArgsParser::Required::Yes); - args_parser.parse(argc, argv); - - for (auto path : paths) { - if (file_exists(path)) { - int rc = utime(path, nullptr); - if (rc < 0) - perror("utime"); - } else { - int fd = open(path, O_CREAT, 0100644); - if (fd < 0) { - perror("open"); - return 1; - } - int rc = close(fd); - if (rc < 0) { - perror("close"); - return 1; - } - } - } - return 0; -} diff --git a/Userland/tr.cpp b/Userland/tr.cpp deleted file mode 100644 index 8096598e9a..0000000000 --- a/Userland/tr.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - const char* from_chars = nullptr; - const char* to_chars = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(from_chars, "Character to translate from", "from"); - args_parser.add_positional_argument(to_chars, "Character to translate to", "to"); - args_parser.parse(argc, argv); - - // TODO: Support multiple characters to translate from and to - auto from = from_chars[0]; - auto to = to_chars[0]; - - for (;;) { - char ch = fgetc(stdin); - if (feof(stdin)) - break; - if (ch == from) - putchar(to); - else - putchar(ch); - } - - return 0; -} diff --git a/Userland/tree.cpp b/Userland/tree.cpp deleted file mode 100644 index 5f6dd334a5..0000000000 --- a/Userland/tree.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2020, Stijn De Ridder - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static bool flag_show_hidden_files = false; -static bool flag_show_only_directories = false; -static int max_depth = INT_MAX; - -static int g_directories_seen = 0; -static int g_files_seen = 0; - -static void print_directory_tree(const String& root_path, int depth, const String& indent_string) -{ - if (depth > 0) { - String root_indent_string; - if (depth > 1) { - root_indent_string = indent_string.substring(0, (depth - 1) * 4); - } else { - root_indent_string = ""; - } - out("{}|-- ", root_indent_string); - } - - String root_dir_name = AK::LexicalPath(root_path).basename(); - out("\033[34;1m{}\033[0m\n", root_dir_name); - - if (depth >= max_depth) { - return; - } - - Core::DirIterator di(root_path, flag_show_hidden_files ? Core::DirIterator::SkipParentAndBaseDir : Core::DirIterator::SkipDots); - if (di.has_error()) { - warnln("{}: {}", root_path, di.error_string()); - return; - } - - Vector names; - while (di.has_next()) { - String name = di.next_path(); - if (di.has_error()) { - warnln("{}: {}", root_path, di.error_string()); - continue; - } - names.append(name); - } - - quick_sort(names); - - for (size_t i = 0; i < names.size(); i++) { - String name = names[i]; - - StringBuilder builder; - builder.append(root_path); - if (!root_path.ends_with('/')) { - builder.append('/'); - } - builder.append(name); - String full_path = builder.to_string(); - - struct stat st; - int rc = lstat(full_path.characters(), &st); - if (rc == -1) { - warnln("lstat({}) failed: {}", full_path, strerror(errno)); - continue; - } - - if (S_ISDIR(st.st_mode)) { - g_directories_seen++; - - bool at_last_entry = i == names.size() - 1; - String new_indent_string; - if (at_last_entry) { - new_indent_string = String::formatted("{} ", indent_string); - } else { - new_indent_string = String::formatted("{}| ", indent_string); - } - - print_directory_tree(full_path.characters(), depth + 1, new_indent_string); - } else if (!flag_show_only_directories) { - g_files_seen++; - - outln("{}|-- {}", indent_string, name); - } - } -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath tty", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector directories; - - Core::ArgsParser args_parser; - args_parser.add_option(flag_show_hidden_files, "Show hidden files", "all", 'a'); - args_parser.add_option(flag_show_only_directories, "Show only directories", "only-directories", 'd'); - args_parser.add_option(max_depth, "Maximum depth of the tree", "maximum-depth", 'L', "level"); - args_parser.add_positional_argument(directories, "Directories to print", "directories", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (max_depth < 1) { - warnln("{}: Invalid level, must be greater than 0.", argv[0]); - return 1; - } - - if (directories.is_empty()) { - print_directory_tree(".", 0, ""); - puts(""); - } else { - for (const char* directory : directories) { - print_directory_tree(directory, 0, ""); - puts(""); - } - } - - outln("{} directories, {} files", g_directories_seen, g_files_seen); - - return 0; -} diff --git a/Userland/true.cpp b/Userland/true.cpp deleted file mode 100644 index 3e3d125d4c..0000000000 --- a/Userland/true.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include - -int main(int, char**) -{ - return 0; -} diff --git a/Userland/truncate.cpp b/Userland/truncate.cpp deleted file mode 100644 index ad0cbd531d..0000000000 --- a/Userland/truncate.cpp +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include - -#include -#include -#include - -enum TruncateOperation { - OP_Set, - OP_Grow, - OP_Shrink, -}; - -int main(int argc, char** argv) -{ - const char* resize = nullptr; - const char* reference = nullptr; - const char* file = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(resize, "Resize the target file to (or by) this size. Prefix with + or - to expand or shrink the file, or a bare number to set the size exactly", "size", 's', "size"); - args_parser.add_option(reference, "Resize the target file to match the size of this one", "reference", 'r', "file"); - args_parser.add_positional_argument(file, "File path", "file"); - args_parser.parse(argc, argv); - - if (!resize && !reference) { - args_parser.print_usage(stderr, argv[0]); - return 1; - } - - if (resize && reference) { - args_parser.print_usage(stderr, argv[0]); - return 1; - } - - auto op = OP_Set; - int size = 0; - - if (resize) { - String str = resize; - - switch (str[0]) { - case '+': - op = OP_Grow; - str = str.substring(1, str.length() - 1); - break; - case '-': - op = OP_Shrink; - str = str.substring(1, str.length() - 1); - break; - } - - auto size_opt = str.to_int(); - if (!size_opt.has_value()) { - args_parser.print_usage(stderr, argv[0]); - return 1; - } - size = size_opt.value(); - } - - if (reference) { - struct stat st; - int rc = stat(reference, &st); - if (rc < 0) { - perror("stat"); - return 1; - } - - size = st.st_size; - } - - int fd = open(file, O_RDWR | O_CREAT, 0666); - if (fd < 0) { - perror("open"); - return 1; - } - - struct stat st; - if (fstat(fd, &st) < 0) { - perror("fstat"); - return 1; - } - - switch (op) { - case OP_Set: - break; - case OP_Grow: - size = st.st_size + size; - break; - case OP_Shrink: - size = st.st_size - size; - break; - } - - if (ftruncate(fd, size) < 0) { - perror("ftruncate"); - return 1; - } - - if (close(fd) < 0) { - perror("close"); - return 1; - } - - return 0; -} diff --git a/Userland/tt.cpp b/Userland/tt.cpp deleted file mode 100644 index b5f4730ee9..0000000000 --- a/Userland/tt.cpp +++ /dev/null @@ -1,393 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -static int mutex_test(); -static int detached_test(); -static int priority_test(); -static int stack_size_test(); -static int staying_alive_test(); -static int set_stack_test(); - -int main(int argc, char** argv) -{ - const char* test_name = "n"; - - Core::ArgsParser args_parser; - args_parser.set_general_help( - "Exercise error-handling and edge-case paths of the execution environment " - "(i.e., Kernel or UE) by doing unusual thread-related things."); - args_parser.add_positional_argument(test_name, "Test to run (m = mutex, d = detached, p = priority, s = stack size, t = simple thread test, x = set stack, nothing = join race)", "test-name", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (*test_name == 'm') - return mutex_test(); - if (*test_name == 'd') - return detached_test(); - if (*test_name == 'p') - return priority_test(); - if (*test_name == 's') - return stack_size_test(); - if (*test_name == 't') - return staying_alive_test(); - if (*test_name == 'x') - return set_stack_test(); - if (*test_name != 'n') { - args_parser.print_usage(stdout, argv[0]); - return 1; - } - - printf("Hello from the first thread!\n"); - pthread_t thread_id; - int rc = pthread_create( - &thread_id, nullptr, [](void*) -> void* { - printf("Hi there, from the second thread!\n"); - pthread_exit((void*)0xDEADBEEF); - return nullptr; - }, - nullptr); - if (rc < 0) { - perror("pthread_create"); - return 1; - } - void* retval; - rc = pthread_join(thread_id, &retval); - if (rc < 0) { - perror("pthread_join"); - return 1; - } - printf("Okay, joined and got retval=%p\n", retval); - return 0; -} - -static pthread_mutex_t mutex; - -int mutex_test() -{ - int rc = pthread_mutex_init(&mutex, nullptr); - if (rc < 0) { - perror("pthread_mutex_init"); - return 1; - } - pthread_t thread_id; - rc = pthread_create( - &thread_id, nullptr, [](void*) -> void* { - printf("I'm the secondary thread :^)\n"); - for (;;) { - pthread_mutex_lock(&mutex); - printf("Second thread stole mutex\n"); - sleep(1); - printf("Second thread giving back mutex\n"); - pthread_mutex_unlock(&mutex); - sleep(1); - } - pthread_exit((void*)0xDEADBEEF); - return nullptr; - }, - nullptr); - if (rc < 0) { - perror("pthread_create"); - return 1; - } - for (;;) { - pthread_mutex_lock(&mutex); - printf("Obnoxious spam!\n"); - pthread_mutex_unlock(&mutex); - usleep(10000); - } - return 0; -} - -int detached_test() -{ - pthread_attr_t attributes; - int rc = pthread_attr_init(&attributes); - if (rc != 0) { - printf("pthread_attr_init: %s\n", strerror(rc)); - return 1; - } - - int detach_state = 99; // clearly invalid - rc = pthread_attr_getdetachstate(&attributes, &detach_state); - if (rc != 0) { - printf("pthread_attr_getdetachstate: %s\n", strerror(rc)); - return 2; - } - printf("Default detach state: %s\n", detach_state == PTHREAD_CREATE_JOINABLE ? "joinable" : "detached"); - - detach_state = PTHREAD_CREATE_DETACHED; - rc = pthread_attr_setdetachstate(&attributes, detach_state); - if (rc != 0) { - printf("pthread_attr_setdetachstate: %s\n", strerror(rc)); - return 3; - } - printf("Set detach state on new thread to detached\n"); - - pthread_t thread_id; - rc = pthread_create( - &thread_id, &attributes, [](void*) -> void* { - printf("I'm the secondary thread :^)\n"); - sleep(1); - pthread_exit((void*)0xDEADBEEF); - return nullptr; - }, - nullptr); - if (rc != 0) { - printf("pthread_create: %s\n", strerror(rc)); - return 4; - } - - void* ret_val; - rc = pthread_join(thread_id, &ret_val); - if (rc != 0 && rc != EINVAL) { - printf("pthread_join: %s\n", strerror(rc)); - return 5; - } - if (rc != EINVAL) { - printf("Expected EINVAL! Thread was joinable?\n"); - return 6; - } - - sleep(2); - printf("Thread was created detached. I sure hope it exited on its own.\n"); - - rc = pthread_attr_destroy(&attributes); - if (rc != 0) { - printf("pthread_attr_destroy: %s\n", strerror(rc)); - return 7; - } - - return 0; -} - -int priority_test() -{ - pthread_attr_t attributes; - int rc = pthread_attr_init(&attributes); - if (rc != 0) { - printf("pthread_attr_init: %s\n", strerror(rc)); - return 1; - } - - struct sched_param sched_params; - rc = pthread_attr_getschedparam(&attributes, &sched_params); - if (rc != 0) { - printf("pthread_attr_getschedparam: %s\n", strerror(rc)); - return 2; - } - printf("Default priority: %d\n", sched_params.sched_priority); - - sched_params.sched_priority = 3; - rc = pthread_attr_setschedparam(&attributes, &sched_params); - if (rc != 0) { - printf("pthread_attr_setschedparam: %s\n", strerror(rc)); - return 3; - } - printf("Set thread priority to 3\n"); - - pthread_t thread_id; - rc = pthread_create( - &thread_id, &attributes, [](void*) -> void* { - printf("I'm the secondary thread :^)\n"); - sleep(1); - pthread_exit((void*)0xDEADBEEF); - return nullptr; - }, - nullptr); - if (rc < 0) { - perror("pthread_create"); - return 4; - } - - rc = pthread_join(thread_id, nullptr); - if (rc < 0) { - perror("pthread_join"); - return 5; - } - - rc = pthread_attr_destroy(&attributes); - if (rc != 0) { - printf("pthread_attr_destroy: %s\n", strerror(rc)); - return 6; - } - - return 0; -} - -int stack_size_test() -{ - pthread_attr_t attributes; - int rc = pthread_attr_init(&attributes); - if (rc != 0) { - printf("pthread_attr_init: %s\n", strerror(rc)); - return 1; - } - - size_t stack_size; - rc = pthread_attr_getstacksize(&attributes, &stack_size); - if (rc != 0) { - printf("pthread_attr_getstacksize: %s\n", strerror(rc)); - return 2; - } - printf("Default stack size: %zu\n", stack_size); - - stack_size = 8 * 1024 * 1024; - rc = pthread_attr_setstacksize(&attributes, stack_size); - if (rc != 0) { - printf("pthread_attr_setstacksize: %s\n", strerror(rc)); - return 3; - } - printf("Set thread stack size to 8 MiB\n"); - - pthread_t thread_id; - rc = pthread_create( - &thread_id, &attributes, [](void*) -> void* { - printf("I'm the secondary thread :^)\n"); - sleep(1); - pthread_exit((void*)0xDEADBEEF); - return nullptr; - }, - nullptr); - if (rc < 0) { - perror("pthread_create"); - return 4; - } - - rc = pthread_join(thread_id, nullptr); - if (rc < 0) { - perror("pthread_join"); - return 5; - } - - rc = pthread_attr_destroy(&attributes); - if (rc != 0) { - printf("pthread_attr_destroy: %s\n", strerror(rc)); - return 6; - } - - return 0; -} - -int staying_alive_test() -{ - pthread_t thread_id; - int rc = pthread_create( - &thread_id, nullptr, [](void*) -> void* { - printf("I'm the secondary thread :^)\n"); - sleep(20); - printf("Secondary thread is still alive\n"); - sleep(3520); - printf("Secondary thread exiting\n"); - pthread_exit((void*)0xDEADBEEF); - return nullptr; - }, - nullptr); - if (rc < 0) { - perror("pthread_create"); - return 1; - } - - sleep(1); - printf("I'm the main thread :^)\n"); - sleep(3600); - - printf("Main thread exiting\n"); - return 0; -} - -int set_stack_test() -{ - pthread_attr_t attributes; - int rc = pthread_attr_init(&attributes); - if (rc < 0) { - printf("pthread_attr_init: %s\n", strerror(rc)); - return 1; - } - - size_t stack_size = 8 * 1024 * 1024; - void* stack_addr = mmap_with_name(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, 0, 0, "Cool stack"); - - if (!stack_addr) { - perror("mmap_with_name"); - return -1; - } - - rc = pthread_attr_setstack(&attributes, stack_addr, stack_size); - if (rc != 0) { - printf("pthread_attr_setstack: %s\n", strerror(rc)); - return 2; - } - printf("Set thread stack to %p, size %zu\n", stack_addr, stack_size); - - size_t stack_size_verify; - void* stack_addr_verify; - - rc = pthread_attr_getstack(&attributes, &stack_addr_verify, &stack_size_verify); - if (rc != 0) { - printf("pthread_attr_getstack: %s\n", strerror(rc)); - return 3; - } - - if (stack_addr != stack_addr_verify || stack_size != stack_size_verify) { - printf("Stack address and size don't match! addr: %p %p, size: %zu %zu\n", stack_addr, stack_addr_verify, stack_size, stack_size_verify); - return 4; - } - - pthread_t thread_id; - rc = pthread_create( - &thread_id, &attributes, [](void*) -> void* { - printf("I'm the secondary thread :^)\n"); - sleep(1); - pthread_exit((void*)0xDEADBEEF); - return nullptr; - }, - nullptr); - if (rc < 0) { - perror("pthread_create"); - return 5; - } - - rc = pthread_join(thread_id, nullptr); - if (rc < 0) { - perror("pthread_join"); - return 6; - } - - rc = pthread_attr_destroy(&attributes); - if (rc != 0) { - printf("pthread_attr_destroy: %s\n", strerror(rc)); - return 7; - } - - return 0; -} diff --git a/Userland/tty.cpp b/Userland/tty.cpp deleted file mode 100644 index 509eb522de..0000000000 --- a/Userland/tty.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include - -int main(int, char**) -{ - char* tty = ttyname(0); - if (!tty) { - perror("Error"); - return 1; - } - printf("%s\n", tty); - return 0; -} diff --git a/Userland/umount.cpp b/Userland/umount.cpp deleted file mode 100644 index 63b20cf344..0000000000 --- a/Userland/umount.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - const char* mount_point = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(mount_point, "Mount point", "mountpoint"); - args_parser.parse(argc, argv); - - if (umount(mount_point) < 0) { - perror("umount"); - return 1; - } - return 0; -} diff --git a/Userland/uname.cpp b/Userland/uname.cpp deleted file mode 100644 index 99b0606009..0000000000 --- a/Userland/uname.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - bool flag_system = false; - bool flag_node = false; - bool flag_release = false; - bool flag_machine = false; - bool flag_all = false; - - Core::ArgsParser args_parser; - args_parser.add_option(flag_system, "Print the system name (default)", nullptr, 's'); - args_parser.add_option(flag_node, "Print the node name", nullptr, 'n'); - args_parser.add_option(flag_release, "Print the system release", nullptr, 'r'); - args_parser.add_option(flag_machine, "Print the machine hardware name", nullptr, 'm'); - args_parser.add_option(flag_all, "Print all information (same as -snrm)", nullptr, 'a'); - args_parser.parse(argc, argv); - - if (flag_all) - flag_system = flag_node = flag_release = flag_machine = true; - - if (!flag_system && !flag_node && !flag_release && !flag_machine) - flag_system = true; - - utsname uts; - int rc = uname(&uts); - if (rc < 0) { - perror("uname() failed"); - return 0; - } - - Vector parts; - if (flag_system) - parts.append(uts.sysname); - if (flag_node) - parts.append(uts.nodename); - if (flag_release) - parts.append(uts.release); - if (flag_machine) - parts.append(uts.machine); - StringBuilder builder; - builder.join(' ', parts); - puts(builder.to_string().characters()); - return 0; -} diff --git a/Userland/uniq.cpp b/Userland/uniq.cpp deleted file mode 100644 index 0034c1cb62..0000000000 --- a/Userland/uniq.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2020, Matthew L. Curry - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -struct linebuf { - char* buf = NULL; - size_t len = 0; -}; - -static FILE* get_stream(const char* filepath, const char* perms) -{ - FILE* ret; - - if (filepath == nullptr) { - if (perms[0] == 'r') - return stdin; - return stdout; - } - - ret = fopen(filepath, perms); - if (ret == nullptr) { - perror("fopen"); - exit(1); - } - - return ret; -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath wpath cpath", nullptr) > 0) { - perror("pledge"); - return 1; - } - - const char* inpath = nullptr; - const char* outpath = nullptr; - Core::ArgsParser args_parser; - args_parser.add_positional_argument(inpath, "Input file", "input", Core::ArgsParser::Required::No); - args_parser.add_positional_argument(outpath, "Output file", "output", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - FILE* infile = get_stream(inpath, "r"); - FILE* outfile = get_stream(outpath, "w"); - - struct linebuf buffers[2]; - struct linebuf* previous = &(buffers[0]); - struct linebuf* current = &(buffers[1]); - bool first_run = true; - for (;;) { - errno = 0; - ssize_t rc = getline(&(current->buf), &(current->len), infile); - if (rc < 0 && errno != 0) { - perror("getline"); - exit(1); - } - if (rc < 0) - break; - if (!first_run && strcmp(current->buf, previous->buf) == 0) - continue; - - fputs(current->buf, outfile); - AK::swap(current, previous); - first_run = false; - } - - fclose(infile); - fclose(outfile); - - return 0; -} diff --git a/Userland/unzip.cpp b/Userland/unzip.cpp deleted file mode 100644 index d21228c822..0000000000 --- a/Userland/unzip.cpp +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2020, Andrés Vieira - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -static const u8 central_directory_file_header_sig[] = "\x50\x4b\x01\x02"; - -static bool seek_and_read(u8* buffer, const MappedFile& file, off_t seek_to, size_t bytes_to_read) -{ - if (!buffer) - return false; - - if ((size_t)seek_to >= file.size()) - return false; - - memcpy(buffer, (const char*)file.data() + seek_to, bytes_to_read); - - return true; -} - -static bool find_next_central_directory(off_t file_size, const MappedFile& file, off_t current_index, off_t& return_index) -{ - off_t start_index = current_index == 0 ? current_index : current_index + 1; - for (off_t index = start_index; index < file_size - 4; index++) { - u8 buffer[4]; - if (!seek_and_read(buffer, file, index, 4)) - return false; - - if (!memcmp(buffer, central_directory_file_header_sig, 4)) { - return_index = index; - return true; - } - } - return false; -} - -static bool unpack_file_for_central_directory_index(off_t central_directory_index, const MappedFile& file) -{ - enum CentralFileDirectoryHeaderOffsets { - CFDHCompressionMethodOffset = 10, - CFDHLocalFileHeaderIndexOffset = 42, - }; - enum LocalFileHeaderOffsets { - LFHCompressionMethodOffset = 8, - LFHCompressedSizeOffset = 18, - LFHFileNameLengthOffset = 26, - LFHExtraFieldLengthOffset = 28, - LFHFileNameBaseOffset = 30, - }; - enum CompressionMethod { - None = 0, - Shrunk = 1, - Factor1 = 2, - Factor2 = 3, - Factor3 = 4, - Factor4 = 5, - Implode = 6, - Deflate = 8, - EnhancedDeflate = 9, - PKWareDCLImplode = 10, - BZIP2 = 12, - LZMA = 14, - TERSE = 18, - LZ77 = 19, - }; - - u8 buffer[4]; - if (!seek_and_read(buffer, file, central_directory_index + CFDHLocalFileHeaderIndexOffset, 4)) - return false; - off_t local_file_header_index = buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; - - if (!seek_and_read(buffer, file, local_file_header_index + LFHCompressionMethodOffset, 2)) - return false; - auto compression_method = buffer[1] << 8 | buffer[0]; - // FIXME: Remove once any decompression is supported. - ASSERT(compression_method == None); - - if (!seek_and_read(buffer, file, local_file_header_index + LFHCompressedSizeOffset, 4)) - return false; - off_t compressed_file_size = buffer[3] << 24 | buffer[2] << 16 | buffer[1] << 8 | buffer[0]; - - if (!seek_and_read(buffer, file, local_file_header_index + LFHFileNameLengthOffset, 2)) - return false; - off_t file_name_length = buffer[1] << 8 | buffer[0]; - - if (!seek_and_read(buffer, file, local_file_header_index + LFHExtraFieldLengthOffset, 2)) - return false; - off_t extra_field_length = buffer[1] << 8 | buffer[0]; - - char file_name[file_name_length + 1]; - if (!seek_and_read((u8*)file_name, file, local_file_header_index + LFHFileNameBaseOffset, file_name_length)) - return false; - file_name[file_name_length] = '\0'; - - if (file_name[file_name_length - 1] == '/') { - if (mkdir(file_name, 0755) < 0) { - perror("mkdir"); - return false; - } - } else { - auto new_file = Core::File::construct(String { file_name }); - if (!new_file->open(Core::IODevice::WriteOnly)) { - fprintf(stderr, "Can't write file %s: %s\n", file_name, new_file->error_string()); - return false; - } - - printf(" extracting: %s\n", file_name); - u8 raw_file_contents[compressed_file_size]; - if (!seek_and_read(raw_file_contents, file, local_file_header_index + LFHFileNameBaseOffset + file_name_length + extra_field_length, compressed_file_size)) - return false; - - // FIXME: Try to uncompress data here. We're just ignoring it as no decompression methods are implemented yet. - if (!new_file->write(raw_file_contents, compressed_file_size)) { - fprintf(stderr, "Can't write file contents in %s: %s\n", file_name, new_file->error_string()); - return false; - } - - if (!new_file->close()) { - fprintf(stderr, "Can't close file %s: %s\n", file_name, new_file->error_string()); - return false; - } - } - - return true; -} - -int main(int argc, char** argv) -{ - const char* path; - int map_size_limit = 32 * MiB; - - Core::ArgsParser args_parser; - args_parser.add_option(map_size_limit, "Maximum chunk size to map", "map-size-limit", 0, "size"); - args_parser.add_positional_argument(path, "File to unzip", "path", Core::ArgsParser::Required::Yes); - args_parser.parse(argc, argv); - - String zip_file_path { path }; - - struct stat st; - int rc = stat(zip_file_path.characters(), &st); - if (rc < 0) { - perror("stat"); - return 1; - } - - // FIXME: Map file chunk-by-chunk once we have mmap() with offset. - // This will require mapping some parts then unmapping them repeatedly, - // but it would be significantly faster and less syscall heavy than seek()/read() at every read. - if (st.st_size >= map_size_limit) { - fprintf(stderr, "unzip warning: Refusing to map file since it is larger than %s, pass '--map-size-limit %d' to get around this\n", - human_readable_size(map_size_limit).characters(), - round_up_to_power_of_two(st.st_size, 16)); - return 1; - } - - auto file_or_error = MappedFile ::map(zip_file_path); - if (file_or_error.is_error()) { - warnln("Failed to open {}: {}", zip_file_path, file_or_error.error()); - return 1; - } - auto& mapped_file = *file_or_error.value(); - - printf("Archive: %s\n", zip_file_path.characters()); - - off_t index = 0; - while (find_next_central_directory(st.st_size, mapped_file, index, index)) { - bool success = unpack_file_for_central_directory_index(index, mapped_file); - if (!success) { - printf("Could not find local file header for a file.\n"); - return 4; - } - } - - return 0; -} diff --git a/Userland/uptime.cpp b/Userland/uptime.cpp deleted file mode 100644 index c8b21ae368..0000000000 --- a/Userland/uptime.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include - -int main(int, char**) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - FILE* fp = fopen("/proc/uptime", "r"); - if (!fp) { - perror("fopen(/proc/uptime)"); - return 1; - } - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - char buffer[BUFSIZ]; - auto* p = fgets(buffer, sizeof(buffer), fp); - if (!p) { - perror("fgets"); - return 1; - } - - unsigned seconds; - sscanf(buffer, "%u", &seconds); - - printf("Up "); - - if (seconds / 86400 > 0) { - printf("%d day%s, ", seconds / 86400, (seconds / 86400) == 1 ? "" : "s"); - seconds %= 86400; - } - - if (seconds / 3600 > 0) { - printf("%d hour%s, ", seconds / 3600, (seconds / 3600) == 1 ? "" : "s"); - seconds %= 3600; - } - - if (seconds / 60 > 0) { - printf("%d minute%s, ", seconds / 60, (seconds / 60) == 1 ? "" : "s"); - seconds %= 60; - } - - printf("%d second%s\n", seconds, seconds == 1 ? "" : "s"); - - fclose(fp); - return 0; -} diff --git a/Userland/useradd.cpp b/Userland/useradd.cpp deleted file mode 100644 index 627e1209f2..0000000000 --- a/Userland/useradd.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2019-2020, Jesse Buhagiar - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -constexpr uid_t BASE_UID = 1000; -constexpr gid_t USERS_GID = 100; -constexpr const char* DEFAULT_SHELL = "/bin/Shell"; - -int main(int argc, char** argv) -{ - if (pledge("stdio wpath rpath cpath chown", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* home_path = nullptr; - int uid = 0; - int gid = USERS_GID; - bool create_home_dir = false; - const char* password = ""; - const char* shell = DEFAULT_SHELL; - const char* gecos = ""; - const char* username = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(home_path, "Home directory for the new user", "home-dir", 'd', "path"); - args_parser.add_option(uid, "User ID (uid) for the new user", "uid", 'u', "uid"); - args_parser.add_option(gid, "Group ID (gid) for the new user", "gid", 'g', "gid"); - args_parser.add_option(password, "Encrypted password of the new user", "password", 'p', "password"); - args_parser.add_option(create_home_dir, "Create home directory if it does not exist", "create-home", 'm'); - args_parser.add_option(shell, "Path to the default shell binary for the new user", "shell", 's', "path-to-shell"); - args_parser.add_option(gecos, "GECOS name of the new user", "gecos", 'n', "general-info"); - args_parser.add_positional_argument(username, "Login user identity (username)", "login"); - - args_parser.parse(argc, argv); - - // Let's run a quick sanity check on username - if (strpbrk(username, "\\/!@#$%^&*()~+=`:\n")) { - fprintf(stderr, "invalid character in username, %s\n", username); - return 1; - } - - // Disallow names starting with _ and - - if (username[0] == '_' || username[0] == '-' || !isalpha(username[0])) { - fprintf(stderr, "invalid username, %s\n", username); - return 1; - } - - if (uid < 0) { - fprintf(stderr, "invalid uid %d!\n", uid); - return 3; - } - - // First, let's sort out the uid for the user - if (uid > 0) { - if (getpwuid(static_cast(uid))) { - fprintf(stderr, "uid %u already exists!\n", uid); - return 4; - } - - } else { - for (uid = BASE_UID; getpwuid(static_cast(uid)); uid++) { - } - } - - if (gid < 0) { - fprintf(stderr, "invalid gid %d\n", gid); - return 3; - } - - FILE* pwfile = fopen("/etc/passwd", "a"); - if (!pwfile) { - perror("failed to open /etc/passwd"); - return 1; - } - - String home; - if (!home_path) - home = String::format("/home/%s", username); - else - home = home_path; - - if (create_home_dir) { - if (mkdir(home.characters(), 0700) < 0) { - perror(String::format("failed to create directory %s", home.characters()).characters()); - return 12; - } - - if (chown(home.characters(), static_cast(uid), static_cast(gid)) < 0) { - perror(String::format("failed to chown %s to %u:%u", home.characters(), uid, gid).characters()); - - if (rmdir(home.characters()) < 0) { - perror(String::format("failed to rmdir %s", home.characters()).characters()); - return 12; - } - return 12; - } - } - - struct passwd p; - p.pw_name = const_cast(username); - p.pw_passwd = const_cast(password); - p.pw_dir = const_cast(home.characters()); - p.pw_uid = static_cast(uid); - p.pw_gid = static_cast(gid); - p.pw_shell = const_cast(shell); - p.pw_gecos = const_cast(gecos); - - if (putpwent(&p, pwfile) < 0) { - perror("putpwent"); - return 1; - } - - fclose(pwfile); - - return 0; -} diff --git a/Userland/userdel.cpp b/Userland/userdel.cpp deleted file mode 100644 index 818b73f0b4..0000000000 --- a/Userland/userdel.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2020, Fei Wu - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio wpath rpath cpath fattr proc exec", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/etc/", "rwc") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/bin/rm", "x") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - const char* username = nullptr; - bool remove_home = false; - - Core::ArgsParser args_parser; - args_parser.add_option(remove_home, "Remove home directory", "remove", 'r'); - args_parser.add_positional_argument(username, "Login user identity (username)", "login"); - args_parser.parse(argc, argv); - - if (!remove_home) { - if (pledge("stdio wpath rpath cpath fattr", nullptr) < 0) { - perror("pledge"); - return 1; - } - } - - char temp_filename[] = "/etc/passwd.XXXXXX"; - auto fd = mkstemp(temp_filename); - if (fd == -1) { - perror("failed to create temporary file"); - return 1; - } - - FILE* temp_file = fdopen(fd, "w"); - if (!temp_file) { - perror("fdopen"); - if (unlink(temp_filename) < 0) { - perror("unlink"); - } - - return 1; - } - - bool user_exists = false; - String home_directory; - - int rc = 0; - setpwent(); - for (auto* pw = getpwent(); pw; pw = getpwent()) { - if (strcmp(pw->pw_name, username)) { - if (putpwent(pw, temp_file) != 0) { - perror("failed to put an entry in the temporary passwd file"); - rc = 1; - break; - } - } else { - user_exists = true; - if (remove_home) - home_directory = pw->pw_dir; - } - } - endpwent(); - - if (fclose(temp_file)) { - perror("fclose"); - if (!rc) - rc = 1; - } - - if (rc == 0 && !user_exists) { - fprintf(stderr, "specified user doesn't exist\n"); - rc = 6; - } - - if (rc == 0 && chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) { - perror("chmod"); - rc = 1; - } - - if (rc == 0 && rename(temp_filename, "/etc/passwd") < 0) { - perror("failed to rename the temporary passwd file"); - rc = 1; - } - - if (rc) { - if (unlink(temp_filename) < 0) { - perror("unlink"); - } - return rc; - } - - if (remove_home) { - if (access(home_directory.characters(), F_OK) == -1) - return 0; - - String real_path = Core::File::real_path_for(home_directory); - - if (real_path == "/") { - fprintf(stderr, "home directory is /, not deleted!\n"); - return 12; - } - - pid_t child; - const char* argv[] = { "rm", "-r", home_directory.characters(), nullptr }; - if ((errno = posix_spawn(&child, "/bin/rm", nullptr, nullptr, const_cast(argv), environ))) { - perror("posix_spawn"); - return 12; - } - int wstatus; - if (waitpid(child, &wstatus, 0) < 0) { - perror("waitpid"); - return 12; - } - if (WEXITSTATUS(wstatus)) { - fprintf(stderr, "failed to remove the home directory\n"); - return 12; - } - } - - return 0; -} diff --git a/Userland/utmpupdate.cpp b/Userland/utmpupdate.cpp deleted file mode 100644 index c055559b7e..0000000000 --- a/Userland/utmpupdate.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio wpath cpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/var/run/utmp", "rwc") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - pid_t pid = 0; - bool flag_create = false; - bool flag_delete = false; - const char* tty_name = nullptr; - const char* from = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_option(flag_create, "Create entry", "create", 'c'); - args_parser.add_option(flag_delete, "Delete entry", "delete", 'd'); - args_parser.add_option(pid, "PID", "PID", 'p', "PID"); - args_parser.add_option(from, "From", "from", 'f', "From"); - args_parser.add_positional_argument(tty_name, "TTY name", "tty"); - - args_parser.parse(argc, argv); - - if (flag_create && flag_delete) { - warnln("-c and -d are mutually exclusive"); - return 1; - } - - dbgln("Updating utmp from UID={} GID={} EGID={} PID={}", getuid(), getgid(), getegid(), pid); - - auto file_or_error = Core::File::open("/var/run/utmp", Core::IODevice::ReadWrite); - if (file_or_error.is_error()) { - dbgln("Error: {}", file_or_error.error()); - return 1; - } - - auto& file = *file_or_error.value(); - - auto file_contents = file.read_all(); - auto previous_json = JsonValue::from_string(file_contents); - - JsonObject json; - - if (!previous_json.has_value() || !previous_json.value().is_object()) { - dbgln("Error: Could not parse JSON"); - } else { - json = previous_json.value().as_object(); - } - - if (flag_create) { - JsonObject entry; - entry.set("pid", pid); - entry.set("uid", getuid()); - entry.set("from", from); - entry.set("login_at", time(nullptr)); - json.set(tty_name, move(entry)); - } else { - ASSERT(flag_delete); - dbgln("Removing {} from utmp", tty_name); - json.remove(tty_name); - } - - if (!file.seek(0)) { - dbgln("Seek failed"); - return 1; - } - - if (!file.truncate(0)) { - dbgln("Truncation failed"); - return 1; - } - - if (!file.write(json.to_string())) { - dbgln("Write failed"); - return 1; - } - - return 0; -} diff --git a/Userland/w.cpp b/Userland/w.cpp deleted file mode 100644 index 4c9dbc106b..0000000000 --- a/Userland/w.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int main() -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/dev", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/etc/passwd", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/var/run/utmp", "r") < 0) { - perror("unveil"); - return 1; - } - - if (unveil("/proc", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - auto file_or_error = Core::File::open("/var/run/utmp", Core::IODevice::ReadOnly); - if (file_or_error.is_error()) { - warnln("Error: {}", file_or_error.error()); - return 1; - } - auto& file = *file_or_error.value(); - auto json = JsonValue::from_string(file.read_all()); - if (!json.has_value() || !json.value().is_object()) { - warnln("Error: Could not parse /var/run/utmp"); - return 1; - } - - auto process_statistics = Core::ProcessStatisticsReader::get_all(); - if (!process_statistics.has_value()) { - warnln("Error: Could not get process statistics"); - return 1; - } - - auto now = time(nullptr); - - printf("\033[1m%-10s %-12s %-16s %-6s %s\033[0m\n", - "USER", "TTY", "LOGIN@", "IDLE", "WHAT"); - json.value().as_object().for_each_member([&](auto& tty, auto& value) { - const JsonObject& entry = value.as_object(); - auto uid = entry.get("uid").to_u32(); - [[maybe_unused]] auto pid = entry.get("pid").to_i32(); - - auto login_time = Core::DateTime::from_timestamp(entry.get("login_at").to_number()); - auto login_at = login_time.to_string("%b%d %H:%M:%S"); - - auto* pw = getpwuid(uid); - String username; - if (pw) - username = pw->pw_name; - else - username = String::number(uid); - - StringBuilder builder; - String idle_string = "n/a"; - struct stat st; - if (stat(tty.characters(), &st) == 0) { - auto idle_time = now - st.st_mtime; - if (idle_time >= 0) { - builder.appendf("%llds", idle_time); - idle_string = builder.to_string(); - } - } - - String what = "n/a"; - - for (auto& it : process_statistics.value()) { - if (it.value.tty == tty && it.value.pid == it.value.pgid) - what = it.value.name; - } - - printf("%-10s %-12s %-16s %-6s %s\n", - username.characters(), - tty.characters(), - login_at.characters(), - idle_string.characters(), - what.characters()); - }); - return 0; -} diff --git a/Userland/watch.cpp b/Userland/watch.cpp deleted file mode 100644 index 1377a7667c..0000000000 --- a/Userland/watch.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2020, Sahan Fernando - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -static int opt_interval = 2; -static bool flag_noheader = false; -static bool flag_beep_on_fail = false; -static volatile int exit_code = 0; -static volatile pid_t child_pid = -1; - -static String build_header_string(const Vector& command, const struct timeval& interval) -{ - StringBuilder builder; - builder.appendff("Every {}", interval.tv_sec); - builder.appendf(".%ds: \x1b[1m", interval.tv_usec / 100000); - builder.join(' ', command); - builder.append("\x1b[0m"); - return builder.build(); -} - -static struct timeval get_current_time() -{ - struct timespec ts; - struct timeval tv; - clock_gettime(CLOCK_MONOTONIC_COARSE, &ts); - timespec_to_timeval(ts, tv); - return tv; -} - -static int64_t usecs_from(const struct timeval& start, const struct timeval& end) -{ - struct timeval diff; - timeval_sub(end, start, diff); - return 1000000 * diff.tv_sec + diff.tv_usec; -} - -static void handle_signal(int signal) -{ - if (child_pid > 0) { - if (kill(child_pid, signal) < 0) { - perror("kill"); - } - int status; - if (waitpid(child_pid, &status, 0) < 0) { - perror("waitpid"); - } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - exit_code = 1; - } - } - exit(exit_code); -} - -static int run_command(const Vector& command) -{ - if ((errno = posix_spawnp(const_cast(&child_pid), command[0], nullptr, nullptr, const_cast(command.data()), environ))) { - exit_code = 1; - perror("posix_spawn"); - return errno; - } - - // Wait for the child to terminate, then return its exit code. - int status; - pid_t exited_pid; - do { - exited_pid = waitpid(child_pid, &status, 0); - } while (exited_pid < 0 && errno == EINTR); - ASSERT(exited_pid == child_pid); - child_pid = -1; - if (exited_pid < 0) { - perror("waitpid"); - return 1; - } - if (WIFEXITED(status)) { - return WEXITSTATUS(status); - } else { - return 1; - } -} - -int main(int argc, char** argv) -{ - signal(SIGINT, handle_signal); - if (pledge("stdio proc exec", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector command; - Core::ArgsParser args_parser; - args_parser.set_general_help("Execute a command repeatedly, and watch its output over time."); - args_parser.add_option(opt_interval, "Amount of time between updates", "interval", 'n', "seconds"); - args_parser.add_option(flag_noheader, "Turn off the header describing the command and interval", "no-title", 't'); - args_parser.add_option(flag_beep_on_fail, "Beep if the command has a non-zero exit code", "beep", 'b'); - args_parser.add_positional_argument(command, "Command to run", "command"); - args_parser.parse(argc, argv); - - struct timeval interval; - if (opt_interval <= 0) { - interval = { 0, 100000 }; - } else { - interval = { opt_interval, 0 }; - } - - auto header = build_header_string(command, interval); - command.append(nullptr); - - auto now = get_current_time(); - auto next_run_time = now; - while (true) { - int usecs_to_sleep = usecs_from(now, next_run_time); - while (usecs_to_sleep > 0) { - usleep(usecs_to_sleep); - now = get_current_time(); - usecs_to_sleep = usecs_from(now, next_run_time); - } - // Clear the screen, then reset the cursor position to the top left. - fprintf(stderr, "\033[H\033[2J"); - // Print the header. - if (!flag_noheader) { - fprintf(stderr, "%s\n\n", header.characters()); - } else { - fflush(stderr); - } - if (run_command(command) != 0) { - exit_code = 1; - if (flag_beep_on_fail) { - fprintf(stderr, "\a"); - fflush(stderr); - } - } - now = get_current_time(); - timeval_add(next_run_time, interval, next_run_time); - if (usecs_from(now, next_run_time) < 0) { - // The next execution is overdue, so we set next_run_time to now to prevent drift. - next_run_time = now; - } - } -} diff --git a/Userland/wc.cpp b/Userland/wc.cpp deleted file mode 100644 index cf9fa62d35..0000000000 --- a/Userland/wc.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * Copyright (c) 2021, Emanuele Torre - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include - -struct Count { - String name; - bool exists { true }; - unsigned lines { 0 }; - unsigned characters { 0 }; - unsigned words { 0 }; - size_t bytes { 0 }; -}; - -bool g_output_line = false; -bool g_output_byte = false; -bool g_output_word = false; - -static void wc_out(const Count& count) -{ - if (g_output_line) - out("{:7} ", count.lines); - if (g_output_word) - out("{:7} ", count.words); - if (g_output_byte) - out("{:7} ", count.bytes); - - outln("{:>14}", count.name); -} - -static Count get_count(const String& file_specifier) -{ - Count count; - FILE* file_pointer = nullptr; - if (file_specifier == "-") { - count.name = ""; - file_pointer = stdin; - } else { - count.name = file_specifier; - if ((file_pointer = fopen(file_specifier.characters(), "r")) == nullptr) { - warnln("wc: unable to open {}", file_specifier); - count.exists = false; - return count; - } - } - - bool start_a_new_word = true; - for (int ch = fgetc(file_pointer); ch != EOF; ch = fgetc(file_pointer)) { - count.bytes++; - if (isspace(ch)) { - start_a_new_word = true; - if (ch == '\n') - count.lines++; - } else if (start_a_new_word) { - start_a_new_word = false; - count.words++; - } - } - - if (file_pointer != stdin) - fclose(file_pointer); - - return count; -} - -static Count get_total_count(const Vector& counts) -{ - Count total_count { "total" }; - for (auto& count : counts) { - total_count.lines += count.lines; - total_count.words += count.words; - total_count.characters += count.characters; - total_count.bytes += count.bytes; - } - return total_count; -} - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - Vector file_specifiers; - - Core::ArgsParser args_parser; - args_parser.add_option(g_output_line, "Output line count", "lines", 'l'); - args_parser.add_option(g_output_byte, "Output byte count", "bytes", 'c'); - args_parser.add_option(g_output_word, "Output word count", "words", 'w'); - args_parser.add_positional_argument(file_specifiers, "File to process", "file", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - if (!g_output_line && !g_output_byte && !g_output_word) - g_output_line = g_output_byte = g_output_word = true; - - Vector counts; - for (const auto& file_specifier : file_specifiers) - counts.append(get_count(file_specifier)); - - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (file_specifiers.is_empty()) - counts.append(get_count("-")); - else if (file_specifiers.size() > 1) - counts.append(get_total_count(counts)); - - for (const auto& count : counts) { - if (count.exists) - wc_out(count); - } - - return 0; -} diff --git a/Userland/which.cpp b/Userland/which.cpp deleted file mode 100644 index e902fae2e7..0000000000 --- a/Userland/which.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* filename = nullptr; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(filename, "Name of executable", "executable"); - args_parser.parse(argc, argv); - - auto fullpath = Core::find_executable_in_path(filename); - if (fullpath.is_null()) { - printf("no '%s' in path\n", filename); - return 1; - } - - printf("%s\n", fullpath.characters()); - return 0; -} diff --git a/Userland/whoami.cpp b/Userland/whoami.cpp deleted file mode 100644 index 830d1796ba..0000000000 --- a/Userland/whoami.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include - -int main(int, char**) -{ - if (pledge("stdio rpath", nullptr) < 0) { - perror("pledge"); - return 1; - } - - if (unveil("/etc/passwd", "r") < 0) { - perror("unveil"); - return 1; - } - - unveil(nullptr, nullptr); - - puts(getlogin()); - return 0; -} diff --git a/Userland/xargs.cpp b/Userland/xargs.cpp deleted file mode 100644 index 68e4eb54e3..0000000000 --- a/Userland/xargs.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (c) 2020, The SerenityOS developers. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -bool run_command(Vector&& child_argv, bool verbose, bool is_stdin, int devnull_fd); - -enum Decision { - Unget, - Continue, - Stop, -}; -bool read_items(FILE* fp, char entry_separator, Function); - -class ParsedInitialArguments { -public: - ParsedInitialArguments(Vector&, const StringView& placeholder); - - void for_each_joined_argument(const StringView&, Function) const; - - size_t size() const { return m_all_parts.size(); } - -private: - Vector> m_all_parts; -}; - -int main(int argc, char** argv) -{ - if (pledge("stdio rpath proc exec", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* placeholder = nullptr; - bool split_with_nulls = false; - const char* specified_delimiter = "\n"; - Vector arguments; - bool verbose = false; - const char* file_to_read = "-"; - int max_lines_for_one_command = 0; - int max_bytes_for_one_command = ARG_MAX; - - Core::ArgsParser args_parser; - args_parser.set_general_help("Read arguments from stdin and interpret them as command-line arguments for another program. See also: 'man xargs'."); - args_parser.add_option(placeholder, "Placeholder string to be replaced in arguments", "replace", 'I', "placeholder"); - args_parser.add_option(split_with_nulls, "Split input items with the null character instead of newline", "null", '0'); - args_parser.add_option(specified_delimiter, "Split the input items with the specified character", "delimiter", 'd', "delim"); - args_parser.add_option(verbose, "Display each command before executing it", "verbose", 'v'); - args_parser.add_option(file_to_read, "Read arguments from the specified file instead of stdin", "arg-file", 'a', "file"); - args_parser.add_option(max_lines_for_one_command, "Use at most max-lines lines to create a command", "line-limit", 'L', "max-lines"); - args_parser.add_option(max_bytes_for_one_command, "Use at most max-chars characters to create a command", "char-limit", 's', "max-chars"); - args_parser.add_positional_argument(arguments, "Command and any initial arguments for it", "command", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - size_t max_bytes = min(ARG_MAX, max_bytes_for_one_command); - size_t max_lines = max(max_lines_for_one_command, 0); - - if (!split_with_nulls && strlen(specified_delimiter) > 1) { - fprintf(stderr, "xargs: the delimiter must be a single byte\n"); - return 1; - } - - char entry_separator = split_with_nulls ? '\0' : *specified_delimiter; - - StringView placeholder_view { placeholder }; - - if (!placeholder_view.is_empty()) - max_lines = 1; - - if (arguments.is_empty()) - arguments.append("echo"); - - ParsedInitialArguments initial_arguments(arguments, placeholder_view); - - FILE* fp = stdin; - bool is_stdin = true; - - if (StringView { "-" } != file_to_read) { - // A file was specified, try to open it. - fp = fopen(file_to_read, "re"); - if (!fp) { - perror("fopen"); - return 1; - } - is_stdin = false; - } - - StringBuilder builder; - Vector child_argv; - - int devnull_fd = 0; - - if (is_stdin) { - devnull_fd = open("/dev/null", O_RDONLY | O_CLOEXEC); - if (devnull_fd < 0) { - perror("open"); - return 1; - } - } - - size_t total_command_length = 0; - size_t items_used_for_this_command = 0; - - auto fail = read_items(fp, entry_separator, [&](StringView item) { - if (item.ends_with('\n')) - item = item.substring_view(0, item.length() - 1); - - if (item.is_empty()) - return Continue; - - // The first item is processed differently, as all the initial-arguments are processed _with_ that item - // as their substitution target (assuming that substitution is enabled). - // Note that if substitution is not enabled, we manually insert a substitution target at the end of initial-arguments, - // so this item has a place to go. - if (items_used_for_this_command == 0) { - child_argv.ensure_capacity(initial_arguments.size()); - - initial_arguments.for_each_joined_argument(item, [&](const String& string) { - total_command_length += string.length(); - child_argv.append(strdup(string.characters())); - }); - - ++items_used_for_this_command; - } else { - if ((max_lines > 0 && items_used_for_this_command + 1 > max_lines) || total_command_length + item.length() + 1 >= max_bytes) { - // Note: This `move' does not actually move-construct a new Vector at the callsite, it only allows perfect-forwarding - // and does not invalidate `child_argv' in this scope. - // The same applies for the one below. - if (!run_command(move(child_argv), verbose, is_stdin, devnull_fd)) - return Stop; - items_used_for_this_command = 0; - total_command_length = 0; - return Unget; - } else { - child_argv.append(strndup(item.characters_without_null_termination(), item.length())); - total_command_length += item.length(); - ++items_used_for_this_command; - } - } - - return Continue; - }); - - if (!fail && !child_argv.is_empty()) - fail = !run_command(move(child_argv), verbose, is_stdin, devnull_fd); - - if (!is_stdin) - fclose(fp); - - return fail ? 1 : 0; -} - -bool read_items(FILE* fp, char entry_separator, Function callback) -{ - bool fail = false; - - for (;;) { - char* item = nullptr; - size_t buffer_size = 0; - - auto item_size = getdelim(&item, &buffer_size, entry_separator, fp); - - if (item_size < 0) { - // getdelim() will return -1 and set errno to 0 on EOF. - if (errno != 0) { - perror("getdelim"); - fail = true; - } - break; - } - - Decision decision; - do { - decision = callback(item); - if (decision == Stop) { - free(item); - return true; - } - } while (decision == Unget); - - free(item); - } - - return fail; -} - -bool run_command(Vector&& child_argv, bool verbose, bool is_stdin, int devnull_fd) -{ - child_argv.append(nullptr); - - if (verbose) { - StringBuilder builder; - builder.join(" ", child_argv); - fprintf(stderr, "xargs: %s\n", builder.to_string().characters()); - fflush(stderr); - } - - auto pid = fork(); - if (pid < 0) { - perror("fork"); - return false; - } - - if (pid == 0) { - if (is_stdin) - dup2(devnull_fd, STDIN_FILENO); - - execvp(child_argv[0], child_argv.data()); - exit(1); - } - - for (auto* ptr : child_argv) - free(ptr); - - child_argv.clear_with_capacity(); - - int wstatus = 0; - if (waitpid(pid, &wstatus, 0) < 0) { - perror("waitpid"); - return false; - } - - if (WIFEXITED(wstatus)) { - if (WEXITSTATUS(wstatus) != 0) - return false; - } else { - return false; - } - - return true; -} - -ParsedInitialArguments::ParsedInitialArguments(Vector& arguments, const StringView& placeholder) -{ - m_all_parts.ensure_capacity(arguments.size()); - bool some_argument_has_placeholder = false; - - for (auto argument : arguments) { - StringView arg { argument }; - - if (placeholder.is_empty()) { - m_all_parts.append({ arg }); - } else { - auto parts = arg.split_view(placeholder, true); - some_argument_has_placeholder = some_argument_has_placeholder || parts.size() > 1; - m_all_parts.append(move(parts)); - } - } - - // Append an implicit placeholder at the end if no argument has any placeholders. - if (!some_argument_has_placeholder) { - Vector parts; - parts.append(""); - parts.append(""); - m_all_parts.append(move(parts)); - } -} - -void ParsedInitialArguments::for_each_joined_argument(const StringView& separator, Function callback) const -{ - StringBuilder builder; - for (auto& parts : m_all_parts) { - builder.clear(); - builder.join(separator, parts); - callback(builder.to_string()); - } -} diff --git a/Userland/yes.cpp b/Userland/yes.cpp deleted file mode 100644 index 31b715ef8f..0000000000 --- a/Userland/yes.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2018-2020, Andreas Kling - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -int main(int argc, char** argv) -{ - if (pledge("stdio", nullptr) < 0) { - perror("pledge"); - return 1; - } - - const char* string = "yes"; - - Core::ArgsParser args_parser; - args_parser.add_positional_argument(string, "String to output (defaults to 'yes')", "string", Core::ArgsParser::Required::No); - args_parser.parse(argc, argv); - - for (;;) - puts(string); - return 0; -} -- cgit v1.2.3