BuildSystem: Cleanup userspace directory layout

userspace programs are now in userspace/programs
userspace tests are now in userspace/tests

This makes listing userspace projects much cleaner. Libraries were
already separated to their own directory, so other programs should also.
This commit is contained in:
2024-07-03 09:15:22 +03:00
parent 5dc441c4af
commit 8ddab05ed3
107 changed files with 78 additions and 67 deletions

View File

@@ -0,0 +1,45 @@
set(USERSPACE_PROGRAMS
cat
cat-mmap
chmod
cp
dd
dhcp-client
echo
getopt
http-server
id
image
init
loadfont
loadkeys
ls
meminfo
mkdir
nslookup
poweroff
resolver
rm
Shell
sleep
snake
stat
sudo
sync
tee
Terminal
touch
u8sum
whoami
WindowServer
yes
)
foreach(project ${USERSPACE_PROGRAMS})
add_subdirectory(${project})
add_dependencies(userspace ${project})
# This is to allow cmake to link when libc updates
target_link_options(${project} PRIVATE -nolibc)
# Default compile options
target_compile_options(${project} PRIVATE -g -O2)
endforeach()

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(Shell ${SOURCES})
banan_link_library(Shell ban)
banan_link_library(Shell libc)
install(TARGETS Shell OPTIONAL)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
set(SOURCES
main.cpp
Terminal.cpp
)
add_executable(Terminal ${SOURCES})
banan_include_headers(Terminal ban)
banan_link_library(Terminal libc)
banan_link_library(Terminal libfont)
banan_link_library(Terminal libgui)
banan_link_library(Terminal libinput)
install(TARGETS Terminal OPTIONAL)

View File

