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:
45
userspace/programs/CMakeLists.txt
Normal file
45
userspace/programs/CMakeLists.txt
Normal 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()
|
||||
9
userspace/programs/Shell/CMakeLists.txt
Normal file
9
userspace/programs/Shell/CMakeLists.txt
Normal 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)
|
||||
1097
userspace/programs/Shell/main.cpp
Normal file
1097
userspace/programs/Shell/main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
13
userspace/programs/Terminal/CMakeLists.txt
Normal file
13
userspace/programs/Terminal/CMakeLists.txt
Normal 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)
|
||||
380
userspace/programs/Terminal/Terminal.cpp
Normal file
380
userspace/programs/Terminal/Terminal.cpp
Normal 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));
|
||||
}
|
||||
64
userspace/programs/Terminal/Terminal.h
Normal file
64
userspace/programs/Terminal/Terminal.h
Normal 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 };
|
||||
};
|
||||
7
userspace/programs/Terminal/main.cpp
Normal file
7
userspace/programs/Terminal/main.cpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "Terminal.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
Terminal terminal;
|
||||
terminal.run();
|
||||
}
|
||||
16
userspace/programs/WindowServer/CMakeLists.txt
Normal file
16
userspace/programs/WindowServer/CMakeLists.txt
Normal 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)
|
||||
35
userspace/programs/WindowServer/Cursor.h
Normal file
35
userspace/programs/WindowServer/Cursor.h
Normal 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$`";
|
||||
45
userspace/programs/WindowServer/Framebuffer.cpp
Normal file
45
userspace/programs/WindowServer/Framebuffer.cpp
Normal 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;
|
||||
}
|
||||
16
userspace/programs/WindowServer/Framebuffer.h
Normal file
16
userspace/programs/WindowServer/Framebuffer.h
Normal 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();
|
||||
80
userspace/programs/WindowServer/Utils.h
Normal file
80
userspace/programs/WindowServer/Utils.h
Normal 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;
|
||||
}
|
||||
|
||||
};
|
||||
72
userspace/programs/WindowServer/Window.cpp
Normal file
72
userspace/programs/WindowServer/Window.cpp
Normal 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;
|
||||
}
|
||||
76
userspace/programs/WindowServer/Window.h
Normal file
76
userspace/programs/WindowServer/Window.h
Normal 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>;
|
||||
};
|
||||
475
userspace/programs/WindowServer/WindowServer.cpp
Normal file
475
userspace/programs/WindowServer/WindowServer.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
64
userspace/programs/WindowServer/WindowServer.h
Normal file
64
userspace/programs/WindowServer/WindowServer.h
Normal 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;
|
||||
};
|
||||
299
userspace/programs/WindowServer/main.cpp
Normal file
299
userspace/programs/WindowServer/main.cpp
Normal 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, ¤t_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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
8
userspace/programs/cat-mmap/CMakeLists.txt
Normal file
8
userspace/programs/cat-mmap/CMakeLists.txt
Normal 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)
|
||||
66
userspace/programs/cat-mmap/main.cpp
Normal file
66
userspace/programs/cat-mmap/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/cat/CMakeLists.txt
Normal file
8
userspace/programs/cat/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(cat ${SOURCES})
|
||||
banan_link_library(cat libc)
|
||||
|
||||
install(TARGETS cat OPTIONAL)
|
||||
50
userspace/programs/cat/main.cpp
Normal file
50
userspace/programs/cat/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/chmod/CMakeLists.txt
Normal file
8
userspace/programs/chmod/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(chmod ${SOURCES})
|
||||
banan_link_library(chmod libc)
|
||||
|
||||
install(TARGETS chmod OPTIONAL)
|
||||
43
userspace/programs/chmod/main.cpp
Normal file
43
userspace/programs/chmod/main.cpp
Normal 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;
|
||||
}
|
||||
9
userspace/programs/cp/CMakeLists.txt
Normal file
9
userspace/programs/cp/CMakeLists.txt
Normal 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)
|
||||
143
userspace/programs/cp/main.cpp
Normal file
143
userspace/programs/cp/main.cpp
Normal 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;
|
||||
}
|
||||
28
userspace/programs/create_program.sh
Executable file
28
userspace/programs/create_program.sh
Executable 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
|
||||
8
userspace/programs/dd/CMakeLists.txt
Normal file
8
userspace/programs/dd/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(dd ${SOURCES})
|
||||
banan_link_library(dd libc)
|
||||
|
||||
install(TARGETS dd OPTIONAL)
|
||||
189
userspace/programs/dd/main.cpp
Normal file
189
userspace/programs/dd/main.cpp
Normal 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;
|
||||
}
|
||||
9
userspace/programs/dhcp-client/CMakeLists.txt
Normal file
9
userspace/programs/dhcp-client/CMakeLists.txt
Normal 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)
|
||||
385
userspace/programs/dhcp-client/main.cpp
Normal file
385
userspace/programs/dhcp-client/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/echo/CMakeLists.txt
Normal file
8
userspace/programs/echo/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(echo ${SOURCES})
|
||||
banan_link_library(echo libc)
|
||||
|
||||
install(TARGETS echo OPTIONAL)
|
||||
40
userspace/programs/echo/main.cpp
Normal file
40
userspace/programs/echo/main.cpp
Normal 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;
|
||||
}
|
||||
9
userspace/programs/getopt/CMakeLists.txt
Normal file
9
userspace/programs/getopt/CMakeLists.txt
Normal 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)
|
||||
44
userspace/programs/getopt/main.cpp
Normal file
44
userspace/programs/getopt/main.cpp
Normal 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;
|
||||
}
|
||||
10
userspace/programs/http-server/CMakeLists.txt
Normal file
10
userspace/programs/http-server/CMakeLists.txt
Normal 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)
|
||||
516
userspace/programs/http-server/HTTPServer.cpp
Normal file
516
userspace/programs/http-server/HTTPServer.cpp
Normal 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;
|
||||
}
|
||||
49
userspace/programs/http-server/HTTPServer.h
Normal file
49
userspace/programs/http-server/HTTPServer.h
Normal 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;
|
||||
};
|
||||
73
userspace/programs/http-server/main.cpp
Normal file
73
userspace/programs/http-server/main.cpp
Normal 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();
|
||||
}
|
||||
9
userspace/programs/id/CMakeLists.txt
Normal file
9
userspace/programs/id/CMakeLists.txt
Normal 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)
|
||||
36
userspace/programs/id/main.cpp
Normal file
36
userspace/programs/id/main.cpp
Normal 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");
|
||||
}
|
||||
10
userspace/programs/image/CMakeLists.txt
Normal file
10
userspace/programs/image/CMakeLists.txt
Normal 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)
|
||||
110
userspace/programs/image/main.cpp
Normal file
110
userspace/programs/image/main.cpp
Normal 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;
|
||||
}
|
||||
9
userspace/programs/init/CMakeLists.txt
Normal file
9
userspace/programs/init/CMakeLists.txt
Normal 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)
|
||||
138
userspace/programs/init/main.cpp
Normal file
138
userspace/programs/init/main.cpp
Normal 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");
|
||||
}
|
||||
|
||||
}
|
||||
9
userspace/programs/loadfont/CMakeLists.txt
Normal file
9
userspace/programs/loadfont/CMakeLists.txt
Normal 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)
|
||||
30
userspace/programs/loadfont/main.cpp
Normal file
30
userspace/programs/loadfont/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/loadkeys/CMakeLists.txt
Normal file
8
userspace/programs/loadkeys/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(loadkeys ${SOURCES})
|
||||
banan_link_library(loadkeys libc)
|
||||
|
||||
install(TARGETS loadkeys OPTIONAL)
|
||||
42
userspace/programs/loadkeys/main.cpp
Normal file
42
userspace/programs/loadkeys/main.cpp
Normal 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;
|
||||
}
|
||||
9
userspace/programs/ls/CMakeLists.txt
Normal file
9
userspace/programs/ls/CMakeLists.txt
Normal 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)
|
||||
315
userspace/programs/ls/main.cpp
Normal file
315
userspace/programs/ls/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/meminfo/CMakeLists.txt
Normal file
8
userspace/programs/meminfo/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(meminfo ${SOURCES})
|
||||
banan_link_library(meminfo libc)
|
||||
|
||||
install(TARGETS meminfo OPTIONAL)
|
||||
99
userspace/programs/meminfo/main.cpp
Normal file
99
userspace/programs/meminfo/main.cpp
Normal 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);
|
||||
}
|
||||
8
userspace/programs/mkdir/CMakeLists.txt
Normal file
8
userspace/programs/mkdir/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(mkdir ${SOURCES})
|
||||
banan_link_library(mkdir libc)
|
||||
|
||||
install(TARGETS mkdir OPTIONAL)
|
||||
23
userspace/programs/mkdir/main.cpp
Normal file
23
userspace/programs/mkdir/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/nslookup/CMakeLists.txt
Normal file
8
userspace/programs/nslookup/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(nslookup ${SOURCES})
|
||||
banan_link_library(nslookup libc)
|
||||
|
||||
install(TARGETS nslookup OPTIONAL)
|
||||
54
userspace/programs/nslookup/main.cpp
Normal file
54
userspace/programs/nslookup/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/poweroff/CMakeLists.txt
Normal file
8
userspace/programs/poweroff/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(poweroff ${SOURCES})
|
||||
banan_link_library(poweroff libc)
|
||||
|
||||
install(TARGETS poweroff OPTIONAL)
|
||||
38
userspace/programs/poweroff/main.cpp
Normal file
38
userspace/programs/poweroff/main.cpp
Normal 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;
|
||||
}
|
||||
9
userspace/programs/resolver/CMakeLists.txt
Normal file
9
userspace/programs/resolver/CMakeLists.txt
Normal 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)
|
||||
338
userspace/programs/resolver/main.cpp
Normal file
338
userspace/programs/resolver/main.cpp
Normal 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;
|
||||
}
|
||||
9
userspace/programs/rm/CMakeLists.txt
Normal file
9
userspace/programs/rm/CMakeLists.txt
Normal 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)
|
||||
131
userspace/programs/rm/main.cpp
Normal file
131
userspace/programs/rm/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/sleep/CMakeLists.txt
Normal file
8
userspace/programs/sleep/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(sleep ${SOURCES})
|
||||
banan_link_library(sleep libc)
|
||||
|
||||
install(TARGETS sleep OPTIONAL)
|
||||
38
userspace/programs/sleep/main.cpp
Normal file
38
userspace/programs/sleep/main.cpp
Normal 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);
|
||||
}
|
||||
9
userspace/programs/snake/CMakeLists.txt
Normal file
9
userspace/programs/snake/CMakeLists.txt
Normal 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)
|
||||
248
userspace/programs/snake/main.cpp
Normal file
248
userspace/programs/snake/main.cpp
Normal 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;
|
||||
}
|
||||
9
userspace/programs/stat/CMakeLists.txt
Normal file
9
userspace/programs/stat/CMakeLists.txt
Normal 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)
|
||||
75
userspace/programs/stat/main.cpp
Normal file
75
userspace/programs/stat/main.cpp
Normal 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");
|
||||
}
|
||||
}
|
||||
16
userspace/programs/sudo/CMakeLists.txt
Normal file
16
userspace/programs/sudo/CMakeLists.txt
Normal 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
|
||||
)
|
||||
31
userspace/programs/sudo/main.cpp
Normal file
31
userspace/programs/sudo/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/sync/CMakeLists.txt
Normal file
8
userspace/programs/sync/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(sync ${SOURCES})
|
||||
banan_link_library(sync libc)
|
||||
|
||||
install(TARGETS sync OPTIONAL)
|
||||
29
userspace/programs/sync/main.cpp
Normal file
29
userspace/programs/sync/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/tee/CMakeLists.txt
Normal file
8
userspace/programs/tee/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(tee ${SOURCES})
|
||||
banan_link_library(tee libc)
|
||||
|
||||
install(TARGETS tee OPTIONAL)
|
||||
64
userspace/programs/tee/main.cpp
Normal file
64
userspace/programs/tee/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/touch/CMakeLists.txt
Normal file
8
userspace/programs/touch/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(touch ${SOURCES})
|
||||
banan_link_library(touch libc)
|
||||
|
||||
install(TARGETS touch OPTIONAL)
|
||||
17
userspace/programs/touch/main.cpp
Normal file
17
userspace/programs/touch/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/u8sum/CMakeLists.txt
Normal file
8
userspace/programs/u8sum/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(u8sum ${SOURCES})
|
||||
banan_link_library(u8sum libc)
|
||||
|
||||
install(TARGETS u8sum OPTIONAL)
|
||||
29
userspace/programs/u8sum/main.cpp
Normal file
29
userspace/programs/u8sum/main.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
9
userspace/programs/whoami/CMakeLists.txt
Normal file
9
userspace/programs/whoami/CMakeLists.txt
Normal 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)
|
||||
14
userspace/programs/whoami/main.cpp
Normal file
14
userspace/programs/whoami/main.cpp
Normal 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;
|
||||
}
|
||||
8
userspace/programs/yes/CMakeLists.txt
Normal file
8
userspace/programs/yes/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
set(SOURCES
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_executable(yes ${SOURCES})
|
||||
banan_link_library(yes libc)
|
||||
|
||||
install(TARGETS yes OPTIONAL)
|
||||
8
userspace/programs/yes/main.cpp
Normal file
8
userspace/programs/yes/main.cpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
for (;;)
|
||||
puts("y");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user