@@ -0,0 +1,380 @@
#include "Terminal.h"
#include <BAN/Debug.h>
#include <BAN/UTF8.h>
#include <ctype.h>
#include <stdlib.h>
#include <sys/select.h>
#include <unistd.h>
void Terminal::start_shell()
{
int shell_stdin[2];
if (pipe(shell_stdin) == -1)
{
dwarnln("pipe: {}", strerror(errno));
exit(1);
}
int shell_stdout[2];
if (pipe(shell_stdout) == -1)
{
dwarnln("pipe: {}", strerror(errno));
exit(1);
}
int shell_stderr[2];
if (pipe(shell_stderr) == -1)
{
dwarnln("pipe: {}", strerror(errno));
exit(1);
}
pid_t shell_pid = fork();
if (shell_pid == 0)
{
if (dup2(shell_stdin[0], STDIN_FILENO) == -1)
{
dwarnln("dup2: {}", strerror(errno));
exit(1);
}
close(shell_stdin[0]);
close(shell_stdin[1]);
if (dup2(shell_stdout[1], STDOUT_FILENO) == -1)
{
dwarnln("dup2: {}", strerror(errno));
exit(1);
}
close(shell_stdout[0]);
close(shell_stdout[1]);
if (dup2(shell_stderr[1], STDERR_FILENO) == -1)
{
dwarnln("dup2: {}", strerror(errno));
exit(1);
}
close(shell_stderr[0]);
close(shell_stderr[1]);
execl("/bin/Shell", "Shell", NULL);
exit(1);
}
if (shell_pid == -1)
{
dwarnln("fork: {}", strerror(errno));
exit(1);
}
close(shell_stdin[0]);
close(shell_stdout[1]);
close(shell_stderr[1]);
m_shell_info = {
.in = shell_stdin[1],
.out = shell_stdout[0],
.err = shell_stderr[0],
.pid = shell_pid
};
}
static volatile bool s_shell_exited = false;
void Terminal::run()
{
signal(SIGCHLD, [](int) { s_shell_exited = true; });
start_shell();
m_window = MUST(LibGUI::Window::create(600, 400, "Terminal"_sv));
m_window->fill(m_bg_color);
m_window->invalidate();
m_font = MUST(LibFont::Font::load("/usr/share/fonts/lat0-16.psfu"_sv));
m_window->set_key_event_callback([&](LibGUI::EventPacket::KeyEvent event) { on_key_event(event); });
const int max_fd = BAN::Math::max(BAN::Math::max(m_shell_info.out, m_shell_info.err), m_window->server_fd());
while (!s_shell_exited)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(m_shell_info.out, &fds);
FD_SET(m_shell_info.err, &fds);
FD_SET(m_window->server_fd(), &fds);
select(max_fd + 1, &fds, nullptr, nullptr, nullptr);
if (FD_ISSET(m_shell_info.out, &fds))
if (!read_shell(m_shell_info.out))
break;
if (FD_ISSET(m_shell_info.err, &fds))
if (!read_shell(m_shell_info.err))
break;
if (FD_ISSET(m_window->server_fd(), &fds))
m_window->poll_events();
}
}
bool Terminal::read_shell(int fd)
{
char buffer[128];
ssize_t nread = read(fd, buffer, sizeof(buffer) - 1);
if (nread < 0)
dwarnln("read: {}", strerror(errno));
if (nread <= 0)
return false;
for (ssize_t i = 0; i < nread; i++)
putchar(buffer[i]);
return true;
}
void Terminal::handle_sgr()
{
constexpr uint32_t colors_default[] {
0x555555,
0xFF5555,
0x55FF55,
0xFFFF55,
0x5555FF,
0xFF55FF,
0x55FFFF,
0xFFFFFF,
};
constexpr uint32_t colors_bright[] {
0xAAAAAA,
0xFFAAAA,
0xAAFFAA,
0xFFFFAA,
0xAAAAFF,
0xFFAAFF,
0xAAFFFF,
0xFFFFFF,
};
switch (m_csi_info.fields[0])
{
case -1: case 0:
m_fg_color = 0xFFFFFF;
m_bg_color = 0x000000;
break;
case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37:
m_fg_color = colors_default[m_csi_info.fields[0] - 30];
break;
case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47:
m_bg_color = colors_default[m_csi_info.fields[0] - 40];
break;
case 90: case 91: case 92: case 93: case 94: case 95: case 96: case 97:
m_fg_color = colors_bright[m_csi_info.fields[0] - 90];
break;
case 100: case 101: case 102: case 103: case 104: case 105: case 106: case 107:
m_bg_color = colors_bright[m_csi_info.fields[0] - 100];
break;
default:
dprintln("TODO: SGR {}", m_csi_info.fields[0]);
break;
}
}
void Terminal::handle_csi(char ch)
{
if (ch == ';')
{
m_csi_info.index++;
return;
}
if (ch == '?')
return;
if (isdigit(ch))
{
if (m_csi_info.index <= 1)
{
auto& field = m_csi_info.fields[m_csi_info.index];
field = (BAN::Math::max(field, 0) * 10) + (ch - '0');
}
return;
}
switch (ch)
{
case 'C':
if (m_csi_info.fields[0] == -1)
m_csi_info.fields[0] = 1;
m_cursor.x = BAN::Math::clamp<int32_t>(m_cursor.x + m_csi_info.fields[0], 0, cols() - 1);
break;
case 'D':
if (m_csi_info.fields[0] == -1)
m_csi_info.fields[0] = 1;
m_cursor.x = BAN::Math::clamp<int32_t>((int32_t)m_cursor.x - m_csi_info.fields[0], 0, cols() - 1);
break;
case 'G':
m_cursor.x = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, cols()) - 1;
break;
case 'H':
m_cursor.y = BAN::Math::clamp<int32_t>(m_csi_info.fields[0], 1, rows()) - 1;
m_cursor.x = BAN::Math::clamp<int32_t>(m_csi_info.fields[1], 1, cols()) - 1;
break;
case 'J':
{
uint32_t rects[2][4] { { (uint32_t)-1 }, { (uint32_t)-1 } };
if (m_csi_info.fields[0] == -1 || m_csi_info.fields[0] == 0)
{
rects[0][0] = m_cursor.x * m_font.width();
rects[0][1] = m_cursor.y * m_font.height();
rects[0][2] = m_window->width() - rects[0][0];
rects[0][3] = m_font.height();
rects[1][0] = 0;
rects[1][1] = (m_cursor.y + 1) * m_font.height();
rects[1][2] = m_window->width();
rects[1][3] = m_window->height() - rects[1][1];
}
else if (m_csi_info.fields[0] == 1)
{
rects[0][0] = 0;
rects[0][1] = m_cursor.y * m_font.height();
rects[0][2] = m_cursor.x * m_font.width();
rects[0][3] = m_font.height();
rects[1][0] = 0;
rects[1][1] = 0;
rects[1][2] = m_window->width();
rects[1][3] = m_cursor.y * m_font.height();
}
else
{
rects[0][0] = 0;
rects[0][1] = 0;
rects[0][2] = m_window->width();
rects[0][3] = m_window->height();
}
for (int i = 0; i < 2; i++)
{
if (rects[i][0] == (uint32_t)-1)
continue;
m_window->fill_rect(rects[i][0], rects[i][1], rects[i][2], rects[i][3], m_bg_color);
m_window->invalidate(rects[i][0], rects[i][1], rects[i][2], rects[i][3]);
}
break;
}
case 'K':
{
m_csi_info.fields[0] = BAN::Math::max(m_csi_info.fields[0], 0);
uint32_t rect[4];
rect[0] = (m_csi_info.fields[0] == 0) ? m_cursor.x * m_font.width() : 0;
rect[1] = m_cursor.y * m_font.height();
rect[2] = (m_csi_info.fields[0] == 1) ? m_cursor.x * m_font.width() : m_window->width() - rect[0];
rect[3] = m_font.height();
m_window->fill_rect(rect[0], rect[1], rect[2], rect[3], m_bg_color);
m_window->invalidate(rect[0], rect[1], rect[2], rect[3]);
break;
}
case 'm':
handle_sgr();
break;
case 's':
m_saved_cursor = m_cursor;
break;
case 'u':
m_cursor = m_saved_cursor;
break;
default:
dprintln("TODO: CSI {}", ch);
break;
}
m_state = State::Normal;
}
void Terminal::putchar(uint32_t codepoint)
{
if (m_state == State::ESC)
{
if (codepoint != '[')
{
dprintln("unknown escape character 0x{H}", codepoint);
m_state = State::Normal;
return;
}
m_state = State::CSI;
m_csi_info.index = 0;
m_csi_info.fields[0] = -1;
m_csi_info.fields[1] = -1;
return;
}
if (m_state == State::CSI)
{
if (codepoint < 0x20 || codepoint > 0xFE)
{
dprintln("invalid CSI 0x{H}", codepoint);
m_state = State::Normal;
return;
}
handle_csi(codepoint);
return;
}
switch (codepoint)
{
case '\e':
m_state = State::ESC;
break;
case '\n':
m_cursor.x = 0;
m_cursor.y++;
break;
case '\r':
m_cursor.x = 0;
break;
case '\b':
if (m_cursor.x > 0)
m_cursor.x--;
break;
default:
{
const uint32_t cell_w = m_font.width();
const uint32_t cell_h = m_font.height();
const uint32_t cell_x = m_cursor.x * cell_w;
const uint32_t cell_y = m_cursor.y * cell_h;
m_window->fill_rect(cell_x, cell_y, cell_w, cell_h, m_bg_color);
m_window->draw_character(codepoint, m_font, cell_x, cell_y, m_fg_color);
m_window->invalidate(cell_x, cell_y, cell_w, cell_h);
m_cursor.x++;
break;
}
}
if (m_cursor.x >= cols())
{
m_cursor.x = 0;
m_cursor.y++;
}
if (m_cursor.y >= rows())
{
uint32_t scroll = m_cursor.y - rows() + 1;
m_cursor.y -= scroll;
m_window->shift_vertical(-scroll * (int32_t)m_font.height());
m_window->fill_rect(0, m_window->height() - scroll * m_font.height(), m_window->width(), scroll * m_font.height(), m_bg_color);
m_window->invalidate();
}
}
void Terminal::on_key_event(LibGUI::EventPacket::KeyEvent event)
{
if (event.released())
return;
if (const char* text = LibInput::key_to_utf8_ansi(event.key, event.modifier))
write(m_shell_info.in, text, strlen(text));
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include <LibFont/Font.h>
#include <LibGUI/Window.h>
class Terminal
{
public:
void run();
uint32_t cols() const { return m_window->width() / m_font.width(); }
uint32_t rows() const { return m_window->height() / m_font.height(); }
private:
void handle_csi(char ch);
void handle_sgr();
void putchar(uint32_t codepoint);
bool read_shell(int fd);
void on_key_event(LibGUI::EventPacket::KeyEvent);
void start_shell();
private:
struct Cursor
{
uint32_t x;
uint32_t y;
};
struct ShellInfo
{
int in;
int out;
int err;
pid_t pid;
};
enum class State
{
Normal,
ESC,
CSI,
};
struct CSIInfo
{
int32_t fields[2];
size_t index;
};
private:
BAN::UniqPtr<LibGUI::Window> m_window;
LibFont::Font m_font;
Cursor m_cursor { 0, 0 };
ShellInfo m_shell_info;
State m_state { State::Normal };
CSIInfo m_csi_info;
Cursor m_saved_cursor { 0, 0 };
uint32_t m_fg_color { 0xFFFFFF };
uint32_t m_bg_color { 0x000000 };
};

View File

@@ -0,0 +1,7 @@
#include "Terminal.h"
int main()
{
Terminal terminal;
terminal.run();
}

View File

@@ -0,0 +1,16 @@
set(SOURCES
main.cpp
Framebuffer.cpp
Window.cpp
WindowServer.cpp
)
add_executable(WindowServer ${SOURCES})
banan_include_headers(WindowServer ban)
banan_include_headers(WindowServer libgui)
banan_link_library(WindowServer libc)
banan_link_library(WindowServer libfont)
banan_link_library(WindowServer libimage)
banan_link_library(WindowServer libinput)
install(TARGETS WindowServer OPTIONAL)

View File

@@ -0,0 +1,35 @@
/* GIMP header image file format (RGB) */
#include <stdint.h>
static int32_t s_cursor_width = 17;
static int32_t s_cursor_height = 26;
static const char* s_cursor_data =
"!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$`!!!!!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$`!!!!````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$``Q$`!!!!````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$``Q$``Q$`!!!!````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$``Q$``Q$``Q$`!!!!````````````````!!!!`Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````!!!!`Q$``Q$``Q$`"
"`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````!!!!`Q$`"
"`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````````"
"!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````````"
"````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````````"
"````````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$`!!!!````````````````"
"````````````````````````!!!!`Q$``Q$``Q$``Q$``Q$`!!!!````````````"
"````````````````````````````````!!!!`Q$``Q$``Q$``Q$`!!!!````````"
"````````````````````````````````````````!!!!`Q$``Q$``Q$`!!!!````"
"````````````````````````````````````````````````!!!!`Q$``Q$`!!!!"
"````````````````````````````````````````````````````````!!!!`Q$`"
"!!!!````````````````````````````````!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
"!!!!!!!!````````````````````````````````!!!!`Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$`!!!!````````````````!!!!````````````````!!!!`Q$``Q$``Q$`"
"`Q$``Q$``Q$`!!!!````````````!!!!`Q$`!!!!````````````!!!!`Q$``Q$`"
"`Q$``Q$``Q$``Q$`!!!!````````!!!!`Q$``Q$`!!!!````````````````!!!!"
"`Q$``Q$``Q$``Q$``Q$`!!!!````!!!!`Q$``Q$``Q$``Q$`!!!!````````````"
"!!!!`Q$``Q$``Q$``Q$``Q$`!!!!!!!!`Q$``Q$``Q$``Q$``Q$`!!!!````````"
"````````!!!!`Q$``Q$``Q$``Q$`!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$`!!!!"
"````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"!!!!````````````!!!!`Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$``Q$`"
"`Q$``Q$`!!!!!!!!!!!!`Q$``Q$``Q$``Q$``Q$`";

View File

@@ -0,0 +1,45 @@
#include "Framebuffer.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/framebuffer.h>
#include <sys/mman.h>
Framebuffer open_framebuffer()
{
int framebuffer_fd = open("/dev/fb0", O_RDWR);
if (framebuffer_fd == -1)
{
perror("open");
exit(1);
}
framebuffer_info_t framebuffer_info;
if (pread(framebuffer_fd, &framebuffer_info, sizeof(framebuffer_info), -1) == -1)
{
perror("pread");
exit(1);
}
const size_t framebuffer_bytes = framebuffer_info.width * framebuffer_info.height * (BANAN_FB_BPP / 8);
uint32_t* framebuffer_mmap = (uint32_t*)mmap(NULL, framebuffer_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_fd, 0);
if (framebuffer_mmap == MAP_FAILED)
{
perror("mmap");
exit(1);
}
memset(framebuffer_mmap, 0, framebuffer_bytes);
msync(framebuffer_mmap, framebuffer_bytes, MS_SYNC);
Framebuffer framebuffer;
framebuffer.fd = framebuffer_fd;
framebuffer.mmap = framebuffer_mmap;
framebuffer.width = framebuffer_info.width;
framebuffer.height = framebuffer_info.height;
framebuffer.bpp = BANAN_FB_BPP;
return framebuffer;
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include "Utils.h"
struct Framebuffer
{
int fd;
uint32_t* mmap;
int32_t width;
int32_t height;
uint8_t bpp;
Rectangle area() const { return { 0, 0, width, height }; }
};
Framebuffer open_framebuffer();

View File

@@ -0,0 +1,80 @@
#pragma once
#include <BAN/Math.h>
#include <BAN/Optional.h>
#include <stdint.h>
struct Position
{
int32_t x;
int32_t y;
};
struct Rectangle
{
int32_t x;
int32_t y;
int32_t width;
int32_t height;
bool contains(Position position) const
{
if (position.x < x || position.x >= x + width)
return false;
if (position.y < y || position.y >= y + height)
return false;
return true;
}
BAN::Optional<Rectangle> get_overlap(Rectangle other) const
{
const auto min_x = BAN::Math::max(x, other.x);
const auto min_y = BAN::Math::max(y, other.y);
const auto max_x = BAN::Math::min(x + width, other.x + other.width);
const auto max_y = BAN::Math::min(y + height, other.y + other.height);
if (min_x >= max_x || min_y >= max_y)
return {};
return Rectangle {
.x = min_x,
.y = min_y,
.width = max_x - min_x,
.height = max_y - min_y,
};
}
Rectangle get_bounding_box(Rectangle other) const
{
const auto min_x = BAN::Math::min(x, other.x);
const auto min_y = BAN::Math::min(y, other.y);
const auto max_x = BAN::Math::max(x + width, other.x + other.width);
const auto max_y = BAN::Math::max(y + height, other.y + other.height);
return Rectangle {
.x = min_x,
.y = min_y,
.width = max_x - min_x,
.height = max_y - min_y,
};
}
bool operator==(const Rectangle& other) const
{
return x == other.x && y == other.y && width == other.width && height == other.height;
}
};
struct Circle
{
int32_t x;
int32_t y;
int32_t radius;
bool contains(Position position) const
{
int32_t dx = position.x - x;
int32_t dy = position.y - y;
return dx * dx + dy * dy <= radius * radius;
}
};

View File

@@ -0,0 +1,72 @@
#include "Window.h"
#include <BAN/Debug.h>
#include <LibGUI/Window.h>
#include <sys/banan-os.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <unistd.h>
Window::Window(int fd, Rectangle area, long smo_key, BAN::StringView title, const LibFont::Font& font)
: m_client_fd(fd)
, m_client_area(area)
, m_smo_key(smo_key)
{
MUST(m_title.append(title));
prepare_title_bar(font);
m_fb_addr = static_cast<uint32_t*>(smo_map(smo_key));
ASSERT(m_fb_addr);
memset(m_fb_addr, 0, client_width() * client_height() * 4);
}
Window::~Window()
{
munmap(m_fb_addr, client_width() * client_height() * 4);
smo_delete(m_smo_key);
LibGUI::EventPacket event;
event.type = LibGUI::EventPacket::Type::DestroyWindow;
send(m_client_fd, &event, sizeof(event), 0);
close(m_client_fd);
}
void Window::prepare_title_bar(const LibFont::Font& font)
{
const size_t title_bar_bytes = title_bar_width() * title_bar_height() * 4;
uint32_t* title_bar_data = new uint32_t[title_bar_bytes];
ASSERT(title_bar_data);
for (size_t i = 0; i < title_bar_bytes; i++)
title_bar_data[i] = 0xFFFFFF;
const auto text_area = title_text_area();
for (size_t i = 0; i < m_title.size() && (i + 1) * font.width() < static_cast<uint32_t>(text_area.width); i++)
{
const auto* glyph = font.glyph(m_title[i]);
if (glyph == nullptr)
continue;
const int32_t y_off = (font.height() < (uint32_t)title_bar_height()) ? (title_bar_height() - font.height()) / 2 : 0;
const int32_t x_off = y_off + i * font.width();
for (int32_t y = 0; (uint32_t)y < font.height(); y++)
{
if (y + y_off >= title_bar_height())
break;
for (int32_t x = 0; (uint32_t)x < font.width(); x++)
{
if (x + x_off >= text_area.width)
break;
const uint8_t bitmask = 1 << (font.width() - x - 1);
if (glyph[y * font.pitch()] & bitmask)
title_bar_data[(y_off + y) * title_bar_width() + (x_off + x)] = 0x000000;
}
}
}
if (m_title_bar_data)
delete[] m_title_bar_data;
m_title_bar_data = title_bar_data;
}

View File

@@ -0,0 +1,76 @@
#pragma once
#include "Utils.h"
#include <BAN/RefPtr.h>
#include <BAN/String.h>
#include <LibFont/Font.h>
class Window : public BAN::RefCounted<Window>
{
public:
Window(int fd, Rectangle area, long smo_key, BAN::StringView title, const LibFont::Font& font);
~Window();
void set_position(Position position)
{
m_client_area.x = position.x;
m_client_area.y = position.y;
}
int client_fd() const { return m_client_fd; }
int32_t client_x() const { return m_client_area.x; }
int32_t client_y() const { return m_client_area.y; }
int32_t client_width() const { return m_client_area.width; }
int32_t client_height() const { return m_client_area.height; }
Rectangle client_size() const { return { 0, 0, client_width(), client_height() }; }
Rectangle client_area() const { return m_client_area; }
int32_t title_bar_x() const { return client_x(); }
int32_t title_bar_y() const { return client_y() - title_bar_height(); }
int32_t title_bar_width() const { return client_width(); }
int32_t title_bar_height() const { return m_title_bar_height; }
Rectangle title_bar_size() const { return { 0, 0, title_bar_width(), title_bar_height() }; }
Rectangle title_bar_area() const { return { title_bar_x(), title_bar_y(), title_bar_width(), title_bar_height() }; }
int32_t full_x() const { return title_bar_x(); }
int32_t full_y() const { return title_bar_y(); }
int32_t full_width() const { return client_width(); }
int32_t full_height() const { return client_height() + title_bar_height(); }
Rectangle full_size() const { return { 0, 0, full_width(), full_height() }; }
Rectangle full_area() const { return { full_x(), full_y(), full_width(), full_height() }; }
const uint32_t* framebuffer() const { return m_fb_addr; }
uint32_t title_bar_pixel(int32_t abs_x, int32_t abs_y, Position cursor) const
{
ASSERT(title_bar_area().contains({ abs_x, abs_y }));
if (auto close_button = close_button_area(); close_button.contains({ abs_x, abs_y }))
return close_button.contains(cursor) ? 0xFF0000 : 0xD00000;
int32_t rel_x = abs_x - title_bar_x();
int32_t rel_y = abs_y - title_bar_y();
return m_title_bar_data[rel_y * title_bar_width() + rel_x];
}
Circle close_button_area() const { return { title_bar_x() + title_bar_width() - title_bar_height() / 2, title_bar_y() + title_bar_height() / 2, title_bar_height() * 3 / 8 }; }
Rectangle title_text_area() const { return { title_bar_x(), title_bar_y(), title_bar_width() - title_bar_height(), title_bar_height() }; }
private:
void prepare_title_bar(const LibFont::Font& font);
private:
static constexpr int32_t m_title_bar_height { 20 };
const int m_client_fd { -1 };
Rectangle m_client_area { 0, 0, 0, 0 };
long m_smo_key { 0 };
uint32_t* m_fb_addr { nullptr };
uint32_t* m_title_bar_data { nullptr };
BAN::String m_title;
friend class BAN::RefPtr<Window>;
};

View File

@@ -0,0 +1,475 @@
#include "Cursor.h"
#include "WindowServer.h"
#include <BAN/Debug.h>
#include <LibGUI/Window.h>
#include <LibInput/KeyboardLayout.h>
#include <stdlib.h>
#include <sys/banan-os.h>
#include <sys/mman.h>
#include <sys/socket.h>
WindowServer::WindowServer(Framebuffer& framebuffer)
: m_framebuffer(framebuffer)
, m_cursor({ framebuffer.width / 2, framebuffer.height / 2 })
, m_font(MUST(LibFont::Font::load("/usr/share/fonts/lat0-16.psfu"_sv)))
{
MUST(m_pages_to_sync_bitmap.resize(BAN::Math::div_round_up<size_t>(m_framebuffer.width * m_framebuffer.height * sizeof(uint32_t), 4096 * 8), 0));
invalidate(m_framebuffer.area());
}
BAN::ErrorOr<void> WindowServer::set_background_image(BAN::UniqPtr<LibImage::Image> image)
{
if (image->width() != (uint64_t)m_framebuffer.width || image->height() != (uint64_t)m_framebuffer.height)
image = TRY(image->resize(m_framebuffer.width, m_framebuffer.height));
m_background_image = BAN::move(image);
invalidate(m_framebuffer.area());
return {};
}
void WindowServer::on_window_packet(int fd, LibGUI::WindowPacket packet)
{
switch (packet.type)
{
case LibGUI::WindowPacketType::CreateWindow:
{
// FIXME: This should be probably allowed
for (auto& window : m_client_windows)
{
if (window->client_fd() == fd)
{
dwarnln("client {} tried to create window while already owning a window", fd);
return;
}
}
const size_t window_fb_bytes = packet.create.width * packet.create.height * 4;
long smo_key = smo_create(window_fb_bytes, PROT_READ | PROT_WRITE);
if (smo_key == -1)
{
dwarnln("smo_create: {}", strerror(errno));
break;
}
Rectangle window_area {
static_cast<int32_t>((m_framebuffer.width - packet.create.width) / 2),
static_cast<int32_t>((m_framebuffer.height - packet.create.height) / 2),
static_cast<int32_t>(packet.create.width),
static_cast<int32_t>(packet.create.height)
};
packet.create.title[sizeof(packet.create.title) - 1] = '\0';
// Window::Window(int fd, Rectangle area, long smo_key, BAN::StringView title, const LibFont::Font& font)
auto window = MUST(BAN::RefPtr<Window>::create(
fd,
window_area,
smo_key,
packet.create.title,
m_font
));
MUST(m_client_windows.push_back(window));
set_focused_window(window);
LibGUI::WindowCreateResponse response;
response.framebuffer_smo_key = smo_key;
if (send(window->client_fd(), &response, sizeof(response), 0) != sizeof(response))
{
dwarnln("send: {}", strerror(errno));
break;
}
break;
}
case LibGUI::WindowPacketType::Invalidate:
{
if (packet.invalidate.width == 0 || packet.invalidate.height == 0)
break;
BAN::RefPtr<Window> target_window;
for (auto& window : m_client_windows)
{
if (window->client_fd() == fd)
{
target_window = window;
break;
}
}
if (!target_window)
{
dwarnln("client {} tried to invalidate window while not owning a window", fd);
break;
}
const int32_t br_x = packet.invalidate.x + packet.invalidate.width - 1;
const int32_t br_y = packet.invalidate.y + packet.invalidate.height - 1;
if (!target_window->client_size().contains({ br_x, br_y }))
{
dwarnln("Invalid Invalidate packet parameters");
break;
}
invalidate({
target_window->client_x() + static_cast<int32_t>(packet.invalidate.x),
target_window->client_y() + static_cast<int32_t>(packet.invalidate.y),
static_cast<int32_t>(packet.invalidate.width),
static_cast<int32_t>(packet.invalidate.height),
});
break;
}
default:
ASSERT_NOT_REACHED();
}
}
void WindowServer::on_key_event(LibInput::KeyEvent event)
{
// Mod key is not passed to clients
if (event.key == LibInput::Key::Super)
{
m_is_mod_key_held = event.pressed();
return;
}
// Quick hack to stop the window server
if (event.pressed() && event.key == LibInput::Key::Escape)
{
m_is_stopped = true;
return;
}
// Kill window with mod+Q
if (m_is_mod_key_held && event.pressed() && event.key == LibInput::Key::Q)
{
if (m_focused_window)
remove_client_fd(m_focused_window->client_fd());
return;
}
if (m_focused_window)
{
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::KeyEvent;
packet.key_event = event;
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
}
void WindowServer::on_mouse_button(LibInput::MouseButtonEvent event)
{
BAN::RefPtr<Window> target_window;
for (size_t i = m_client_windows.size(); i > 0; i--)
{
if (m_client_windows[i - 1]->full_area().contains(m_cursor))
{
target_window = m_client_windows[i - 1];
break;
}
}
// Ignore mouse button events which are not on top of a window
if (!target_window)
return;
set_focused_window(target_window);
// Handle window moving when mod key is held or mouse press on title bar
const bool can_start_move = m_is_mod_key_held || target_window->title_text_area().contains(m_cursor);
if (event.pressed && event.button == LibInput::MouseButton::Left && !m_is_moving_window && can_start_move)
m_is_moving_window = true;
else if (m_is_moving_window && !event.pressed)
m_is_moving_window = false;
else if (!event.pressed && event.button == LibInput::MouseButton::Left && target_window->close_button_area().contains(m_cursor))
{
// NOTE: we always have target window if code reaches here
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::CloseWindow;
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
else if (target_window->client_area().contains(m_cursor))
{
// NOTE: we always have target window if code reaches here
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::MouseButtonEvent;
packet.mouse_button_event.button = event.button;
packet.mouse_button_event.pressed = event.pressed;
packet.mouse_button_event.x = m_cursor.x - m_focused_window->client_x();
packet.mouse_button_event.y = m_cursor.y - m_focused_window->client_y();
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
}
void WindowServer::on_mouse_move(LibInput::MouseMoveEvent event)
{
const int32_t new_x = BAN::Math::clamp(m_cursor.x + event.rel_x, 0, m_framebuffer.width);
const int32_t new_y = BAN::Math::clamp(m_cursor.y - event.rel_y, 0, m_framebuffer.height);
event.rel_x = new_x - m_cursor.x;
event.rel_y = new_y - m_cursor.y;
if (event.rel_x == 0 && event.rel_y == 0)
return;
auto old_cursor = cursor_area();
m_cursor.x = new_x;
m_cursor.y = new_y;
auto new_cursor = cursor_area();
invalidate(old_cursor);
invalidate(new_cursor);
// TODO: Really no need to loop over every window
for (auto& window : m_client_windows)
{
auto title_bar = window->title_bar_area();
if (title_bar.get_overlap(old_cursor).has_value() || title_bar.get_overlap(new_cursor).has_value())
invalidate(title_bar);
}
if (m_is_moving_window)
{
auto old_window = m_focused_window->full_area();
m_focused_window->set_position({
m_focused_window->client_x() + event.rel_x,
m_focused_window->client_y() + event.rel_y,
});
auto new_window = m_focused_window->full_area();
invalidate(old_window);
invalidate(new_window);
return;
}
if (m_focused_window)
{
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::MouseMoveEvent;
packet.mouse_move_event.x = m_cursor.x - m_focused_window->client_x();
packet.mouse_move_event.y = m_cursor.y - m_focused_window->client_y();
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
}
void WindowServer::on_mouse_scroll(LibInput::MouseScrollEvent event)
{
if (m_focused_window)
{
LibGUI::EventPacket packet;
packet.type = LibGUI::EventPacket::Type::MouseScrollEvent;
packet.mouse_scroll_event = event;
send(m_focused_window->client_fd(), &packet, sizeof(packet), 0);
}
}
void WindowServer::set_focused_window(BAN::RefPtr<Window> window)
{
if (m_focused_window == window)
return;
for (size_t i = m_client_windows.size(); i > 0; i--)
{
if (m_client_windows[i - 1] == window)
{
m_focused_window = window;
m_client_windows.remove(i - 1);
MUST(m_client_windows.push_back(window));
invalidate(window->full_area());
break;
}
}
}
void WindowServer::invalidate(Rectangle area)
{
auto fb_overlap = area.get_overlap(m_framebuffer.area());
if (!fb_overlap.has_value())
return;
area = fb_overlap.release_value();
if (m_background_image)
{
ASSERT(m_background_image->width() == (uint64_t)m_framebuffer.width);
ASSERT(m_background_image->height() == (uint64_t)m_framebuffer.height);
for (int32_t y = area.y; y < area.y + area.height; y++)
for (int32_t x = area.x; x < area.x + area.width; x++)
m_framebuffer.mmap[y * m_framebuffer.width + x] = m_background_image->get_color(x, y).as_rgba();
}
else
{
for (int32_t y = area.y; y < area.y + area.height; y++)
memset(&m_framebuffer.mmap[y * m_framebuffer.width + area.x], 0x10, area.width * 4);
}
for (auto& pwindow : m_client_windows)
{
auto& window = *pwindow;
// window title bar
if (auto overlap = window.title_bar_area().get_overlap(area); overlap.has_value())
{
for (int32_t y_off = 0; y_off < overlap->height; y_off++)
{
for (int32_t x_off = 0; x_off < overlap->width; x_off++)
{
uint32_t pixel = window.title_bar_pixel(
overlap->x + x_off,
overlap->y + y_off,
m_cursor
);
m_framebuffer.mmap[(overlap->y + y_off) * m_framebuffer.width + overlap->x + x_off] = pixel;
}
}
}
// window client area
if (auto overlap = window.client_area().get_overlap(area); overlap.has_value())
{
const int32_t src_x = overlap->x - window.client_x();
const int32_t src_y = overlap->y - window.client_y();
for (int32_t y_off = 0; y_off < overlap->height; y_off++)
{
memcpy(
&m_framebuffer.mmap[(overlap->y + y_off) * m_framebuffer.width + overlap->x],
&window.framebuffer()[(src_y + y_off) * window.client_width() + src_x],
overlap->width * 4
);
}
}
}
auto cursor = cursor_area();
if (auto overlap = cursor.get_overlap(area); overlap.has_value())
{
for (int32_t y_off = 0; y_off < overlap->height; y_off++)
{
for (int32_t x_off = 0; x_off < overlap->width; x_off++)
{
const int32_t rel_x = overlap->x - m_cursor.x + x_off;
const int32_t rel_y = overlap->y - m_cursor.y + y_off;
const uint32_t offset = (rel_y * s_cursor_width + rel_x) * 4;
uint32_t r = (((s_cursor_data[offset + 0] - 33) << 2) | ((s_cursor_data[offset + 1] - 33) >> 4));
uint32_t g = ((((s_cursor_data[offset + 1] - 33) & 0xF) << 4) | ((s_cursor_data[offset + 2] - 33) >> 2));
uint32_t b = ((((s_cursor_data[offset + 2] - 33) & 0x3) << 6) | ((s_cursor_data[offset + 3] - 33)));
uint32_t color = (r << 16) | (g << 8) | b;
if (color != 0xFF00FF)
m_framebuffer.mmap[(overlap->y + y_off) * m_framebuffer.width + (overlap->x + x_off)] = color;
}
}
}
const uintptr_t mmap_start = reinterpret_cast<uintptr_t>(m_framebuffer.mmap) + area.y * m_framebuffer.width * 4;
const uintptr_t mmap_end = mmap_start + (area.height + 1) * m_framebuffer.width * 4;
uintptr_t mmap_addr = mmap_start & ~(uintptr_t)0xFFF;
while (mmap_addr < mmap_end)
{
size_t index = (mmap_addr - reinterpret_cast<uintptr_t>(m_framebuffer.mmap)) / 4096;
size_t byte = index / 8;
size_t bit = index % 8;
m_pages_to_sync_bitmap[byte] |= 1 << bit;
mmap_addr += 4096;
}
}
void WindowServer::sync()
{
size_t synced_pages = 0;
for (size_t i = 0; i < m_pages_to_sync_bitmap.size() * 8; i++)
{
size_t byte = i / 8;
size_t bit = i % 8;
if (!(m_pages_to_sync_bitmap[byte] & (1 << bit)))
continue;
size_t len = 1;
while (i + len < m_pages_to_sync_bitmap.size() * 8)
{
size_t byte = (i + len) / 8;
size_t bit = (i + len) % 8;
if (!(m_pages_to_sync_bitmap[byte] & (1 << bit)))
break;
len++;
}
msync(
reinterpret_cast<uint8_t*>(m_framebuffer.mmap) + i * 4096,
len * 4096,
MS_SYNC
);
synced_pages += len;
i += len;
}
memset(m_pages_to_sync_bitmap.data(), 0, m_pages_to_sync_bitmap.size());
}
Rectangle WindowServer::cursor_area() const
{
return { m_cursor.x, m_cursor.y, s_cursor_width, s_cursor_height };
}
void WindowServer::add_client_fd(int fd)
{
MUST(m_client_fds.push_back(fd));
}
void WindowServer::remove_client_fd(int fd)
{
for (size_t i = 0; i < m_client_fds.size(); i++)
{
if (m_client_fds[i] == fd)
{
m_client_fds.remove(i);
break;
}
}
for (size_t i = 0; i < m_client_windows.size(); i++)
{
auto window = m_client_windows[i];
if (window->client_fd() == fd)
{
auto window_area = window->full_area();
m_client_windows.remove(i);
invalidate(window_area);
if (window == m_focused_window)
{
m_focused_window = nullptr;
if (!m_client_windows.empty())
set_focused_window(m_client_windows.back());
}
break;
}
}
m_deleted_window = true;
}
int WindowServer::get_client_fds(fd_set& fds) const
{
int max_fd = 0;
for (int fd : m_client_fds)
{
FD_SET(fd, &fds);
max_fd = BAN::Math::max(max_fd, fd);
}
return max_fd;
}
void WindowServer::for_each_client_fd(const BAN::Function<BAN::Iteration(int)>& callback)
{
m_deleted_window = false;
for (int fd : m_client_fds)
{
if (m_deleted_window)
break;
callback(fd);
}
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include "Framebuffer.h"
#include "Window.h"
#include <BAN/Function.h>
#include <BAN/Iteration.h>
#include <BAN/Vector.h>
#include <BAN/HashMap.h>
#include <LibFont/Font.h>
#include <LibGUI/Window.h>
#include <LibImage/Image.h>
#include <LibInput/KeyEvent.h>
#include <LibInput/MouseEvent.h>
#include <sys/select.h>
class WindowServer
{
public:
WindowServer(Framebuffer& framebuffer);
BAN::ErrorOr<void> set_background_image(BAN::UniqPtr<LibImage::Image>);
void on_window_packet(int fd, LibGUI::WindowPacket);
void on_key_event(LibInput::KeyEvent event);
void on_mouse_button(LibInput::MouseButtonEvent event);
void on_mouse_move(LibInput::MouseMoveEvent event);
void on_mouse_scroll(LibInput::MouseScrollEvent event);
void set_focused_window(BAN::RefPtr<Window> window);
void invalidate(Rectangle area);
void sync();
Rectangle cursor_area() const;
void add_client_fd(int fd);
void remove_client_fd(int fd);
int get_client_fds(fd_set& fds) const;
void for_each_client_fd(const BAN::Function<BAN::Iteration(int)>& callback);
bool is_stopped() const { return m_is_stopped; }
private:
Framebuffer& m_framebuffer;
BAN::Vector<BAN::RefPtr<Window>> m_client_windows;
BAN::Vector<int> m_client_fds;
BAN::Vector<uint8_t> m_pages_to_sync_bitmap;
BAN::UniqPtr<LibImage::Image> m_background_image;
bool m_is_mod_key_held { false };
bool m_is_moving_window { false };
BAN::RefPtr<Window> m_focused_window;
Position m_cursor;
bool m_deleted_window { false };
bool m_is_stopped { false };
LibFont::Font m_font;
};

View File

@@ -0,0 +1,299 @@
#include "WindowServer.h"
#include <BAN/Debug.h>
#include <BAN/ScopeGuard.h>
#include <LibGUI/Window.h>
#include <LibInput/KeyboardLayout.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/banan-os.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
struct Config
{
BAN::UniqPtr<LibImage::Image> background_image;
};
BAN::Optional<BAN::String> file_read_line(FILE* file)
{
BAN::String line;
char buffer[128];
while (fgets(buffer, sizeof(buffer), file))
{
MUST(line.append(buffer));
if (line.back() == '\n')
{
line.pop_back();
return BAN::move(line);
}
}
if (line.empty())
return {};
return BAN::move(line);
}
Config parse_config()
{
Config config;
auto home_env = getenv("HOME");
if (!home_env)
{
dprintln("HOME environment variable not set");
return config;
}
auto config_path = MUST(BAN::String::formatted("{}/.config/WindowServer.conf", home_env));
FILE* fconfig = fopen(config_path.data(), "r");
if (!fconfig)
{
dprintln("Could not open '{}'", config_path);
return config;
}
BAN::ScopeGuard _([fconfig] { fclose(fconfig); });
while (true)
{
auto line = file_read_line(fconfig);
if (!line.has_value())
break;
if (line->empty())
continue;
auto parts = MUST(line->sv().split('='));
if (parts.size() != 2)
{
dwarnln("Invalid config line: {}", line.value());
break;
}
auto variable = parts[0];
auto value = parts[1];
if (variable == "bg"_sv)
{
auto image = LibImage::Image::load_from_file(value);
if (image.is_error())
dwarnln("Could not load image: {}", image.error());
else
config.background_image = image.release_value();
}
else
{
dwarnln("Unknown config variable: {}", variable);
break;
}
}
return config;
}
int open_server_fd()
{
struct stat st;
if (stat(LibGUI::s_window_server_socket.data(), &st) != -1)
unlink(LibGUI::s_window_server_socket.data());
int server_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (server_fd == -1)
{
perror("socket");
exit(1);
}
sockaddr_un server_addr;
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, LibGUI::s_window_server_socket.data());
if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == -1)
{
perror("bind");
exit(1);
}
if (chmod("/tmp/resolver.sock", 0777) == -1)
{
perror("chmod");
exit(1);
}
if (listen(server_fd, SOMAXCONN) == -1)
{
perror("listen");
exit(1);
}
return server_fd;
}
int main()
{
srand(time(nullptr));
int server_fd = open_server_fd();
auto framebuffer = open_framebuffer();
if (framebuffer.bpp != 32)
{
dwarnln("Window server requires 32 bpp");
return 1;
}
if (tty_ctrl(STDIN_FILENO, TTY_CMD_UNSET, TTY_FLAG_ENABLE_INPUT) == -1)
{
perror("tty_ctrl");
exit(1);
}
atexit([]() { tty_ctrl(STDIN_FILENO, TTY_CMD_SET, TTY_FLAG_ENABLE_INPUT); });
MUST(LibInput::KeyboardLayout::initialize());
MUST(LibInput::KeyboardLayout::get().load_from_file("/usr/share/keymaps/us.keymap"_sv));
int keyboard_fd = open("/dev/input0", O_RDONLY);
if (keyboard_fd == -1)
perror("open");
int mouse_fd = open("/dev/input1", O_RDONLY);
if (mouse_fd == -1)
perror("open");
dprintln("Window server started");
size_t window_packet_sizes[LibGUI::WindowPacketType::COUNT] {};
window_packet_sizes[LibGUI::WindowPacketType::INVALID] = 0;
window_packet_sizes[LibGUI::WindowPacketType::CreateWindow] = sizeof(LibGUI::WindowCreatePacket);
window_packet_sizes[LibGUI::WindowPacketType::Invalidate] = sizeof(LibGUI::WindowInvalidatePacket);
static_assert(LibGUI::WindowPacketType::COUNT == 3);
auto config = parse_config();
WindowServer window_server(framebuffer);
if (config.background_image)
if (auto ret = window_server.set_background_image(BAN::move(config.background_image)); ret.is_error())
dwarnln("Could not set background image: {}", ret.error());
constexpr uint64_t sync_interval_us = 1'000'000 / 60;
timespec last_sync { .tv_sec = 0, .tv_nsec = 0 };
while (!window_server.is_stopped())
{
timespec current_ts;
clock_gettime(CLOCK_MONOTONIC, &current_ts);
uint64_t us_since_last_sync = (current_ts.tv_sec - last_sync.tv_sec) * 1'000'000 + (current_ts.tv_nsec - last_sync.tv_nsec) / 1000;
if (us_since_last_sync > sync_interval_us)
{
window_server.sync();
us_since_last_sync = 0;
last_sync = current_ts;
}
int max_fd = server_fd;
fd_set fds;
FD_ZERO(&fds);
FD_SET(server_fd, &fds);
if (keyboard_fd != -1)
{
FD_SET(keyboard_fd, &fds);
max_fd = BAN::Math::max(max_fd, keyboard_fd);
}
if (mouse_fd != -1)
{
FD_SET(mouse_fd, &fds);
max_fd = BAN::Math::max(max_fd, mouse_fd);
}
max_fd = BAN::Math::max(max_fd, window_server.get_client_fds(fds));
timeval select_timeout {
.tv_sec = 0,
.tv_usec = static_cast<long>(sync_interval_us - us_since_last_sync)
};
int nselect = select(max_fd + 1, &fds, nullptr, nullptr, &select_timeout);
if (nselect == 0)
continue;
if (nselect == -1)
{
dwarnln("select: {}", strerror(errno));
break;
}
if (FD_ISSET(server_fd, &fds))
{
int window_fd = accept(server_fd, nullptr, nullptr);
if (window_fd == -1)
{
dwarnln("accept: {}", strerror(errno));
continue;
}
window_server.add_client_fd(window_fd);
}
if (keyboard_fd != -1 && FD_ISSET(keyboard_fd, &fds))
{
LibInput::RawKeyEvent event;
if (read(keyboard_fd, &event, sizeof(event)) == -1)
{
perror("read");
continue;
}
window_server.on_key_event(LibInput::KeyboardLayout::get().key_event_from_raw(event));
}
if (mouse_fd != -1 && FD_ISSET(mouse_fd, &fds))
{
LibInput::MouseEvent event;
if (read(mouse_fd, &event, sizeof(event)) == -1)
{
perror("read");
continue;
}
switch (event.type)
{
case LibInput::MouseEventType::MouseButtonEvent:
window_server.on_mouse_button(event.button_event);
break;
case LibInput::MouseEventType::MouseMoveEvent:
window_server.on_mouse_move(event.move_event);
break;
case LibInput::MouseEventType::MouseScrollEvent:
window_server.on_mouse_scroll(event.scroll_event);
break;
}
}
window_server.for_each_client_fd(
[&](int fd) -> BAN::Iteration
{
if (!FD_ISSET(fd, &fds))
return BAN::Iteration::Continue;
LibGUI::WindowPacket packet;
ssize_t nrecv = recv(fd, &packet, sizeof(packet), 0);
if (nrecv < 0)
dwarnln("recv: {}", strerror(errno));
if (nrecv <= 0)
{
window_server.remove_client_fd(fd);
return BAN::Iteration::Continue;
}
if (packet.type == LibGUI::WindowPacketType::INVALID || packet.type >= LibGUI::WindowPacketType::COUNT)
dwarnln("Invalid WindowPacket (type {})", (int)packet.type);
if (static_cast<size_t>(nrecv) != window_packet_sizes[packet.type])
dwarnln("Invalid WindowPacket size (type {}, size {})", (int)packet.type, nrecv);
else
window_server.on_window_packet(fd, packet);
return BAN::Iteration::Continue;
}
);
}
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(cat-mmap ${SOURCES})
banan_link_library(cat-mmap libc)
install(TARGETS cat-mmap OPTIONAL)

View File

@@ -0,0 +1,66 @@
#include <fcntl.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/stat.h>
bool cat_file(int fd)
{
struct stat st;
if (fstat(fd, &st) == -1)
{
perror("stat");
return false;
}
void* addr = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED)
{
perror("mmap");
return false;
}
ssize_t nwrite = write(STDOUT_FILENO, addr, st.st_size);
if (nwrite == -1)
perror("write");
if (static_cast<uint8_t*>(addr)[st.st_size - 1] != '\n')
if (write(STDOUT_FILENO, "\n", 1) == -1)
perror("write");
if (munmap(addr, st.st_size) == -1)
{
perror("munmap");
return false;
}
return true;
}
int main(int argc, char** argv)
{
int ret = 0;
if (argc > 1)
{
for (int i = 1; i < argc; i++)
{
int fd = open(argv[i], O_RDONLY);
if (fd == -1)
{
perror(argv[i]);
ret = 1;
continue;
}
if (!cat_file(fd))
ret = 1;
close(fd);
}
}
else
{
if (!cat_file(STDIN_FILENO))
ret = 1;
}
return ret;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(cat ${SOURCES})
banan_link_library(cat libc)
install(TARGETS cat OPTIONAL)

View File

@@ -0,0 +1,50 @@
#include <fcntl.h>
#include <stdio.h>
bool cat_file(int fd)
{
char last = '\0';
char buffer[1024];
while (ssize_t n_read = read(fd, buffer, sizeof(buffer)))
{
if (n_read == -1)
{
perror("read");
return false;
}
write(STDOUT_FILENO, buffer, n_read);
last = buffer[n_read - 1];
}
if (last != '\n')
write(STDOUT_FILENO, "\n", 1);
return true;
}
int main(int argc, char** argv)
{
int ret = 0;
if (argc > 1)
{
for (int i = 1; i < argc; i++)
{
int fd = open(argv[i], O_RDONLY);
if (fd == -1)
{
perror(argv[i]);
ret = 1;
continue;
}
if (!cat_file(fd))
ret = 1;
close(fd);
}
}
else
{
if (!cat_file(STDIN_FILENO))
ret = 1;
}
return ret;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(chmod ${SOURCES})
banan_link_library(chmod libc)
install(TARGETS chmod OPTIONAL)

View File

@@ -0,0 +1,43 @@
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
void usage(const char* argv0, int ret)
{
FILE* out = (ret == 0) ? stdout : stderr;
fprintf(out, "usage: %s MODE FILE...\n", argv0);
fprintf(out, " Change the mode of each FILE to MODE.\n");
exit(ret);
}
int main(int argc, char** argv)
{
if (argc <= 2)
usage(argv[0], 1);
int base = (argv[1][0] == '0') ? 8 : 10;
mode_t mode = 0;
for (const char* ptr = argv[1]; *ptr; ptr++)
{
if (!isdigit(*ptr))
{
fprintf(stderr, "Invalid MODE %s\n", argv[1]);
usage(argv[0], 1);
}
mode = (mode * base) + (*ptr - '0');
}
int ret = 0;
for (int i = 2; i < argc; i++)
{
if (chmod(argv[i], mode) == -1)
{
perror("chmod");
ret = 1;
}
}
return ret;
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(cp ${SOURCES})
banan_include_headers(cp ban)
banan_link_library(cp libc)
install(TARGETS cp OPTIONAL)

View File

@@ -0,0 +1,143 @@
#include <BAN/String.h>
#include <BAN/Vector.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#define STR_STARTS_WITH(str, arg) (strncmp(str, arg, sizeof(arg) - 1) == 0)
#define STR_EQUAL(str, arg) (strcmp(str, arg) == 0)
bool copy_file(const BAN::String& source, BAN::String destination)
{
struct stat st;
if (stat(source.data(), &st) == -1)
{
fprintf(stderr, "%s: ", source.data());
perror("stat");
return false;
}
if (S_ISDIR(st.st_mode))
{
fprintf(stderr, "%s: is a directory\n", source.data());
return false;
}
if (stat(destination.data(), &st) != -1 && S_ISDIR(st.st_mode))
{
MUST(destination.push_back('/'));
MUST(destination.append(MUST(source.sv().split('/')).back()));
}
int src_fd = open(source.data(), O_RDONLY);
if (src_fd == -1)
{
fprintf(stderr, "%s: ", source.data());
perror("open");
return false;
}
int dest_fd = open(destination.data(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (dest_fd == -1)
{
fprintf(stderr, "%s: ", destination.data());
perror("open");
close(src_fd);
return false;
}
bool ret = true;
char buffer[1024];
while (ssize_t nread = read(src_fd, buffer, sizeof(buffer)))
{
if (nread < 0)
{
fprintf(stderr, "%s: ", source.data());
perror("read");
ret = false;
break;
}
ssize_t written = 0;
while (written < nread)
{
ssize_t nwrite = write(dest_fd, buffer, nread - written);
if (nwrite < 0)
{
fprintf(stderr, "%s: ", destination.data());
perror("write");
ret = false;
}
if (nwrite <= 0)
break;
written += nwrite;
}
if (written < nread)
break;
}
close(src_fd);
close(dest_fd);
return ret;
}
bool copy_file_to_directory(const BAN::String& source, const BAN::String& destination)
{
auto temp = destination;
MUST(temp.append(MUST(source.sv().split('/')).back()));
return copy_file(source, destination);
}
void usage(const char* argv0, int ret)
{
FILE* out = (ret == 0) ? stdout : stderr;
fprintf(out, "usage: %s [OPTIONS]... SOURCE... DEST\n", argv0);
fprintf(out, "Copies files SOURCE... to DEST\n");
fprintf(out, "OPTIONS:\n");
fprintf(out, " -h, --help\n");
fprintf(out, " Show this message and exit\n");
exit(ret);
}
int main(int argc, char** argv)
{
BAN::Vector<BAN::StringView> src;
BAN::StringView dest;
int i = 1;
for (; i < argc; i++)
{
if (STR_EQUAL(argv[i], "-h") || STR_EQUAL(argv[i], "--help"))
{
usage(argv[0], 0);
}
else if (argv[i][0] == '-')
{
fprintf(stderr, "Unknown argument %s\n", argv[i]);
usage(argv[0], 1);
}
else
{
break;
}
}
for (; i < argc - 1; i++)
MUST(src.push_back(argv[i]));
dest = argv[argc - 1];
if (src.empty())
{
fprintf(stderr, "Missing destination operand\n");
usage(argv[0], 1);
}
int ret = 0;
for (auto file_path : src)
if (!copy_file(file_path, dest))
ret = 1;
return ret;
}

View File

@@ -0,0 +1,28 @@
#!/bin/bash
set -e
PROGRAM_NAME=$1
mkdir $PROGRAM_NAME
cat > $PROGRAM_NAME/CMakeLists.txt << EOF
set(SOURCES
main.cpp
)
add_executable($PROGRAM_NAME \${SOURCES})
banan_link_library($PROGRAM_NAME ban)
banan_link_library($PROGRAM_NAME libc)
install(TARGETS $PROGRAM_NAME)
EOF
cat > $PROGRAM_NAME/main.cpp << EOF
#include <stdio.h>
int main()
{
printf("Hello World\n");
}
EOF

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(dd ${SOURCES})
banan_link_library(dd libc)
install(TARGETS dd OPTIONAL)

View File

@@ -0,0 +1,189 @@
#include <ctype.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define CURRENT_NS() ({ timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); ts.tv_sec * 1'000'000'000 + ts.tv_nsec; })
uint64_t parse_sized_u64(const char* val)
{
uint64_t result = 0;
const char* ptr = val;
for (; *ptr && isdigit(*ptr); ptr++)
result = (result * 10) + (*ptr - '0');
switch (*ptr)
{
case 'E': result *= 1024; // fall through
case 'P': result *= 1024; // fall through
case 'T': result *= 1024; // fall through
case 'G': result *= 1024; // fall through
case 'M': result *= 1024; // fall through
case 'K': case 'k': result *= 1024; ptr++; break;
}
if (*ptr != '\0')
{
fprintf(stderr, "invalid number: %s\n", val);
exit(1);
}
return result;
}
void print_value_with_unit(uint64_t value_x10, unsigned base, const char* units[])
{
unsigned index = 0;
while (value_x10 / 10 >= base)
{
index++;
value_x10 /= base;
}
if (value_x10 < 100)
printf("%u.%u %s", (unsigned)value_x10 / 10, (unsigned)value_x10 % 10, units[index]);
else
printf("%u %s", (unsigned)value_x10 / 10, units[index]);
}
void print_time(uint64_t start_ns, uint64_t end_ns, uint64_t transfered, bool last = false)
{
static bool first = true;
if (!first)
printf("\e[F");
first = false;
printf("%" PRIu64 " bytes", transfered);
if (transfered >= 1000)
{
printf(" (");
{
const char* units[] { "", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB" };
print_value_with_unit(transfered * 10, 1000, units);
}
if (transfered >= 1024)
{
printf(", ");
const char* units[] { "", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB" };
print_value_with_unit(transfered * 10, 1024, units);
}
printf(")");
}
printf(" copied");
double duration_s = (end_ns - start_ns) / 1e9;
if (last)
printf(", %f s, ", duration_s);
else
printf(", %u s, ", (unsigned)duration_s);
const char* units[] { "B/s", "kB/s", "MB/s", "GB/s", "TB/s", "PB/s", "EB/s", "ZB/s", "YB/s", "RB/s", "QB/s" };
print_value_with_unit(10 * transfered / duration_s, 1000, units);
printf("\e[K\n");
}
int main(int argc, char** argv)
{
const char* input = nullptr;
const char* output = nullptr;
uint64_t bs = 512;
uint64_t count = ~(uint64_t)0;
bool print_progress = false;
for (int i = 1; i < argc; i++)
{
if (strncmp(argv[i], "if=", 3) == 0)
input = argv[i] + 3;
else if (strncmp(argv[i], "of=", 3) == 0)
output = argv[i] + 3;
else if (strncmp(argv[i], "bs=", 3) == 0)
bs = parse_sized_u64(argv[i] + 3);
else if (strncmp(argv[i], "count=", 6) == 0)
count = parse_sized_u64(argv[i] + 6);
else if (strcmp(argv[i], "status=progress") == 0)
print_progress = true;
else
{
fprintf(stderr, "unrecognized option: %s\n", argv[i]);
exit(1);
}
}
int ifd = STDIN_FILENO;
if (input)
{
ifd = open(input, O_RDONLY);
if (ifd == -1)
{
perror("open");
return 1;
}
}
int ofd = STDIN_FILENO;
if (output)
{
ofd = open(output, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (ofd == -1)
{
perror("open");
return 1;
}
}
uint8_t* buffer = (uint8_t*)malloc(bs);
if (buffer == nullptr)
{
perror("malloc");
return 1;
}
uint64_t total_transfered = 0;
uint64_t start_ns = CURRENT_NS();
uint64_t last_print_s = 0;
for (uint64_t i = 0; i != count; i++)
{
ssize_t nread = read(ifd, buffer, bs);
if (nread == -1)
{
perror("read");
return 1;
}
ssize_t nwrite = write(ofd, buffer, nread);
if (nwrite == -1)
{
perror("write");
return 1;
}
total_transfered += nwrite;
if ((size_t)nread < bs || (size_t)nwrite < bs)
break;
if (print_progress)
{
uint64_t current_ns = CURRENT_NS();
if ((current_ns - start_ns) / 1'000'000'000 > last_print_s)
{
print_time(start_ns, current_ns, total_transfered);
last_print_s = (current_ns - start_ns) / 1'000'000'000;
}
}
}
print_time(start_ns, CURRENT_NS(), total_transfered, true);
close(ifd);
close(ofd);
free(buffer);
return 0;
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(dhcp-client ${SOURCES})
banan_include_headers(dhcp-client ban)
banan_link_library(dhcp-client libc)
install(TARGETS dhcp-client OPTIONAL)

View File

@@ -0,0 +1,385 @@
#include <BAN/Debug.h>
#include <BAN/Endianness.h>
#include <BAN/IPv4.h>
#include <BAN/MAC.h>
#include <BAN/Optional.h>
#include <BAN/Vector.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stropts.h>
#include <sys/socket.h>
#define DEBUG_DHCP 1
struct DHCPPacket
{
uint8_t op;
uint8_t htype { 0x01 };
uint8_t hlen { 0x06 };
uint8_t hops { 0x00 };
BAN::NetworkEndian<uint32_t> xid { 0x3903F326 };
BAN::NetworkEndian<uint16_t> secs { 0x0000 };
BAN::NetworkEndian<uint16_t> flags { 0x0000 };
BAN::IPv4Address ciaddr { 0 };
BAN::IPv4Address yiaddr { 0 };
BAN::IPv4Address siaddr { 0 };
BAN::IPv4Address giaddr { 0 };
BAN::MACAddress chaddr;
uint8_t padding[10] {};
uint8_t legacy[192] {};
BAN::NetworkEndian<uint32_t> magic_cookie { 0x63825363 };
uint8_t options[0x100];
};
static_assert(offsetof(DHCPPacket, options) == 240);
enum DHCPType
{
SubnetMask = 1,
Router = 3,
DomainNameServer = 6,
RequestedIPv4Address= 50,
DHCPMessageType = 53,
ServerIdentifier = 54,
ParameterRequestList = 55,
End = 255,
};
enum DHCPMessageType
{
INVALID = 0,
DHCPDISCOVER = 1,
DHCPOFFER = 2,
DHCPREQUEST = 3,
DHCPDECLINE = 4,
DHCPACK = 5,
};
BAN::MACAddress get_mac_address(int socket)
{
ifreq ifreq;
if (ioctl(socket, SIOCGIFHWADDR, &ifreq) == -1)
{
perror("ioctl");
exit(1);
}
BAN::MACAddress mac_address;
memcpy(&mac_address, ifreq.ifr_ifru.ifru_hwaddr.sa_data, sizeof(mac_address));
return mac_address;
}
void update_ipv4_info(int socket, BAN::IPv4Address address, BAN::IPv4Address netmask, BAN::IPv4Address gateway)
{
{
ifreq ifreq;
auto& ifru_addr = *reinterpret_cast<sockaddr_in*>(&ifreq.ifr_ifru.ifru_addr);
ifru_addr.sin_family = AF_INET;
ifru_addr.sin_addr.s_addr = address.raw;
if (ioctl(socket, SIOCSIFADDR, &ifreq) == -1)
{
perror("ioctl");
exit(1);
}
}
{
ifreq ifreq;
auto& ifru_netmask = *reinterpret_cast<sockaddr_in*>(&ifreq.ifr_ifru.ifru_netmask);
ifru_netmask.sin_family = AF_INET;
ifru_netmask.sin_addr.s_addr = netmask.raw;
if (ioctl(socket, SIOCSIFNETMASK, &ifreq) == -1)
{
perror("ioctl");
exit(1);
}
}
if (gateway.raw)
{
ifreq ifreq;
auto& ifru_gwaddr = *reinterpret_cast<sockaddr_in*>(&ifreq.ifr_ifru.ifru_gwaddr);
ifru_gwaddr.sin_family = AF_INET;
ifru_gwaddr.sin_addr.s_addr = gateway.raw;
if (ioctl(socket, SIOCSIFGWADDR, &ifreq) == -1)
{
perror("ioctl");
exit(1);
}
}
}
void send_dhcp_packet(int socket, const DHCPPacket& dhcp_packet, BAN::IPv4Address server_ipv4)
{
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(67);
server_addr.sin_addr.s_addr = server_ipv4.raw;
if (sendto(socket, &dhcp_packet, sizeof(DHCPPacket), 0, (sockaddr*)&server_addr, sizeof(server_addr)) == -1)
{
perror("sendto");
exit(1);
}
}
void send_dhcp_discover(int socket, BAN::MACAddress mac_address)
{
DHCPPacket dhcp_packet;
dhcp_packet.op = 0x01;
dhcp_packet.chaddr = mac_address;
size_t idx = 0;
dhcp_packet.options[idx++] = DHCPMessageType;
dhcp_packet.options[idx++] = 0x01;
dhcp_packet.options[idx++] = DHCPDISCOVER;
dhcp_packet.options[idx++] = ParameterRequestList;
dhcp_packet.options[idx++] = 0x03;
dhcp_packet.options[idx++] = DomainNameServer;
dhcp_packet.options[idx++] = SubnetMask;
dhcp_packet.options[idx++] = Router;
dhcp_packet.options[idx++] = 0xFF;
send_dhcp_packet(socket, dhcp_packet, BAN::IPv4Address { 0xFFFFFFFF });
}
void send_dhcp_request(int socket, BAN::MACAddress mac_address, BAN::IPv4Address offered_ipv4, BAN::IPv4Address server_ipv4)
{
DHCPPacket dhcp_packet;
dhcp_packet.op = 0x01;
dhcp_packet.siaddr = server_ipv4.raw;
dhcp_packet.chaddr = mac_address;
size_t idx = 0;
dhcp_packet.options[idx++] = DHCPMessageType;
dhcp_packet.options[idx++] = 0x01;
dhcp_packet.options[idx++] = DHCPREQUEST;
dhcp_packet.options[idx++] = RequestedIPv4Address;
dhcp_packet.options[idx++] = 0x04;
dhcp_packet.options[idx++] = offered_ipv4.octets[0];
dhcp_packet.options[idx++] = offered_ipv4.octets[1];
dhcp_packet.options[idx++] = offered_ipv4.octets[2];
dhcp_packet.options[idx++] = offered_ipv4.octets[3];
dhcp_packet.options[idx++] = 0xFF;
send_dhcp_packet(socket, dhcp_packet, BAN::IPv4Address { 0xFFFFFFFF });
}
struct DHCPPacketInfo
{
enum DHCPMessageType message_type { INVALID };
BAN::IPv4Address address { 0 };
BAN::IPv4Address subnet { 0 };
BAN::IPv4Address server { 0 };
BAN::Vector<BAN::IPv4Address> routers;
BAN::Vector<BAN::IPv4Address> dns;
};
DHCPPacketInfo parse_dhcp_packet(const DHCPPacket& packet)
{
DHCPPacketInfo packet_info;
packet_info.address = BAN::IPv4Address(packet.yiaddr);
const uint8_t* options = packet.options;
while (*options != End)
{
uint8_t type = *options++;
uint8_t length = *options++;
switch (type)
{
case SubnetMask:
{
if (length != 4)
{
fprintf(stderr, "Subnet mask with invalid length %hhu\n", length);
break;
}
uint32_t raw = *reinterpret_cast<const uint32_t*>(options);
packet_info.subnet = BAN::IPv4Address(raw);
break;
}
case Router:
{
if (length % 4 != 0)
{
fprintf(stderr, "Router with invalid length %hhu\n", length);
break;
}
for (int i = 0; i < length; i += 4)
{
uint32_t raw = *reinterpret_cast<const uint32_t*>(options + i);
MUST(packet_info.routers.emplace_back(raw));
}
break;
}
case DomainNameServer:
{
if (length % 4 != 0)
{
fprintf(stderr, "DNS with invalid length %hhu\n", length);
break;
}
for (int i = 0; i < length; i += 4)
{
uint32_t raw = *reinterpret_cast<const uint32_t*>(options + i);
MUST(packet_info.dns.emplace_back(raw));
}
break;
}
case DHCPMessageType:
{
if (length != 1)
{
fprintf(stderr, "DHCP Message Type with invalid length %hhu\n", length);
break;
}
switch (*options)
{
case DHCPDISCOVER: packet_info.message_type = DHCPDISCOVER; break;
case DHCPOFFER: packet_info.message_type = DHCPOFFER; break;
case DHCPREQUEST: packet_info.message_type = DHCPREQUEST; break;
case DHCPDECLINE: packet_info.message_type = DHCPDECLINE; break;
case DHCPACK: packet_info.message_type = DHCPACK; break;
}
break;
}
case ServerIdentifier:
{
if (length != 4)
{
fprintf(stderr, "Server identifier with invalid length %hhu\n", length);
break;
}
uint32_t raw = *reinterpret_cast<const uint32_t*>(options);
packet_info.server = BAN::IPv4Address(raw);
break;
}
}
options += length;
}
return packet_info;
}
BAN::Optional<DHCPPacketInfo> read_dhcp_packet(int socket)
{
DHCPPacket dhcp_packet;
ssize_t nrecv = recvfrom(socket, &dhcp_packet, sizeof(dhcp_packet), 0, nullptr, nullptr);
if (nrecv == -1)
{
perror("revcfrom");
return {};
}
if (nrecv <= (ssize_t)offsetof(DHCPPacket, options))
{
fprintf(stderr, "invalid DHCP offer\n");
return {};
}
if (dhcp_packet.magic_cookie != 0x63825363)
{
fprintf(stderr, "invalid DHCP offer\n");
return {};
}
return parse_dhcp_packet(dhcp_packet);
}
int main()
{
int socket = ::socket(AF_INET, SOCK_DGRAM, 0);
if (socket == -1)
{
perror("socket");
return 1;
}
sockaddr_in client_addr;
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(68);
client_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(socket, (sockaddr*)&client_addr, sizeof(client_addr)) == -1)
{
perror("bind");
return 1;
}
auto mac_address = get_mac_address(socket);
#if DEBUG_DHCP
dprintln("MAC: {}", mac_address);
#endif
send_dhcp_discover(socket, mac_address);
#if DEBUG_DHCP
dprintln("DHCPDISCOVER sent");
#endif
auto dhcp_offer = read_dhcp_packet(socket);
if (!dhcp_offer.has_value())
return 1;
if (dhcp_offer->message_type != DHCPOFFER)
{
fprintf(stderr, "DHCP server did not respond with DHCPOFFER\n");
return 1;
}
#if DEBUG_DHCP
dprintln("DHCPOFFER");
dprintln(" IP {}", dhcp_offer->address);
dprintln(" SUBNET {}", dhcp_offer->subnet);
dprintln(" SERVER {}", dhcp_offer->server);
#endif
send_dhcp_request(socket, mac_address, dhcp_offer->address, dhcp_offer->server);
#if DEBUG_DHCP
dprintln("DHCPREQUEST sent");
#endif
auto dhcp_ack = read_dhcp_packet(socket);
if (!dhcp_ack.has_value())
return 1;
if (dhcp_ack->message_type != DHCPACK)
{
fprintf(stderr, "DHCP server did not respond with DHCPACK\n");
return 1;
}
#if DEBUG_DHCP
dprintln("DHCPACK");
dprintln(" IP {}", dhcp_ack->address);
dprintln(" SUBNET {}", dhcp_ack->subnet);
dprintln(" SERVER {}", dhcp_ack->server);
#endif
if (dhcp_offer->address != dhcp_ack->address)
{
fprintf(stderr, "DHCP server OFFER and ACK ips don't match\n");
return 1;
}
BAN::IPv4Address gateway { 0 };
if (!dhcp_ack->routers.empty())
gateway = dhcp_ack->routers.front();
update_ipv4_info(socket, dhcp_ack->address, dhcp_ack->subnet, gateway);
close(socket);
return 0;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(echo ${SOURCES})
banan_link_library(echo libc)
install(TARGETS echo OPTIONAL)

View File

@@ -0,0 +1,40 @@
#include <stdio.h>
#include <stdlib.h>
void print_argument(const char* arg)
{
while (*arg)
{
if (*arg == '\\')
{
switch (*(++arg))
{
case 'a': fputc('\a', stdout); break;
case 'b': fputc('\b', stdout); break;
case 'c': exit(0);
case 'f': fputc('\f', stdout); break;
case 'n': fputc('\n', stdout); break;
case 'r': fputc('\r', stdout); break;
case 't': fputc('\t', stdout); break;
case 'v': fputc('\v', stdout); break;
case '\\': fputc('\\', stdout); break;
default: break;
}
}
else
fputc(*arg, stdout);
arg++;
}
}
int main(int argc, char** argv)
{
for (int i = 1; i < argc; i++)
{
print_argument(argv[i]);
if (i < argc - 1)
fputc(' ', stdout);
}
printf("\n");
return 0;
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(getopt ${SOURCES})
banan_include_headers(getopt ban)
banan_link_library(getopt libc)
install(TARGETS getopt OPTIONAL)

View File

@@ -0,0 +1,44 @@
#include <BAN/String.h>
#include <BAN/Vector.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if (argc < 2)
{
fprintf(stderr, "usage: %s OPTSTRING [PARAMETERS]...", argv[0]);
return 1;
}
BAN::Vector<char*> argv_copy(argc - 1);
argv_copy[0] = argv[0];
for (int i = 2; i < argc; i++)
argv_copy[i - 1] = argv[i];
int opt;
BAN::String parsed;
while ((opt = getopt(argc - 1, argv_copy.data(), argv[1])) != -1)
{
if (opt == ':' || opt == '?')
continue;
MUST(parsed.append(" -"));
MUST(parsed.push_back(opt));
if (optarg)
{
MUST(parsed.push_back(' '));
MUST(parsed.append(optarg));
}
optarg = nullptr;
}
printf("%s --", parsed.data());
for (; optind < argc - 1; optind++)
printf(" %s", argv_copy[optind]);
printf("\n");
return 0;
}

View File

@@ -0,0 +1,10 @@
set(SOURCES
main.cpp
HTTPServer.cpp
)
add_executable(http-server ${SOURCES})
banan_link_library(http-server ban)
banan_link_library(http-server libc)
install(TARGETS http-server OPTIONAL)

View File

@@ -0,0 +1,516 @@
#include "HTTPServer.h"
#include <BAN/Debug.h>
#include <BAN/ScopeGuard.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
static BAN::StringView status_to_brief(unsigned);
static BAN::StringView extension_to_mime(BAN::StringView);
HTTPServer::HTTPServer() = default;
HTTPServer::~HTTPServer()
{
if (m_listen_socket != -1)
close(m_listen_socket);
}
BAN::ErrorOr<void> HTTPServer::initialize(BAN::StringView root, BAN::IPv4Address ip, int port)
{
{
char path_buffer[PATH_MAX];
if (root.size() >= PATH_MAX)
return BAN::Error::from_errno(ENAMETOOLONG);
strcpy(path_buffer, root.data());
char canonical_buffer[PATH_MAX];
if (realpath(path_buffer, canonical_buffer) == NULL)
return BAN::Error::from_errno(errno);
TRY(m_web_root.append(canonical_buffer));
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = ip.raw;
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1)
return BAN::Error::from_errno(errno);
BAN::ScopeGuard socket_guard([sock] { close(sock); });
if (bind(sock, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == -1)
return BAN::Error::from_errno(errno);
if (listen(sock, SOMAXCONN) == -1)
return BAN::Error::from_errno(errno);
m_listen_socket = sock;
socket_guard.disable();
return {};
}
void HTTPServer::start()
{
ASSERT(m_listen_socket != -1);
while (true)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(m_listen_socket, &fds);
int max_fd = m_listen_socket;
for (const auto& [fd, _] : m_client_data)
{
FD_SET(fd, &fds);
max_fd = BAN::Math::max(max_fd, fd);
}
if (select(max_fd + 1, &fds, nullptr, nullptr, nullptr) == -1)
{
perror("select");
break;
}
if (FD_ISSET(m_listen_socket, &fds))
{
int new_fd = accept(m_listen_socket, nullptr, nullptr);
if (new_fd == -1)
{
perror("accept");
continue;
}
MUST(m_client_data.emplace(new_fd));
}
for (auto& [fd, data] : m_client_data)
{
if (!FD_ISSET(fd, &fds))
continue;
char buffer[1024];
int nrecv = recv(fd, buffer, sizeof(buffer), 0);
if (nrecv < 0)
perror("recv");
if (nrecv <= 0)
{
close(fd);
m_client_data.remove(fd);
break;
}
size_t old_size = data.size();
if (data.resize(old_size + nrecv).is_error())
{
close(fd);
m_client_data.remove(fd);
break;
}
memcpy(data.data() + old_size, buffer, nrecv);
if (!handle_all_requests(fd, data))
{
close(fd);
m_client_data.remove(fd);
break;
}
}
}
}
BAN::ErrorOr<HTTPRequest> HTTPServer::get_http_request(BAN::Vector<uint8_t>& data_vec)
{
auto data = BAN::ConstByteSpan(data_vec.span());
if (data.size() < 4)
return BAN::Error::from_errno(ENODATA);
size_t len = 0;
for (;; len++)
{
if (len > data.size() - 4)
return BAN::Error::from_errno(ENODATA);
if (!isprint(data[len]) && !isspace(data[len]))
return BAN::Error::from_errno(EINVAL);
if (data[len + 0] != '\r')
continue;
if (data[len + 1] != '\n')
continue;
if (data[len + 2] != '\r')
continue;
if (data[len + 3] != '\n')
continue;
break;
}
auto header_data = BAN::StringView(reinterpret_cast<const char*>(data.data()), len + 1);
auto lines = TRY(header_data.split('\n', false));
if (lines.empty())
return BAN::Error::from_errno(EINVAL);
for (auto& line : lines)
{
if (line.empty() || line.back() != '\r')
return BAN::Error::from_errno(EINVAL);
line = line.substring(0, line.size() - 1);
}
HTTPRequest request;
{
auto request_line = TRY(lines[0].split(' '));
if (request_line.size() != 3)
return BAN::Error::from_errno(EINVAL);
request.method = request_line[0];
request.path = request_line[1];
request.version = request_line[2];
}
size_t content_length = 0;
for (size_t i = 1; i < lines.size(); i++)
{
auto opt_colon = lines[i].find(':');
if (!opt_colon.has_value())
return BAN::Error::from_errno(EINVAL);
auto name = lines[i].substring(0, opt_colon.value());
auto value = lines[i].substring(opt_colon.value() + 1);
while (!value.empty() && isspace(value.front()))
value = value.substring(1);
while (!value.empty() && isspace(value.back()))
value = value.substring(0, value.size() - 1);
TRY(request.headers.emplace_back(name, value));
if (name.size() == "Content-Length"_sv.size())
{
bool is_content_length = true;
for (size_t i = 0; i < name.size() && is_content_length; i++)
if (tolower(name[i]) != tolower("Content-Length"_sv[i]))
is_content_length = false;
if (is_content_length)
content_length = strtoul(value.data(), nullptr, 10);
}
}
if (data.size() < len + 4 + content_length)
return BAN::Error::from_errno(ENODATA);
request.body = data.slice(len + 4, content_length);
{
size_t request_size = len + 4 + content_length;
size_t new_size = data.size() - request_size;
memmove(data_vec.data(), data_vec.data() + request_size, new_size);
MUST(data_vec.resize(new_size));
}
return request;
}
BAN::ErrorOr<void> HTTPServer::send_http_response(int fd, unsigned status, BAN::ConstByteSpan data, BAN::StringView mime)
{
dprintln("HTTP/1.1 {} {}", status, status_to_brief(status));
BAN::String output;
TRY(output.append(MUST(BAN::String::formatted("HTTP/1.1 {} {}\r\n", status, status_to_brief(status)))));
if (!mime.empty())
TRY(output.append(MUST(BAN::String::formatted("Content-Type: {}\r\n", mime))));
TRY(output.append(MUST(BAN::String::formatted("Content-Length: {}\r\n", data.size()))));
TRY(output.append("\r\n"));
size_t total_sent = 0;
while (total_sent < output.size())
{
ssize_t nsend = send(fd, output.data() + total_sent, output.size() - total_sent, 0);
if (nsend == -1)
return BAN::Error::from_errno(errno);
if (nsend == 0)
return BAN::Error::from_errno(ECONNRESET);
total_sent += nsend;
}
total_sent = 0;
while (total_sent < data.size())
{
ssize_t nsend = send(fd, data.data() + total_sent, data.size() - total_sent, 0);
if (nsend == -1)
return BAN::Error::from_errno(errno);
if (nsend == 0)
return BAN::Error::from_errno(ECONNRESET);
total_sent += nsend;
}
return {};
}
BAN::ErrorOr<unsigned> HTTPServer::handle_request(int fd, BAN::Vector<uint8_t>& data)
{
auto request_or_error = get_http_request(data);
if (request_or_error.is_error())
return request_or_error.release_error();
auto request = request_or_error.release_value();
dprintln("{} {} {}", request.method, request.path, request.version);
// remove query string
if (auto idx = request.path.find('?'); idx.has_value())
request.path = request.path.substring(0, idx.value());
// illegal path
if (request.path.empty() || request.path.front() != '/')
return 400;
BAN::StringView path_suffix;
if (request.path.back() == '/')
path_suffix = "index.html"_sv;
else
{
auto file = request.path.substring(request.path.rfind('/').value());
if (!file.contains('.'))
path_suffix = ".html"_sv;
}
auto target_path = TRY(BAN::String::formatted("{}{}{}", m_web_root, request.path, path_suffix));
auto extension = target_path.sv().substring(target_path.sv().rfind('.').value());
dprintln("looking for '{}'", target_path);
char canonical_buffer[PATH_MAX];
if (realpath(target_path.data(), canonical_buffer) == NULL)
{
switch (errno)
{
case EACCES:
return 403;
case ENAMETOOLONG:
case ENOENT:
case ENOTDIR:
return 404;
case ELOOP:
return 500;
default:
return BAN::Error::from_errno(errno);
}
}
dprintln("validating '{}'", canonical_buffer);
if (strncmp(canonical_buffer, m_web_root.data(), m_web_root.size()))
return BAN::Error::from_errno(403);
int file_fd = open(canonical_buffer, O_RDONLY);
if (file_fd == -1)
return (errno == EACCES) ? 403 : 404;
BAN::ScopeGuard _([file_fd] { close(file_fd); });
struct stat file_st;
if (fstat(file_fd, &file_st) == -1)
return 500;
BAN::Vector<uint8_t> file_data;
if (file_data.resize(file_st.st_size).is_error())
return 500;
if (read(file_fd, file_data.data(), file_data.size()) == -1)
return 500;
TRY(send_http_response(fd, 200, BAN::ConstByteSpan(file_data.span()), extension_to_mime(extension)));
return 200;
}
bool HTTPServer::handle_all_requests(int fd, BAN::Vector<uint8_t>& data)
{
while (true)
{
auto result = handle_request(fd, data);
if (result.is_error() && result.error().get_error_code() == ENODATA)
return true;
if (result.is_error())
return false;
if (result.value() == 200)
continue;
if (send_http_response(fd, result.value(), {}, {}).is_error())
return false;
}
}
static BAN::StringView status_to_brief(unsigned status)
{
static BAN::HashMap<unsigned, BAN::StringView> status_to_brief;
if (status_to_brief.empty())
{
MUST(status_to_brief.emplace(100, "Continue"_sv));
MUST(status_to_brief.emplace(101, "Switching Protocols"_sv));
MUST(status_to_brief.emplace(102, "Processing"_sv));
MUST(status_to_brief.emplace(103, "Early Hints"_sv));
MUST(status_to_brief.emplace(200, "OK"_sv));
MUST(status_to_brief.emplace(201, "Created"_sv));
MUST(status_to_brief.emplace(202, "Accepted"_sv));
MUST(status_to_brief.emplace(203, "Non-Authoritative Information"_sv));
MUST(status_to_brief.emplace(204, "No Content"_sv));
MUST(status_to_brief.emplace(205, "Reset Content"_sv));
MUST(status_to_brief.emplace(206, "Partial Content"_sv));
MUST(status_to_brief.emplace(207, "Multi-Status"_sv));
MUST(status_to_brief.emplace(208, "Already Reported"_sv));
MUST(status_to_brief.emplace(226, "IM Used"_sv));
MUST(status_to_brief.emplace(300, "Multiple Choices"_sv));
MUST(status_to_brief.emplace(301, "Moved Permanently"_sv));
MUST(status_to_brief.emplace(302, "Found"_sv));
MUST(status_to_brief.emplace(303, "See Other"_sv));
MUST(status_to_brief.emplace(304, "Not Modified"_sv));
MUST(status_to_brief.emplace(305, "Use Proxy"_sv));
MUST(status_to_brief.emplace(306, "Switch Proxy"_sv));
MUST(status_to_brief.emplace(307, "Temporary Redirect"_sv));
MUST(status_to_brief.emplace(308, "Permanent Redirect"_sv));
MUST(status_to_brief.emplace(400, "Bad Request"_sv));
MUST(status_to_brief.emplace(401, "Unauthorized"_sv));
MUST(status_to_brief.emplace(402, "Payment Required Experimental"_sv));
MUST(status_to_brief.emplace(403, "Forbidden"_sv));
MUST(status_to_brief.emplace(404, "Not Found"_sv));
MUST(status_to_brief.emplace(405, "Method Not Allowed"_sv));
MUST(status_to_brief.emplace(406, "Not Acceptable"_sv));
MUST(status_to_brief.emplace(407, "Proxy Authentication Required"_sv));
MUST(status_to_brief.emplace(408, "Request Timeout"_sv));
MUST(status_to_brief.emplace(409, "Conflict"_sv));
MUST(status_to_brief.emplace(410, "Gone"_sv));
MUST(status_to_brief.emplace(411, "Length Required"_sv));
MUST(status_to_brief.emplace(412, "Precondition Failed"_sv));
MUST(status_to_brief.emplace(413, "Payload Too Large"_sv));
MUST(status_to_brief.emplace(414, "URI Too Long"_sv));
MUST(status_to_brief.emplace(415, "Unsupported Media Type"_sv));
MUST(status_to_brief.emplace(416, "Range Not Satisfiable"_sv));
MUST(status_to_brief.emplace(417, "Expectation Failed"_sv));
MUST(status_to_brief.emplace(418, "I'm a teapot"_sv));
MUST(status_to_brief.emplace(421, "Misdirected Request"_sv));
MUST(status_to_brief.emplace(422, "Unprocessable Content (WebDAV)"_sv));
MUST(status_to_brief.emplace(423, "Locked (WebDAV)"_sv));
MUST(status_to_brief.emplace(424, "Failed Dependency (WebDAV)"_sv));
MUST(status_to_brief.emplace(425, "Too Early Experimental"_sv));
MUST(status_to_brief.emplace(426, "Upgrade Required"_sv));
MUST(status_to_brief.emplace(428, "Precondition Required"_sv));
MUST(status_to_brief.emplace(429, "Too Many Requests"_sv));
MUST(status_to_brief.emplace(431, "Request Header Fields Too Large"_sv));
MUST(status_to_brief.emplace(451, "Unavailable For Legal Reasons"_sv));
MUST(status_to_brief.emplace(500, "Internal Server Error"_sv));
MUST(status_to_brief.emplace(501, "Not Implemented"_sv));
MUST(status_to_brief.emplace(502, "Bad Gateway"_sv));
MUST(status_to_brief.emplace(503, "Service Unavailable"_sv));
MUST(status_to_brief.emplace(504, "Gateway Timeout"_sv));
MUST(status_to_brief.emplace(505, "HTTP Version Not Supported"_sv));
MUST(status_to_brief.emplace(506, "Variant Also Negotiates"_sv));
MUST(status_to_brief.emplace(507, "Insufficient Storage (WebDAV)"_sv));
MUST(status_to_brief.emplace(508, "Loop Detected (WebDAV)"_sv));
MUST(status_to_brief.emplace(510, "Not Extended"_sv));
MUST(status_to_brief.emplace(511, "Network Authentication Required"_sv));
}
auto it = status_to_brief.find(status);
if (it == status_to_brief.end())
return "unknown"_sv;
return it->value;
}
static BAN::StringView extension_to_mime(BAN::StringView extension)
{
static BAN::HashMap<BAN::StringView, BAN::StringView> extension_to_mime;
if (extension_to_mime.empty())
{
MUST(extension_to_mime.emplace(".aac"_sv, "audio/aac"_sv));
MUST(extension_to_mime.emplace(".abw"_sv, "application/x-abiword"_sv));
MUST(extension_to_mime.emplace(".apng"_sv, "image/apng"_sv));
MUST(extension_to_mime.emplace(".arc"_sv, "application/x-freearc"_sv));
MUST(extension_to_mime.emplace(".avif"_sv, "image/avif"_sv));
MUST(extension_to_mime.emplace(".avi"_sv, "video/x-msvideo"_sv));
MUST(extension_to_mime.emplace(".azw"_sv, "application/vnd.amazon.ebook"_sv));
MUST(extension_to_mime.emplace(".bin"_sv, "application/octet-stream"_sv));
MUST(extension_to_mime.emplace(".bmp"_sv, "image/bmp"_sv));
MUST(extension_to_mime.emplace(".bz"_sv, "application/x-bzip"_sv));
MUST(extension_to_mime.emplace(".bz2"_sv, "application/x-bzip2"_sv));
MUST(extension_to_mime.emplace(".cda"_sv, "application/x-cdf"_sv));
MUST(extension_to_mime.emplace(".csh"_sv, "application/x-csh"_sv));
MUST(extension_to_mime.emplace(".css"_sv, "text/css"_sv));
MUST(extension_to_mime.emplace(".csv"_sv, "text/csv"_sv));
MUST(extension_to_mime.emplace(".doc"_sv, "application/msword"_sv));
MUST(extension_to_mime.emplace(".docx"_sv, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"_sv));
MUST(extension_to_mime.emplace(".eot"_sv, "application/vnd.ms-fontobject"_sv));
MUST(extension_to_mime.emplace(".epub"_sv, "application/epub+zip"_sv));
MUST(extension_to_mime.emplace(".gz"_sv, "application/gzip"_sv));
MUST(extension_to_mime.emplace(".gif"_sv, "image/gif"_sv));
MUST(extension_to_mime.emplace(".htm"_sv, "text/html"_sv));
MUST(extension_to_mime.emplace(".html"_sv, "text/html"_sv));
MUST(extension_to_mime.emplace(".ico"_sv, "image/vnd.microsoft.icon"_sv));
MUST(extension_to_mime.emplace(".ics"_sv, "text/calendar"_sv));
MUST(extension_to_mime.emplace(".jar"_sv, "application/java-archive"_sv));
MUST(extension_to_mime.emplace(".jpeg"_sv, "image/jpeg"_sv));
MUST(extension_to_mime.emplace(".jpg"_sv, "image/jpeg"_sv));
MUST(extension_to_mime.emplace(".js"_sv, "text/javascript"_sv));
MUST(extension_to_mime.emplace(".json"_sv, "application/json"_sv));
MUST(extension_to_mime.emplace(".jsonld"_sv, "application/ld+json"_sv));
MUST(extension_to_mime.emplace(".mid"_sv, "audio/midi, audio/x-midi"_sv));
MUST(extension_to_mime.emplace(".midi"_sv, "audio/midi, audio/x-midi"_sv));
MUST(extension_to_mime.emplace(".mjs"_sv, "text/javascript"_sv));
MUST(extension_to_mime.emplace(".mp3"_sv, "audio/mpeg"_sv));
MUST(extension_to_mime.emplace(".mp4"_sv, "video/mp4"_sv));
MUST(extension_to_mime.emplace(".mpeg"_sv, "video/mpeg"_sv));
MUST(extension_to_mime.emplace(".mpkg"_sv, "application/vnd.apple.installer+xml"_sv));
MUST(extension_to_mime.emplace(".odp"_sv, "application/vnd.oasis.opendocument.presentation"_sv));
MUST(extension_to_mime.emplace(".ods"_sv, "application/vnd.oasis.opendocument.spreadsheet"_sv));
MUST(extension_to_mime.emplace(".odt"_sv, "application/vnd.oasis.opendocument.text"_sv));
MUST(extension_to_mime.emplace(".oga"_sv, "audio/ogg"_sv));
MUST(extension_to_mime.emplace(".ogv"_sv, "video/ogg"_sv));
MUST(extension_to_mime.emplace(".ogx"_sv, "application/ogg"_sv));
MUST(extension_to_mime.emplace(".opus"_sv, "audio/ogg"_sv));
MUST(extension_to_mime.emplace(".otf"_sv, "font/otf"_sv));
MUST(extension_to_mime.emplace(".png"_sv, "image/png"_sv));
MUST(extension_to_mime.emplace(".pdf"_sv, "application/pdf"_sv));
MUST(extension_to_mime.emplace(".php"_sv, "application/x-httpd-php"_sv));
MUST(extension_to_mime.emplace(".ppt"_sv, "application/vnd.ms-powerpoint"_sv));
MUST(extension_to_mime.emplace(".pptx"_sv, "application/vnd.openxmlformats-officedocument.presentationml.presentation"_sv));
MUST(extension_to_mime.emplace(".rar"_sv, "application/vnd.rar"_sv));
MUST(extension_to_mime.emplace(".rtf"_sv, "application/rtf"_sv));
MUST(extension_to_mime.emplace(".sh"_sv, "application/x-sh"_sv));
MUST(extension_to_mime.emplace(".svg"_sv, "image/svg+xml"_sv));
MUST(extension_to_mime.emplace(".tar"_sv, "application/x-tar"_sv));
MUST(extension_to_mime.emplace(".tif"_sv, "image/tiff"_sv));
MUST(extension_to_mime.emplace(".tiff"_sv, "image/tiff"_sv));
MUST(extension_to_mime.emplace(".ts"_sv, "video/mp2t"_sv));
MUST(extension_to_mime.emplace(".ttf"_sv, "font/ttf"_sv));
MUST(extension_to_mime.emplace(".txt"_sv, "text/plain"_sv));
MUST(extension_to_mime.emplace(".vsd"_sv, "application/vnd.visio"_sv));
MUST(extension_to_mime.emplace(".wav"_sv, "audio/wav"_sv));
MUST(extension_to_mime.emplace(".weba"_sv, "audio/webm"_sv));
MUST(extension_to_mime.emplace(".webm"_sv, "video/webm"_sv));
MUST(extension_to_mime.emplace(".webp"_sv, "image/webp"_sv));
MUST(extension_to_mime.emplace(".woff"_sv, "font/woff"_sv));
MUST(extension_to_mime.emplace(".woff2"_sv, "font/woff2"_sv));
MUST(extension_to_mime.emplace(".xhtml"_sv, "application/xhtml+xml"_sv));
MUST(extension_to_mime.emplace(".xls"_sv, "application/vnd.ms-excel"_sv));
MUST(extension_to_mime.emplace(".xlsx"_sv, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"_sv));
MUST(extension_to_mime.emplace(".xml"_sv, "application/xml"_sv));
MUST(extension_to_mime.emplace(".xul"_sv, "application/vnd.mozilla.xul+xml"_sv));
MUST(extension_to_mime.emplace(".zip"_sv, "application/zip"_sv));
MUST(extension_to_mime.emplace(".3gp"_sv, "video/3gpp"_sv));
MUST(extension_to_mime.emplace(".3g2"_sv, "video/3gpp2"_sv));
MUST(extension_to_mime.emplace(".7z"_sv, "application/x-7z-compressed"_sv));
}
auto it = extension_to_mime.find(extension);
if (it == extension_to_mime.end())
return "application/octet-stream"_sv;
return it->value;
}

View File

@@ -0,0 +1,49 @@
#pragma once
#include <BAN/ByteSpan.h>
#include <BAN/HashMap.h>
#include <BAN/IPv4.h>
#include <BAN/String.h>
#include <BAN/StringView.h>
#include <BAN/Vector.h>
struct HTTPHeader
{
BAN::StringView name;
BAN::StringView value;
};
struct HTTPRequest
{
BAN::StringView method;
BAN::StringView path;
BAN::StringView version;
BAN::Vector<HTTPHeader> headers;
BAN::ConstByteSpan body;
};
class HTTPServer
{
public:
HTTPServer();
~HTTPServer();
BAN::ErrorOr<void> initialize(BAN::StringView root, BAN::IPv4Address ip, int port);
void start();
BAN::StringView web_root() const { return m_web_root.sv(); }
private:
BAN::ErrorOr<HTTPRequest> get_http_request(BAN::Vector<uint8_t>& data);
BAN::ErrorOr<void> send_http_response(int fd, unsigned status, BAN::ConstByteSpan, BAN::StringView mime);
BAN::ErrorOr<unsigned> handle_request(int fd, BAN::Vector<uint8_t>& data);
// Returns false if the connection should be closed
bool handle_all_requests(int fd, BAN::Vector<uint8_t>& data);
private:
BAN::String m_web_root;
int m_listen_socket { -1 };
BAN::HashMap<int, BAN::Vector<uint8_t>> m_client_data;
};

View File

@@ -0,0 +1,73 @@
#include "HTTPServer.h"
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
int usage(const char* argv0, int ret)
{
FILE* fout = (ret == 0) ? stdout : stderr;
fprintf(fout, "usage: %s [OPTIONS]...\n", argv0);
fprintf(fout, " -h, --help show this message and exit\n");
fprintf(fout, " -r, --root <PATH> web root directory\n");
fprintf(fout, " -b, --bind <IPv4> local address to bind\n");
fprintf(fout, " -p, --port <PORT> local port to bind\n");
return ret;
}
int main(int argc, char** argv)
{
BAN::StringView root = "/var/www"_sv;
BAN::IPv4Address bind = INADDR_ANY;
uint16_t port = 80;
for (int i = 1; i < argc; i++)
{
if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--root") == 0)
{
if (i + 1 >= argc)
return usage(argv[0], 1);
root = argv[i + 1];
i++;
}
else if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--bind") == 0)
{
if (i + 1 >= argc)
return usage(argv[0], 1);
bind = inet_addr(argv[i + 1]);
if (bind.raw == (in_addr_t)(-1))
return usage(argv[0], 1);
i++;
}
else if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0)
{
if (i + 1 >= argc)
return usage(argv[0], 1);
char* end = NULL;
errno = 0;
unsigned long value = strtoul(argv[i + 1], &end, 10);
if (*end || value > 0xFFFF || errno)
return usage(argv[0], 1);
port = value;
i++;
}
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
{
return usage(argv[0], 0);
}
else
{
return usage(argv[0], 1);
}
}
HTTPServer server;
if (auto ret = server.initialize(root, bind, port); ret.is_error())
{
fprintf(stderr, "Could not initialize server: %s\n", strerror(ret.error().get_error_code()));
return 1;
}
BAN::Formatter::println(putchar, "Server started on {}:{} at {}", bind, port, server.web_root());
server.start();
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(id ${SOURCES})
banan_include_headers(id ban)
banan_link_library(id libc)
install(TARGETS id OPTIONAL)

View File

@@ -0,0 +1,36 @@
#include <stdio.h>
#include <unistd.h>
#include <pwd.h>
int main()
{
uid_t uid = getuid();
uid_t euid = geteuid();
gid_t gid = getgid();
gid_t egid = getegid();
passwd* pw_uid = getpwuid(uid);
if (pw_uid == nullptr)
{
fprintf(stderr, "Unknown user #%d\n", uid);
return 1;
}
passwd* pw_euid = getpwuid(euid);
if (pw_euid == nullptr)
{
fprintf(stderr, "Unknown user #%d\n", euid);
return 1;
}
printf("uid=%u(%s)", uid, pw_uid->pw_name);
if (uid != euid)
printf(",euid=%u(%s)",euid, pw_euid->pw_name);
printf(" gid=%u", gid);
if (gid != egid)
printf(",egid=%u", egid);
printf("\n");
}

View File

@@ -0,0 +1,10 @@
set(SOURCES
main.cpp
)
add_executable(image ${SOURCES})
banan_include_headers(image ban)
banan_link_library(image libc)
banan_link_library(image libimage)
install(TARGETS image OPTIONAL)

View File

@@ -0,0 +1,110 @@
#include <LibImage/Image.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/framebuffer.h>
#include <sys/mman.h>
#include <unistd.h>
void render_to_framebuffer(BAN::UniqPtr<LibImage::Image> image, bool scale)
{
int fd = open("/dev/fb0", O_RDWR);
if (fd == -1)
{
perror("open");
exit(1);
}
framebuffer_info_t fb_info;
if (pread(fd, &fb_info, sizeof(fb_info), -1) == -1)
{
perror("pread");
exit(1);
}
if (scale)
image = MUST(image->resize(fb_info.width, fb_info.height));
ASSERT(BANAN_FB_BPP == 24 || BANAN_FB_BPP == 32);
size_t mmap_size = fb_info.height * fb_info.width * BANAN_FB_BPP / 8;
void* mmap_addr = mmap(nullptr, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (mmap_addr == MAP_FAILED)
{
perror("mmap");
exit(1);
}
uint8_t* u8_fb = reinterpret_cast<uint8_t*>(mmap_addr);
const auto& bitmap = image->bitmap();
for (uint64_t y = 0; y < BAN::Math::min<uint64_t>(image->height(), fb_info.height); y++)
{
for (uint64_t x = 0; x < BAN::Math::min<uint64_t>(image->width(), fb_info.width); x++)
{
u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 0] = bitmap[y * image->width() + x].b;
u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 1] = bitmap[y * image->width() + x].g;
u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 2] = bitmap[y * image->width() + x].r;
if constexpr(BANAN_FB_BPP == 32)
u8_fb[(y * fb_info.width + x) * BANAN_FB_BPP / 8 + 3] = bitmap[y * image->width() + x].a;
}
}
if (msync(mmap_addr, mmap_size, MS_SYNC) == -1)
{
perror("msync");
exit(1);
}
munmap(mmap_addr, mmap_size);
close(fd);
}
int usage(char* arg0, int ret)
{
FILE* out = (ret == 0) ? stdout : stderr;
fprintf(out, "usage: %s [options]... IMAGE_PATH\n", arg0);
fprintf(out, "options:\n");
fprintf(out, " -h, --help: show this message and exit\n");
fprintf(out, " -s, --scale: scale image to framebuffer size\n");
return ret;
}
int main(int argc, char** argv)
{
if (argc < 2)
return usage(argv[0], 1);
bool scale = false;
for (int i = 1; i < argc - 1; i++)
{
if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--scale") == 0)
scale = true;
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
return usage(argv[0], 0);
else
return usage(argv[0], 1);
}
auto image_path = BAN::StringView(argv[argc - 1]);
auto image_or_error = LibImage::Image::load_from_file(image_path);
if (image_or_error.is_error())
{
fprintf(stderr, "Could not load image '%.*s': %s\n",
(int)image_path.size(),
image_path.data(),
strerror(image_or_error.error().get_error_code())
);
return 1;
}
render_to_framebuffer(image_or_error.release_value(), scale);
for (;;)
sleep(1);
return 0;
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(init ${SOURCES})
banan_include_headers(init ban)
banan_link_library(init libc)
install(TARGETS init OPTIONAL)

View File

@@ -0,0 +1,138 @@
#include <BAN/String.h>
#include <BAN/Optional.h>
#include <BAN/Vector.h>
#include <ctype.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/banan-os.h>
#include <termios.h>
void initialize_stdio()
{
const char* tty = "/dev/tty";
if (open(tty, O_RDONLY | O_TTY_INIT) != 0) _exit(1);
if (open(tty, O_WRONLY) != 1) _exit(1);
if (open(tty, O_WRONLY) != 2) _exit(1);
if (open("/dev/debug", O_WRONLY) != 3) _exit(1);
}
int main()
{
initialize_stdio();
if (signal(SIGINT, [](int) {}) == SIG_ERR)
perror("signal");
if (load_keymap("/usr/share/keymaps/us.keymap") == -1)
perror("load_keymap");
if (fork() == 0)
{
execl("/bin/dhcp-client", "dhcp-client", NULL);
exit(1);
}
if (fork() == 0)
{
execl("/bin/resolver", "resolver", NULL);
exit(1);
}
bool first = true;
termios termios;
tcgetattr(STDIN_FILENO, &termios);
while (true)
{
tcsetattr(STDIN_FILENO, TCSANOW, &termios);
char name_buffer[128];
while (!first)
{
printf("username: ");
fflush(stdout);
ssize_t nread = read(STDIN_FILENO, name_buffer, sizeof(name_buffer) - 1);
if (nread == -1)
{
perror("read");
return 1;
}
if (nread <= 1 || name_buffer[nread - 1] != '\n')
continue;
name_buffer[nread - 1] = '\0';
break;
}
if (first)
{
strcpy(name_buffer, "user");
first = false;
}
auto* pwd = getpwnam(name_buffer);
if (pwd == nullptr)
continue;
if (chown("/dev/tty", pwd->pw_uid, pwd->pw_gid) == -1)
{
perror("chown");
continue;
}
if (chmod("/dev/tty", 0600) == -1)
{
perror("chmod");
continue;
}
pid_t pid = fork();
if (pid == 0)
{
pid_t pgrp = setpgrp();
if (tcsetpgrp(0, pgrp) == -1)
{
perror("tcsetpgrp");
exit(1);
}
printf("Welcome back %s!\n", pwd->pw_name);
if (setgid(pwd->pw_gid) == -1)
perror("setgid");
if (setuid(pwd->pw_uid) == -1)
perror("setuid");
setenv("PATH", "/bin:/usr/bin", 0);
setenv("HOME", pwd->pw_dir, 1);
chdir(pwd->pw_dir);
execl(pwd->pw_shell, pwd->pw_shell, nullptr);
perror("execl");
exit(1);
}
endpwent();
if (pid == -1)
{
perror("fork");
break;
}
int status;
waitpid(pid, &status, 0);
if (tcsetpgrp(0, getpgrp()) == -1)
perror("tcsetpgrp");
}
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(loadfont ${SOURCES})
banan_link_library(loadfont ban)
banan_link_library(loadfont libc)
install(TARGETS loadfont)

View File

@@ -0,0 +1,30 @@
#include <stdio.h>
#include <stropts.h>
#include <unistd.h>
int usage(int ret, const char* argv0)
{
FILE* fout = ret ? stderr : stdout;
fprintf(fout, "usage: %s FILE\n", argv0);
return ret;
}
int main(int argc, char** argv)
{
if (argc != 2)
return usage(1, argv[0]);
if (!isatty(STDOUT_FILENO))
{
fprintf(stderr, "stdout is not tty\n");
return 1;
}
if (ioctl(STDOUT_FILENO, KD_LOADFONT, argv[1]) == -1)
{
perror("ioctl");
return 1;
}
return 0;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(loadkeys ${SOURCES})
banan_link_library(loadkeys libc)
install(TARGETS loadkeys OPTIONAL)

View File

@@ -0,0 +1,42 @@
#include <stdio.h>
#include <string.h>
#include <sys/banan-os.h>
#include <sys/stat.h>
int try_load_keymap(const char* path)
{
if (load_keymap(path) == -1)
{
perror("load_keymap");
return 1;
}
return 0;
}
int main(int argc, char** argv)
{
if (argc != 2)
{
fprintf(stderr, "usage: %s KEYMAP\n", argv[0]);
return 1;
}
struct stat st;
if (stat(argv[1], &st) == 0)
return try_load_keymap(argv[1]);
char buffer[128];
strcpy(buffer, "/usr/share/keymaps/");
strcat(buffer, argv[1]);
if (stat(buffer, &st) == 0)
return try_load_keymap(buffer);
strcat(buffer, ".keymap");
if (stat(buffer, &st) == 0)
return try_load_keymap(buffer);
fprintf(stderr, "Keymap '%s' not found\n", argv[1]);
return 1;
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(ls ${SOURCES})
banan_link_library(ls ban)
banan_link_library(ls libc)
install(TARGETS ls OPTIONAL)

View File

@@ -0,0 +1,315 @@
#include <BAN/Sort.h>
#include <BAN/String.h>
#include <BAN/Time.h>
#include <BAN/Vector.h>
#include <dirent.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <sys/stat.h>
struct config_t
{
bool list = false;
bool all = false;
bool directory = false;
};
struct simple_entry_t
{
BAN::String name;
struct stat st;
};
struct full_entry_t
{
BAN::String access;
BAN::String hard_links;
BAN::String owner_name;
BAN::String owner_group;
BAN::String size;
BAN::String month;
BAN::String day;
BAN::String time;
BAN::String full_name;
};
const char* entry_color(mode_t mode)
{
// TODO: handle suid, sgid, sticky
if (S_ISFIFO(mode) || S_ISCHR(mode) || S_ISBLK(mode))
return "\e[33m";
if (S_ISDIR(mode))
return "\e[34m";
if (S_ISSOCK(mode))
return "\e[35m";
if (S_ISLNK(mode))
return "\e[36m";
if (mode & (S_IXUSR | S_IXGRP | S_IXOTH))
return "\e[32m";
return "\e[0m";
}
BAN::String build_access_string(mode_t mode)
{
BAN::String access;
MUST(access.resize(10));
access[0] = S_ISBLK(mode) ? 'b' : S_ISCHR(mode) ? 'c' : S_ISDIR(mode) ? 'd' : S_ISFIFO(mode) ? 'f' : S_ISLNK(mode) ? 'l' : S_ISSOCK(mode) ? 's' : '-';
access[1] = (mode & S_IRUSR) ? 'r' : '-';
access[2] = (mode & S_IWUSR) ? 'w' : '-';
access[3] = (mode & S_ISUID) ? ((mode & S_IXUSR) ? 's' : 'S') : (mode & S_IXUSR) ? 'x' : '-';
access[4] = (mode & S_IRGRP) ? 'r' : '-';
access[5] = (mode & S_IWGRP) ? 'w' : '-';
access[6] = (mode & S_ISGID) ? ((mode & S_IXGRP) ? 's' : 'S') : (mode & S_IXGRP) ? 'x' : '-';
access[7] = (mode & S_IROTH) ? 'r' : '-';
access[8] = (mode & S_IWOTH) ? 'w' : '-';
access[9] = (mode & S_ISVTX) ? ((mode & S_IXOTH) ? 't' : 'T') : (mode & S_IXOTH) ? 'x' : '-';
return access;
}
BAN::String build_hard_links_string(nlink_t links)
{
return MUST(BAN::String::formatted("{}", links));
}
BAN::String build_owner_name_string(uid_t uid)
{
struct passwd* passwd = getpwuid(uid);
if (passwd == nullptr)
return MUST(BAN::String::formatted("{}", uid));
return BAN::String(BAN::StringView(passwd->pw_name));
}
BAN::String build_owner_group_string(gid_t gid)
{
struct group* grp = getgrgid(gid);
if (grp == nullptr)
return MUST(BAN::String::formatted("{}", gid));
return BAN::String(BAN::StringView(grp->gr_name));
}
BAN::String build_size_string(off_t size)
{
return MUST(BAN::String::formatted("{}", size));
}
BAN::String build_month_string(BAN::Time time)
{
static const char* months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
return BAN::String(BAN::StringView(months[(time.month - 1) % 12]));
}
BAN::String build_day_string(BAN::Time time)
{
return MUST(BAN::String::formatted("{}", time.day));
}
BAN::String build_time_string(BAN::Time time)
{
static uint32_t current_year = ({ timespec real_time; clock_gettime(CLOCK_REALTIME, &real_time); BAN::from_unix_time(real_time.tv_sec).year; });
if (time.year != current_year)
return MUST(BAN::String::formatted("{}", time.year));
return MUST(BAN::String::formatted("{2}:{2}", time.hour, time.minute));
}
int list_directory(const BAN::String& path, config_t config)
{
BAN::Vector<simple_entry_t> entries;
struct stat st;
auto stat_func = config.directory ? lstat : stat;
if (stat_func(path.data(), &st) == -1)
{
perror("stat");
return 2;
}
int ret = 0;
if (!S_ISDIR(st.st_mode))
MUST(entries.emplace_back(path, st));
else
{
DIR* dirp = opendir(path.data());
if (dirp == NULL)
{
perror("opendir");
return 2;
}
struct dirent* dirent;
while ((dirent = readdir(dirp)))
{
if (!config.all && dirent->d_name[0] == '.')
continue;
if (fstatat(dirfd(dirp), dirent->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1)
{
perror("fstatat");
ret = 1;
continue;
}
MUST(entries.emplace_back(BAN::StringView(dirent->d_name), st));
}
closedir(dirp);
}
BAN::sort::sort(entries.begin(), entries.end(),
[](const simple_entry_t& lhs, const simple_entry_t& rhs)
{
// sort directories first
bool lhs_isdir = S_ISDIR(lhs.st.st_mode);
bool rhs_isdir = S_ISDIR(rhs.st.st_mode);
if (lhs_isdir != rhs_isdir)
return lhs_isdir;
// sort by name
for (size_t i = 0; i < BAN::Math::min(lhs.name.size(), rhs.name.size()); i++)
if (lhs.name[i] != rhs.name[i])
return lhs.name[i] < rhs.name[i];
return lhs.name.size() < rhs.name.size();
}
);
if (!config.list)
{
for (size_t i = 0; i < entries.size(); i++)
{
if (i > 0)
printf(" ");
printf("%s%s\e[m", entry_color(entries[i].st.st_mode), entries[i].name.data());
}
printf("\n");
return ret;
}
BAN::Vector<full_entry_t> full_entries;
MUST(full_entries.reserve(entries.size()));
full_entry_t max_entry;
for (const simple_entry_t& entry : entries)
{
full_entry_t full_entry;
#define GET_ENTRY_STRING(property, input) \
full_entry.property = build_ ## property ## _string(input); \
if (full_entry.property.size() > max_entry.property.size()) \
max_entry.property = full_entry.property;
GET_ENTRY_STRING(access, entry.st.st_mode);
GET_ENTRY_STRING(hard_links, entry.st.st_nlink);
GET_ENTRY_STRING(owner_name, entry.st.st_uid);
GET_ENTRY_STRING(owner_group, entry.st.st_gid);
GET_ENTRY_STRING(size, entry.st.st_size);
BAN::Time time = BAN::from_unix_time(entry.st.st_mtim.tv_sec);
GET_ENTRY_STRING(month, time);
GET_ENTRY_STRING(day, time);
GET_ENTRY_STRING(time, time);
full_entry.full_name = MUST(BAN::String::formatted("{}{}\e[m", entry_color(entry.st.st_mode), entry.name));
MUST(full_entries.push_back(BAN::move(full_entry)));
}
for (const auto& full_entry : full_entries)
printf("%*s %*s %*s %*s %*s %*s %*s %*s %s\n",
(int)max_entry.access.size(), full_entry.access.data(),
(int)max_entry.hard_links.size(), full_entry.hard_links.data(),
(int)max_entry.owner_name.size(), full_entry.owner_name.data(),
(int)max_entry.owner_group.size(), full_entry.owner_group.data(),
(int)max_entry.size.size(), full_entry.size.data(),
(int)max_entry.month.size(), full_entry.month.data(),
(int)max_entry.day.size(), full_entry.day.data(),
(int)max_entry.time.size(), full_entry.time.data(),
full_entry.full_name.data()
);
return ret;
}
int usage(const char* argv0, int ret)
{
FILE* fout = ret ? stderr : stdout;
fprintf(fout, "usage: %s [OPTION]... [FILE]...\n", argv0);
fprintf(fout, " -a, --all show hidden files\n");
fprintf(fout, " -l, --list use list format\n");
fprintf(fout, " -d, --directory show directories as directories, don't list their contents\n");
fprintf(fout, " -h, --help show this message and exit\n");
return ret;
}
int main(int argc, const char* argv[])
{
config_t config;
int i = 1;
for (; i < argc; i++)
{
if (argv[i][0] != '-')
break;
if (argv[i][1] == '\0')
break;
if (argv[i][1] == '-')
{
if (strcmp(argv[i], "--help") == 0)
return usage(argv[0], 0);
else if (strcmp(argv[i], "--all") == 0)
config.all = true;
else if (strcmp(argv[i], "--list") == 0)
config.list = true;
else if (strcmp(argv[i], "--directory") == 0)
config.directory = true;
else
{
fprintf(stderr, "unrecognized option '%s'\n", argv[i]);
return usage(argv[0], 2);
}
}
else
{
for (size_t j = 1; argv[i][j]; j++)
{
if (argv[i][j] == 'h')
return usage(argv[0], 0);
else if (argv[i][j] == 'a')
config.all = true;
else if (argv[i][j] == 'l')
config.list = true;
else if (argv[i][j] == 'd')
config.directory = true;
else
{
fprintf(stderr, "unrecognized option '%c'\n", argv[i][j]);
return usage(argv[0], 2);
}
}
}
}
BAN::Vector<BAN::String> files;
if (i == argc)
MUST(files.emplace_back("."_sv));
else for (; i < argc; i++)
MUST(files.emplace_back(BAN::StringView(argv[i])));
int ret = 0;
for (size_t i = 0; i < files.size(); i++)
{
if (i > 0)
printf("\n");
if (files.size() > 1)
printf("%s:\n", files[i].data());
ret = BAN::Math::max(ret, list_directory(files[i], config));
}
return ret;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(meminfo ${SOURCES})
banan_link_library(meminfo libc)
install(TARGETS meminfo OPTIONAL)

View File

@@ -0,0 +1,99 @@
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/banan-os.h>
bool is_only_digits(const char* str)
{
while (*str)
if (!isdigit(*str++))
return false;
return true;
}
int main()
{
DIR* proc = opendir("/proc");
if (proc == nullptr)
{
perror("opendir");
return 1;
}
char path_buffer[128] {};
while (dirent* proc_ent = readdir(proc))
{
if (proc_ent->d_type != DT_DIR)
continue;
if (!is_only_digits(proc_ent->d_name))
continue;
{
strcpy(path_buffer, proc_ent->d_name);
strcat(path_buffer, "/cmdline");
int fd = openat(dirfd(proc), path_buffer, O_RDONLY);
if (fd == -1)
{
if (errno != EACCES)
perror("openat");
continue;
}
printf("process: ");
while (ssize_t nread = read(fd, path_buffer, sizeof(path_buffer) - 1))
{
if (nread < 0)
{
perror("read");
break;
}
for (int i = 0; i < nread; i++)
if (path_buffer[i] == '\0')
path_buffer[i] = ' ';
path_buffer[nread] = '\0';
int written = 0;
while (written < nread)
written += printf("%s ", path_buffer + written);
}
printf("\n");
close(fd);
}
printf(" pid: %s\n", proc_ent->d_name);
{
strcpy(path_buffer, proc_ent->d_name);
strcat(path_buffer, "/meminfo");
int fd = openat(dirfd(proc), path_buffer, O_RDONLY);
if (fd == -1)
{
perror("openat");
continue;
}
proc_meminfo_t meminfo;
if (read(fd, &meminfo, sizeof(meminfo)) == -1)
perror("read");
else
{
size_t percent_times_100 = 10000 * meminfo.phys_pages / meminfo.virt_pages;
printf(" vmem: %zu pages (%zu bytes)\n", meminfo.virt_pages, meminfo.page_size * meminfo.virt_pages);
printf(" pmem: %zu pages (%zu bytes) %zu.%02zu%%\n", meminfo.phys_pages, meminfo.page_size * meminfo.phys_pages, percent_times_100 / 100, percent_times_100 % 100);
}
close(fd);
}
}
closedir(proc);
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(mkdir ${SOURCES})
banan_link_library(mkdir libc)
install(TARGETS mkdir OPTIONAL)

View File

@@ -0,0 +1,23 @@
#include <stdio.h>
#include <sys/stat.h>
int main(int argc, char** argv)
{
if (argc <= 1)
{
fprintf(stderr, "Missing operand\n");
return 1;
}
int ret = 0;
for (int i = 1; i < argc; i++)
{
if (mkdir(argv[i], 0755) == -1)
{
perror("mkdir");
ret = 1;
}
}
return ret;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(nslookup ${SOURCES})
banan_link_library(nslookup libc)
install(TARGETS nslookup OPTIONAL)

View File

@@ -0,0 +1,54 @@
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define MAX(a, b) ((a) < (b) ? (b) : (a))
int main(int argc, char** argv)
{
if (argc != 2)
{
fprintf(stderr, "usage: %s DOMAIN\n", argv[0]);
return 1;
}
int socket = ::socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (socket == -1)
{
perror("socket");
return 1;
}
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/resolver.sock");
if (connect(socket, (sockaddr*)&addr, sizeof(addr)) == -1)
{
perror("connect");
return 1;
}
if (send(socket, argv[1], strlen(argv[1]), 0) == -1)
{
perror("send");
return 1;
}
sockaddr_storage storage;
if (recv(socket, &storage, sizeof(storage), 0) == -1)
{
perror("recv");
return 1;
}
close(socket);
char buffer[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
printf("%s\n", inet_ntop(storage.ss_family, storage.ss_storage, buffer, sizeof(buffer)));
return 0;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(poweroff ${SOURCES})
banan_link_library(poweroff libc)
install(TARGETS poweroff OPTIONAL)

View File

@@ -0,0 +1,38 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/banan-os.h>
void usage(int ret, char* arg0)
{
FILE* fout = (ret == 0) ? stdout : stderr;
fprintf(fout, "usage: %s [OPTIONS]...\n", arg0);
fprintf(fout, " -s, --shutdown Shutdown the system (default)\n");
fprintf(fout, " -r, --reboot Reboot the system\n");
fprintf(fout, " -h, --help Show this message\n");
exit(ret);
}
int main(int argc, char** argv)
{
int operation = POWEROFF_SHUTDOWN;
for (int i = 1; i < argc; i++)
{
if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--shutdown") == 0)
operation = POWEROFF_SHUTDOWN;
else if (strcmp(argv[i], "-r") == 0 || strcmp(argv[i], "--reboot") == 0)
operation = POWEROFF_REBOOT;
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
usage(0, argv[0]);
else
usage(1, argv[0]);
}
if (poweroff(operation) == -1)
{
perror("poweroff");
return 1;
}
return 0;
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(resolver ${SOURCES})
banan_link_library(resolver ban)
banan_link_library(resolver libc)
install(TARGETS resolver OPTIONAL)

View File

@@ -0,0 +1,338 @@
#include <BAN/ByteSpan.h>
#include <BAN/Debug.h>
#include <BAN/Endianness.h>
#include <BAN/HashMap.h>
#include <BAN/IPv4.h>
#include <BAN/String.h>
#include <BAN/StringView.h>
#include <BAN/Vector.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
struct DNSPacket
{
BAN::NetworkEndian<uint16_t> identification { 0 };
BAN::NetworkEndian<uint16_t> flags { 0 };
BAN::NetworkEndian<uint16_t> question_count { 0 };
BAN::NetworkEndian<uint16_t> answer_count { 0 };
BAN::NetworkEndian<uint16_t> authority_RR_count { 0 };
BAN::NetworkEndian<uint16_t> additional_RR_count { 0 };
uint8_t data[];
};
static_assert(sizeof(DNSPacket) == 12);
struct DNSAnswer
{
uint8_t __storage[12];
BAN::NetworkEndian<uint16_t>& name() { return *reinterpret_cast<BAN::NetworkEndian<uint16_t>*>(__storage + 0x00); };
BAN::NetworkEndian<uint16_t>& type() { return *reinterpret_cast<BAN::NetworkEndian<uint16_t>*>(__storage + 0x02); };
BAN::NetworkEndian<uint16_t>& class_() { return *reinterpret_cast<BAN::NetworkEndian<uint16_t>*>(__storage + 0x04); };
BAN::NetworkEndian<uint32_t>& ttl() { return *reinterpret_cast<BAN::NetworkEndian<uint32_t>*>(__storage + 0x06); };
BAN::NetworkEndian<uint16_t>& data_len() { return *reinterpret_cast<BAN::NetworkEndian<uint16_t>*>(__storage + 0x0A); };
uint8_t data[];
};
static_assert(sizeof(DNSAnswer) == 12);
enum QTYPE : uint16_t
{
A = 0x0001,
CNAME = 0x0005,
AAAA = 0x001C,
};
struct DNSEntry
{
time_t valid_until { 0 };
BAN::IPv4Address address { 0 };
};
struct DNSResponse
{
uint16_t id;
DNSEntry entry;
};
bool send_dns_query(int socket, BAN::StringView domain, uint16_t id)
{
static uint8_t buffer[4096];
memset(buffer, 0, sizeof(buffer));
DNSPacket& request = *reinterpret_cast<DNSPacket*>(buffer);
request.identification = id;
request.flags = 0x0100;
request.question_count = 1;
size_t idx = 0;
auto labels = MUST(BAN::StringView(domain).split('.'));
for (auto label : labels)
{
ASSERT(label.size() <= 0xFF);
request.data[idx++] = label.size();
for (char c : label)
request.data[idx++] = c;
}
request.data[idx++] = 0x00;
*(uint16_t*)&request.data[idx] = htons(QTYPE::A); idx += 2;
*(uint16_t*)&request.data[idx] = htons(0x0001); idx += 2;
sockaddr_in nameserver;
nameserver.sin_family = AF_INET;
nameserver.sin_port = htons(53);
nameserver.sin_addr.s_addr = inet_addr("8.8.8.8");
if (sendto(socket, &request, sizeof(DNSPacket) + idx, 0, (sockaddr*)&nameserver, sizeof(nameserver)) == -1)
{
dprintln("sendto: {}", strerror(errno));
return false;
}
return true;
}
BAN::Optional<DNSResponse> read_dns_response(int socket)
{
static uint8_t buffer[4096];
ssize_t nrecv = recvfrom(socket, buffer, sizeof(buffer), 0, nullptr, nullptr);
if (nrecv == -1)
{
dprintln("recvfrom: {}", strerror(errno));
return {};
}
DNSPacket& reply = *reinterpret_cast<DNSPacket*>(buffer);
if (reply.flags & 0x0F)
{
dprintln("DNS error (rcode {})", (unsigned)(reply.flags & 0xF));
return {};
}
size_t idx = 0;
for (size_t i = 0; i < reply.question_count; i++)
{
while (reply.data[idx])
idx += reply.data[idx] + 1;
idx += 5;
}
DNSAnswer& answer = *reinterpret_cast<DNSAnswer*>(&reply.data[idx]);
if (answer.type() != QTYPE::A)
{
dprintln("Not A record");
return {};
}
if (answer.data_len() != 4)
{
dprintln("corrupted package");
return {};
}
DNSResponse result;
result.id = reply.identification;
result.entry.valid_until = time(nullptr) + answer.ttl();
result.entry.address = BAN::IPv4Address(*reinterpret_cast<uint32_t*>(answer.data));
return result;
}
int create_service_socket()
{
int socket = ::socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (socket == -1)
{
dprintln("socket: {}", strerror(errno));
return -1;
}
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/resolver.sock");
if (bind(socket, (sockaddr*)&addr, sizeof(addr)) == -1)
{
dprintln("bind: {}", strerror(errno));
close(socket);
return -1;
}
if (chmod("/tmp/resolver.sock", 0777) == -1)
{
dprintln("chmod: {}", strerror(errno));
close(socket);
return -1;
}
if (listen(socket, 10) == -1)
{
dprintln("listen: {}", strerror(errno));
close(socket);
return -1;
}
return socket;
}
BAN::Optional<BAN::String> read_service_query(int socket)
{
static char buffer[4096];
ssize_t nrecv = recv(socket, buffer, sizeof(buffer), 0);
if (nrecv == -1)
{
dprintln("recv: {}", strerror(errno));
return {};
}
buffer[nrecv] = '\0';
return BAN::String(buffer);
}
int main(int, char**)
{
srand(time(nullptr));
int service_socket = create_service_socket();
if (service_socket == -1)
return 1;
int dns_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (dns_socket == -1)
{
dprintln("socket: {}", strerror(errno));
return 1;
}
BAN::HashMap<BAN::String, DNSEntry> dns_cache;
struct Client
{
Client(int socket)
: socket(socket)
{ }
const int socket;
bool close { false };
uint16_t query_id { 0 };
BAN::String query;
};
BAN::LinkedList<Client> clients;
for (;;)
{
int max_sock = BAN::Math::max(service_socket, dns_socket);
fd_set fds;
FD_ZERO(&fds);
FD_SET(service_socket, &fds);
FD_SET(dns_socket, &fds);
for (auto& client : clients)
{
FD_SET(client.socket, &fds);
max_sock = BAN::Math::max(max_sock, client.socket);
}
int nselect = select(max_sock + 1, &fds, nullptr, nullptr, nullptr);
if (nselect == -1)
{
perror("select");
continue;
}
if (FD_ISSET(service_socket, &fds))
{
int client = accept(service_socket, nullptr, nullptr);
if (client == -1)
{
perror("accept");
continue;
}
MUST(clients.emplace_back(client));
}
if (FD_ISSET(dns_socket, &fds))
{
auto result = read_dns_response(dns_socket);
if (!result.has_value())
continue;
for (auto& client : clients)
{
if (client.query_id != result->id)
continue;
(void)dns_cache.insert(client.query, result->entry);
sockaddr_storage storage;
storage.ss_family = AF_INET;
memcpy(storage.ss_storage, &result->entry.address.raw, sizeof(result->entry.address.raw));
if (send(client.socket, &storage, sizeof(storage), 0) == -1)
dprintln("send: {}", strerror(errno));
client.close = true;
break;
}
}
for (auto& client : clients)
{
if (!FD_ISSET(client.socket, &fds))
continue;
if (!client.query.empty())
{
dprintln("Client already has a query");
continue;
}
auto query = read_service_query(client.socket);
if (!query.has_value())
continue;
BAN::Optional<DNSEntry> result;
if (dns_cache.contains(*query))
{
auto& cached = dns_cache[*query];
if (time(nullptr) <= cached.valid_until)
result = cached;
else
dns_cache.remove(*query);
}
if (result.has_value())
{
sockaddr_storage storage;
storage.ss_family = AF_INET;
memcpy(storage.ss_storage, &result->address.raw, sizeof(result->address.raw));
if (send(client.socket, &storage, sizeof(storage), 0) == -1)
dprintln("send: {}", strerror(errno));
client.close = true;
continue;
}
client.query = query.release_value();
client.query_id = rand() % 0xFFFF;
send_dns_query(dns_socket, client.query, client.query_id);
}
for (auto it = clients.begin(); it != clients.end();)
{
if (!it->close)
{
it++;
continue;
}
close(it->socket);
it = clients.remove(it);
}
}
return 0;
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(rm ${SOURCES})
banan_include_headers(rm ban)
banan_link_library(rm libc)
install(TARGETS rm OPTIONAL)

View File

@@ -0,0 +1,131 @@
#include <BAN/String.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
bool delete_recursive(const char* path)
{
struct stat st;
if (stat(path, &st) == -1)
{
perror(path);
return false;
}
bool ret = true;
if (S_ISDIR(st.st_mode))
{
DIR* dir = opendir(path);
while (struct dirent* dirent = readdir(dir))
{
if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
continue;
BAN::String dirent_path;
MUST(dirent_path.append(path));
MUST(dirent_path.push_back('/'));
MUST(dirent_path.append(dirent->d_name));
if (dirent->d_type == DT_DIR)
{
if (!delete_recursive(dirent_path.data()))
ret = false;
}
else
{
if (unlink(dirent_path.data()) == -1)
{
perror(dirent_path.data());
ret = false;
}
}
}
closedir(dir);
}
if (remove(path) == -1)
{
perror(path);
return false;
}
return ret;
}
void usage(const char* argv0, int ret)
{
FILE* out = (ret == 0) ? stdout : stderr;
fprintf(out, "usage: %s [OPTIONS]... FILE...\n", argv0);
fprintf(out, " remove each FILE\n");
fprintf(out, "OPTIONS:\n");
fprintf(out, " -r remove directories and their contents recursively\n");
fprintf(out, " -h, --help show this message and exit\n");
exit(ret);
}
int main(int argc, char** argv)
{
bool recursive = false;
int i = 1;
for (; i < argc; i++)
{
if (argv[i][0] != '-')
break;
if (strcmp(argv[i], "-r") == 0)
recursive = true;
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
usage(argv[0], 0);
else
{
fprintf(stderr, "unrecognized argument %s. use --help for more information\n", argv[i]);
return 1;
}
}
if (i >= argc)
{
fprintf(stderr, "missing operand. use --help for more information\n");
return 1;
}
int ret = 0;
for (; i < argc; i++)
{
if (recursive)
{
if (!delete_recursive(argv[i]))
ret = 1;
}
else
{
struct stat st;
if (stat(argv[i], &st) == -1)
{
perror(argv[i]);
ret = 1;
continue;
}
if (S_ISDIR(st.st_mode))
{
fprintf(stderr, "%s: %s\n", argv[i], strerror(EISDIR));
ret = 1;
continue;
}
if (unlink(argv[i]) == -1)
{
perror(argv[i]);
ret = 1;
}
}
}
return ret;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(sleep ${SOURCES})
banan_link_library(sleep libc)
install(TARGETS sleep OPTIONAL)

View File

@@ -0,0 +1,38 @@
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int usage(char* argv0, int ret)
{
FILE* fp = (ret == 0) ? stdout : stderr;
fprintf(fp, "usage: %s SECONDS\n", argv0);
return ret;
}
int main(int argc, char** argv)
{
if (argc != 2)
return usage(argv[0], 1);
if (strlen(argv[1]) > 9)
{
fprintf(stderr, "SECONDS argument too large\n");
return usage(argv[0], 1);
}
int seconds = 0;
const char* ptr = argv[1];
while (*ptr)
{
if (!isdigit(*ptr))
{
fprintf(stderr, "invalid SECONDS argument\n");
return usage(argv[0], 1);
}
seconds = (seconds * 10) + (*ptr - '0');
ptr++;
}
sleep(seconds);
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(snake ${SOURCES})
banan_include_headers(snake ban)
banan_link_library(snake libc)
install(TARGETS snake OPTIONAL)

View File

@@ -0,0 +1,248 @@
#include <BAN/Vector.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
enum Direction
{
None,
Unknown,
Left,
Right,
Up,
Down,
};
struct Point
{
int x, y;
bool operator==(const Point& other) const { return x == other.x && y == other.y; }
};
bool g_running = true;
Point g_grid_size = { 21, 21 };
Direction g_direction = Direction::Up;
Point g_head = { 10, 10 };
size_t g_tail_target = 3;
int g_score = 0;
BAN::Vector<Point> g_tail;
Point g_apple;
Direction query_input()
{
char c;
if (read(STDIN_FILENO, &c, 1) != 1)
return Direction::None;
switch (c)
{
case 'w': case 'W':
return Direction::Up;
case 'a': case 'A':
return Direction::Left;
case 's': case 'S':
return Direction::Down;
case 'd': case 'D':
return Direction::Right;
default:
return Direction::Unknown;
}
}
void set_grid_tile(Point point, const char* str)
{
printf("\e[%d;%dH%s", (point.y + 1) + 1, (point.x + 1) * 2 + 1, str);
}
Point get_random_point()
{
return { .x = rand() % g_grid_size.x, .y = rand() % g_grid_size.y };
}
void update_apple()
{
regenerate:
g_apple = get_random_point();
if (g_head == g_apple)
goto regenerate;
for (auto point : g_tail)
if (point == g_apple)
goto regenerate;
set_grid_tile(g_apple, "\e[31mO");
}
void setup_grid()
{
// Move cursor to beginning and clear screen
printf("\e[H\e[J");
// Render top line
putchar('#');
for (int x = 1; x < g_grid_size.x + 2; x++)
printf(" #");
putchar('\n');
// Render side lines
for (int y = 0; y < g_grid_size.y; y++)
printf("#\e[%dC#\n", g_grid_size.x * 2 + 1);
// Render Bottom line
putchar('#');
for (int x = 1; x < g_grid_size.x + 2; x++)
printf(" #");
putchar('\n');
// Render snake head
set_grid_tile(g_head, "O");
// Generate and render apple
srand(time(0));
update_apple();
// Render score
printf("\e[%dH\e[mScore: %d", g_grid_size.y + 3, g_score);
fflush(stdout);
}
void update()
{
auto input = Direction::None;
auto new_direction = Direction::None;
while ((input = query_input()) != Direction::None)
{
switch (input)
{
case Direction::Up:
if (g_direction != Direction::Down)
new_direction = Direction::Up;
break;
case Direction::Down:
if (g_direction != Direction::Up)
new_direction = Direction::Down;
break;
case Direction::Left:
if (g_direction != Direction::Right)
new_direction = Direction::Left;
break;
case Direction::Right:
if (g_direction != Direction::Left)
new_direction = Direction::Right;
break;
default:
break;
}
}
if (new_direction != g_direction && new_direction != Direction::None)
g_direction = new_direction;
auto old_head = g_head;
switch (g_direction)
{
case Direction::Up:
g_head.y--;
break;
case Direction::Down:
g_head.y++;
break;
case Direction::Left:
g_head.x--;
break;
case Direction::Right:
g_head.x++;
break;
default:
ASSERT_NOT_REACHED();
}
if (g_head.x < 0 || g_head.y < 0 || g_head.x >= g_grid_size.x || g_head.y >= g_grid_size.y)
{
g_running = false;
return;
}
for (auto point : g_tail)
{
if (point == g_head)
{
g_running = false;
return;
}
}
MUST(g_tail.insert(0, old_head));
if (g_tail.size() > g_tail_target)
{
set_grid_tile(g_tail.back(), " ");
g_tail.pop_back();
}
if (g_head == g_apple)
{
g_tail_target++;
g_score++;
update_apple();
printf("\e[%dH\e[mScore: %d", g_grid_size.y + 3, g_score);
}
set_grid_tile(old_head, "\e[32mo");
set_grid_tile(g_head, "\e[32mO");
fflush(stdout);
}
int main()
{
// Make stdin non blocking
if (fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK))
{
perror("fcntl");
return 1;
}
// Set stdin mode to non-canonical
termios tcold, tcnew;
if (tcgetattr(STDIN_FILENO, &tcold) == -1)
{
perror("tcgetattr");
return 1;
}
tcnew = tcold;
tcnew.c_lflag &= ~(ECHO | ICANON);
if (tcsetattr(STDIN_FILENO, TCSANOW, &tcnew))
{
perror("tcsetattr");
return 1;
}
printf("\e[?25l");
setup_grid();
timespec delay;
delay.tv_sec = 0;
delay.tv_nsec = 100'000'000;
while (g_running)
{
nanosleep(&delay, nullptr);
update();
}
// Restore stdin mode
if (tcsetattr(STDIN_FILENO, TCSANOW, &tcold))
{
perror("tcsetattr");
return 1;
}
// Reset ansi state
printf("\e[m\e[?25h\e[%dH", g_grid_size.y + 4);
return 0;
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(stat ${SOURCES})
banan_link_library(stat ban)
banan_link_library(stat libc)
install(TARGETS stat OPTIONAL)

View File

@@ -0,0 +1,75 @@
#include <stdio.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <BAN/Time.h>
void print_timestamp(timespec ts)
{
auto time = BAN::from_unix_time(ts.tv_sec);
printf("%04d-%02d-%02d %02d:%02d:%02d.%09ld",
time.year, time.month, time.day,
time.hour, time.minute, time.second,
ts.tv_nsec
);
}
int main(int argc, char** argv)
{
for (int i = 1; i < argc; i++)
{
struct stat st;
if (stat(argv[i], &st) == -1)
{
perror("stat");
continue;
}
char access[11];
const char* type = nullptr;
if (S_ISBLK(st.st_mode)) {
access[0] = 'b';
type = "block special file";
} else if (S_ISCHR(st.st_mode)) {
access[0] = 'c';
type = "character special file";
} else if (S_ISDIR(st.st_mode)) {
access[0] = 'd';
type = "directory";
} else if (S_ISFIFO(st.st_mode)) {
access[0] = 'f';
type = "fifo";
} else if (S_ISREG(st.st_mode)) {
access[0] = '-';
type = "regular file";
} else if (S_ISLNK(st.st_mode)) {
access[0] = 'l';
type = "symbolic link";
} else if (S_ISSOCK(st.st_mode)) {
access[0] = 's';
type = "socket";
} else {
access[0] = '-';
type = "unknown";
}
access[1] = (st.st_mode & S_IRUSR) ? 'r' : '-';
access[2] = (st.st_mode & S_IWUSR) ? 'w' : '-';
access[3] = (st.st_mode & S_ISUID) ? ((st.st_mode & S_IXUSR) ? 's' : 'S') : (st.st_mode & S_IXUSR) ? 'x' : '-';
access[4] = (st.st_mode & S_IRGRP) ? 'r' : '-';
access[5] = (st.st_mode & S_IWGRP) ? 'w' : '-';
access[6] = (st.st_mode & S_ISGID) ? ((st.st_mode & S_IXGRP) ? 's' : 'S') : (st.st_mode & S_IXGRP) ? 'x' : '-';
access[7] = (st.st_mode & S_IROTH) ? 'r' : '-';
access[8] = (st.st_mode & S_IWOTH) ? 'w' : '-';
access[9] = (st.st_mode & S_ISVTX) ? ((st.st_mode & S_IXOTH) ? 't' : 'T') : (st.st_mode & S_IXOTH) ? 'x' : '-';
access[10] = '\0';
printf(" File: %s\n", argv[i]);
printf(" Size: %-15ld Blocks: %-10ld IO Block: %-6ld %s\n", st.st_size, st.st_blocks, st.st_blksize, type);
printf("Device: %u,%-5u Inode: %-11llu Links: %-5lu Device type: %u,%u\n", major(st.st_dev), minor(st.st_dev), st.st_ino, st.st_nlink, major(st.st_rdev), minor(st.st_rdev));
printf("Access: (%04o/%s) Uid: %5d Gid: %5d\n", st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX), access, st.st_uid, st.st_gid);
printf("Access: "); print_timestamp(st.st_atim); printf("\n");
printf("Modify: "); print_timestamp(st.st_mtim); printf("\n");
printf("Change: "); print_timestamp(st.st_ctim); printf("\n");
}
}

View File

@@ -0,0 +1,16 @@
set(SOURCES
main.cpp
)
add_executable(sudo ${SOURCES})
banan_link_library(sudo libc)
install(
TARGETS sudo
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
SETUID
OPTIONAL
)

View File

@@ -0,0 +1,31 @@
#include <stdio.h>
#include <unistd.h>
int usage(char* argv0, int ret)
{
FILE* fp = (ret == 0) ? stdout : stderr;
fprintf(fp, "usage: %s COMMAND [ARGUMENTS...]\n", argv0);
return ret;
}
int main(int argc, char** argv)
{
if (argc < 2)
return usage(argv[0], 1);
if (setuid(0) == -1)
{
perror("setuid");
return 1;
}
if (setgid(0) == -1)
{
perror("setgid");
return 1;
}
execvp(argv[1], argv + 1);
perror("execvp");
return 1;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(sync ${SOURCES})
banan_link_library(sync libc)
install(TARGETS sync OPTIONAL)

View File

@@ -0,0 +1,29 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void usage(int ret, char* cmd)
{
FILE* fout = (ret == 0) ? stdout : stderr;
fprintf(fout, "usage: %s [OPTION]...\n", cmd);
fprintf(fout, "Tells the kernel to start a disk sync as soon as possible\n");
fprintf(fout, " -b, --block return only after sync is complete\n");
fprintf(fout, " -h, --help show this message and exit\n");
exit(ret);
}
int main(int argc, char** argv)
{
bool block = false;
for (int i = 1; i < argc; i++)
{
if (strcmp(argv[i], "-b") == 0 || strcmp(argv[i], "--block") == 0)
block = true;
else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
usage(0, argv[0]);
else
usage(1, argv[0]);
}
syncsync(block);
return 0;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(tee ${SOURCES})
banan_link_library(tee libc)
install(TARGETS tee OPTIONAL)

View File

@@ -0,0 +1,64 @@
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#define MAX_FILES 20
#define BUF_SIZE 1024
int main(int argc, char** argv)
{
int files[MAX_FILES] {};
size_t file_count = 0;
int arg = 1;
int oflag = O_WRONLY | O_CREAT;
if (arg < argc && strcmp(argv[arg], "-a") == 0)
{
oflag |= O_APPEND;
arg++;
}
else
{
oflag |= O_TRUNC;
}
for (int i = arg; i < argc; i++)
{
files[file_count] = open(argv[i], oflag, 0644);
if (files[file_count] == -1)
perror(argv[i]);
else
file_count++;
if (file_count >= MAX_FILES)
{
fprintf(stderr, "only up to %d files are supported\n", MAX_FILES);
break;
}
}
char* buffer = (char*)malloc(BUF_SIZE);
for (;;)
{
ssize_t nread = read(STDIN_FILENO, buffer, BUF_SIZE);
if (nread == -1)
perror("stdin");
if (nread <= 0)
break;
write(STDOUT_FILENO, buffer, nread);
for (size_t i = 0; i < file_count; i++)
write(files[i], buffer, nread);
}
free(buffer);
if (ferror(stdin))
perror("stdin");
for (size_t i = 0; i < file_count; i++)
close(files[i]);
return 0;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(touch ${SOURCES})
banan_link_library(touch libc)
install(TARGETS touch OPTIONAL)

View File

@@ -0,0 +1,17 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char** argv)
{
int ret = 0;
for (int i = 1; i < argc; i++)
{
if (creat(argv[i], 0644) == -1 && errno != EEXIST)
{
perror(argv[i]);
ret = 1;
}
}
return ret;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(u8sum ${SOURCES})
banan_link_library(u8sum libc)
install(TARGETS u8sum OPTIONAL)

View File

@@ -0,0 +1,29 @@
#include <fcntl.h>
#include <stdio.h>
int main(int argc, char** argv)
{
for (int i = 1; i < argc; i++)
{
FILE* fp = fopen(argv[i], "r");
if (fp == nullptr)
{
perror("fopen");
continue;
}
uint8_t sum = 0;
uint8_t buffer[1024];
while (size_t ret = fread(buffer, 1, sizeof(buffer), fp))
for (size_t j = 0; j < ret; j++)
sum += buffer[j];
if (ferror(fp))
perror("fread");
else
printf("%s: %02x\n", argv[i], sum);
fclose(fp);
}
}

View File

@@ -0,0 +1,9 @@
set(SOURCES
main.cpp
)
add_executable(whoami ${SOURCES})
banan_include_headers(whoami ban)
banan_link_library(whoami libc)
install(TARGETS whoami OPTIONAL)

View File

@@ -0,0 +1,14 @@
#include <stdio.h>
#include <unistd.h>
int main()
{
char* login = getlogin();
if (login == nullptr)
{
printf("unknown user %d\n", geteuid());
return 1;
}
printf("%s\n", login);
return 0;
}

View File

@@ -0,0 +1,8 @@
set(SOURCES
main.cpp
)
add_executable(yes ${SOURCES})
banan_link_library(yes libc)
install(TARGETS yes OPTIONAL)

View File

@@ -0,0 +1,8 @@
#include <stdio.h>
int main()
{
for (;;)
puts("y");
return 0;
}