Compare commits
5 Commits
140910763e
...
bfb45c7d4a
Author | SHA1 | Date | |
---|---|---|---|
bfb45c7d4a | |||
d3fbddb191 | |||
0ab8efd405 | |||
2aefbdc4ee | |||
15dc71e8e1 |
@ -40,6 +40,12 @@ Additionally, the build process needs some extra dependencies to run: `cmake`, `
|
|||||||
|
|
||||||
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` manually since `run.sh` does it for you.
|
If you have no toolchain set up, `run.sh` will build it automatically, which means that you don't necessarily have to run `setup.sh` manually since `run.sh` does it for you.
|
||||||
|
|
||||||
|
## Login UI
|
||||||
|
|
||||||
|
For development convenience, the system automatically starts a GUI session as the default user, without prompting for a password.
|
||||||
|
|
||||||
|
Despite this, Luna does have a login window built-in. If you'd like to try this feature out or start a GUI session as a different user, you'll need to edit [base/etc/init/99-login](base/etc/init/99-login) and change the line that says `Command=/usr/bin/loginui --autologin=selene` to `Command=/usr/bin/loginui`.
|
||||||
|
|
||||||
## Prebuilt images
|
## Prebuilt images
|
||||||
|
|
||||||
Prebuilt ISO images for every release version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
|
Prebuilt ISO images for every release version can be found at [pub.cloudapio.eu](https://pub.cloudapio.eu/luna/releases).
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
Name=login
|
Name=login
|
||||||
Description=Start a graphical user session.
|
Description=Start a graphical user session.
|
||||||
Command=/usr/bin/startui --user=selene
|
Command=/usr/bin/loginui --autologin=selene
|
||||||
StandardOutput=/dev/uart0
|
StandardOutput=/dev/uart0
|
||||||
StandardError=/dev/uart0
|
StandardError=/dev/uart0
|
||||||
Restart=true
|
Restart=true
|
||||||
|
@ -13,3 +13,5 @@ add_subdirectory(apps)
|
|||||||
|
|
||||||
luna_service(launch.cpp launch)
|
luna_service(launch.cpp launch)
|
||||||
luna_service(run.cpp run)
|
luna_service(run.cpp run)
|
||||||
|
luna_service(loginui.cpp loginui)
|
||||||
|
target_link_libraries(loginui PRIVATE ui)
|
||||||
|
@ -23,7 +23,7 @@ void sigchld_handler(int)
|
|||||||
wait(nullptr);
|
wait(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void sighup_handler(int)
|
void sigquit_handler(int)
|
||||||
{
|
{
|
||||||
// Reload the taskbar by exec-ing the executable, resetting everything.
|
// Reload the taskbar by exec-ing the executable, resetting everything.
|
||||||
StringView args[] = { "/usr/bin/taskbar" };
|
StringView args[] = { "/usr/bin/taskbar" };
|
||||||
@ -151,7 +151,7 @@ Result<int> luna_main(int, char**)
|
|||||||
TRY(app.init("/tmp/wsys.sock"));
|
TRY(app.init("/tmp/wsys.sock"));
|
||||||
|
|
||||||
TRY(os::EventLoop::the().register_signal_handler(SIGCHLD, sigchld_handler));
|
TRY(os::EventLoop::the().register_signal_handler(SIGCHLD, sigchld_handler));
|
||||||
TRY(os::EventLoop::the().register_signal_handler(SIGHUP, sighup_handler));
|
TRY(os::EventLoop::the().register_signal_handler(SIGQUIT, sigquit_handler));
|
||||||
|
|
||||||
launcher_client = TRY(os::IPC::Client::connect("/tmp/launch.sock", false));
|
launcher_client = TRY(os::IPC::Client::connect("/tmp/launch.sock", false));
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <os/ArgumentParser.h>
|
#include <os/ArgumentParser.h>
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
|
#include <os/IPC.h>
|
||||||
#include <os/LocalServer.h>
|
#include <os/LocalServer.h>
|
||||||
#include <os/Process.h>
|
#include <os/Process.h>
|
||||||
#include <os/Security.h>
|
#include <os/Security.h>
|
||||||
@ -66,6 +67,9 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
auto server = TRY(os::LocalServer::create(socket_path, false));
|
auto server = TRY(os::LocalServer::create(socket_path, false));
|
||||||
TRY(server->listen(20));
|
TRY(server->listen(20));
|
||||||
|
|
||||||
|
// We're ready now.
|
||||||
|
os::IPC::notify_parent();
|
||||||
|
|
||||||
Vector<OwnedPtr<os::IPC::ClientConnection>> clients;
|
Vector<OwnedPtr<os::IPC::ClientConnection>> clients;
|
||||||
Vector<struct pollfd> fds;
|
Vector<struct pollfd> fds;
|
||||||
TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 }));
|
TRY(fds.try_append({ .fd = server->fd(), .events = POLLIN, .revents = 0 }));
|
||||||
|
@ -23,6 +23,8 @@ namespace ui
|
|||||||
|
|
||||||
Result<void> draw(ui::Canvas& canvas) override;
|
Result<void> draw(ui::Canvas& canvas) override;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
StringView data();
|
StringView data();
|
||||||
|
|
||||||
void on_submit(os::Function<StringView>&& action)
|
void on_submit(os::Function<StringView>&& action)
|
||||||
|
@ -17,6 +17,8 @@ namespace ui
|
|||||||
{
|
{
|
||||||
InputField::InputField(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
|
InputField::InputField(SharedPtr<ui::Font> font) : ui::TextInput(), m_font(font)
|
||||||
{
|
{
|
||||||
|
u8 zero = 0;
|
||||||
|
m_data.append_data(&zero, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result<ui::EventResult> InputField::handle_key_event(const ui::KeyEventRequest& request)
|
Result<ui::EventResult> InputField::handle_key_event(const ui::KeyEventRequest& request)
|
||||||
@ -35,7 +37,7 @@ namespace ui
|
|||||||
|
|
||||||
if (request.code == moon::K_RightArrow)
|
if (request.code == moon::K_RightArrow)
|
||||||
{
|
{
|
||||||
if (m_cursor < m_data.size()) m_cursor++;
|
if (m_cursor < (m_data.size() - 1)) m_cursor++;
|
||||||
else
|
else
|
||||||
return ui::EventResult::DidNotHandle;
|
return ui::EventResult::DidNotHandle;
|
||||||
update_cursor();
|
update_cursor();
|
||||||
@ -67,9 +69,7 @@ namespace ui
|
|||||||
|
|
||||||
if (iscntrl(request.letter)) return ui::EventResult::DidNotHandle;
|
if (iscntrl(request.letter)) return ui::EventResult::DidNotHandle;
|
||||||
|
|
||||||
if (m_cursor == m_data.size()) TRY(m_data.append_data((const u8*)&request.letter, 1));
|
TRY(insert_character(request.letter));
|
||||||
else
|
|
||||||
TRY(insert_character(request.letter));
|
|
||||||
|
|
||||||
m_cursor++;
|
m_cursor++;
|
||||||
|
|
||||||
@ -78,6 +78,14 @@ namespace ui
|
|||||||
return ui::EventResult::DidHandle;
|
return ui::EventResult::DidHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InputField::clear()
|
||||||
|
{
|
||||||
|
m_data.try_resize(0);
|
||||||
|
u8 zero = 0;
|
||||||
|
m_data.append_data(&zero, 1);
|
||||||
|
m_cursor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
Result<void> InputField::draw(ui::Canvas& canvas)
|
Result<void> InputField::draw(ui::Canvas& canvas)
|
||||||
{
|
{
|
||||||
int visible_characters = canvas.width / m_font->width();
|
int visible_characters = canvas.width / m_font->width();
|
||||||
@ -107,6 +115,7 @@ namespace ui
|
|||||||
|
|
||||||
StringView InputField::data()
|
StringView InputField::data()
|
||||||
{
|
{
|
||||||
|
if (!m_data.size()) return StringView {};
|
||||||
return StringView { (const char*)m_data.data(), m_data.size() };
|
return StringView { (const char*)m_data.data(), m_data.size() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
171
gui/loginui.cpp
Normal file
171
gui/loginui.cpp
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/**
|
||||||
|
* @file loginui.cpp
|
||||||
|
* @author apio (cloudapio.eu)
|
||||||
|
* @brief Graphical login prompt.
|
||||||
|
*
|
||||||
|
* @copyright Copyright (c) 2024, the Luna authors.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <luna/String.h>
|
||||||
|
#include <os/ArgumentParser.h>
|
||||||
|
#include <os/File.h>
|
||||||
|
#include <os/FileSystem.h>
|
||||||
|
#include <os/IPC.h>
|
||||||
|
#include <os/Process.h>
|
||||||
|
#include <pwd.h>
|
||||||
|
#include <shadow.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <ui/App.h>
|
||||||
|
#include <ui/Button.h>
|
||||||
|
#include <ui/InputField.h>
|
||||||
|
#include <ui/Label.h>
|
||||||
|
#include <ui/Layout.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
enum Stage
|
||||||
|
{
|
||||||
|
UsernameInput,
|
||||||
|
PasswordInput,
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr ui::Color BACKGROUND_COLOR = ui::Color::from_rgb(89, 89, 89);
|
||||||
|
|
||||||
|
Result<int> luna_main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
StringView username;
|
||||||
|
|
||||||
|
os::ArgumentParser parser;
|
||||||
|
parser.add_description("Login prompt for a graphical UI session.");
|
||||||
|
parser.add_system_program_info("loginui"_sv);
|
||||||
|
// FIXME: Make this a config option instead of a switch.
|
||||||
|
// Also, calling "loginui --autologin=user" is functionally identical to calling "startui --user=user", the only
|
||||||
|
// difference is that it makes the init config easier to change (only adding or removing the autologin flag, instead
|
||||||
|
// of changing the program to use)
|
||||||
|
parser.add_value_argument(username, ' ', "autologin", "login as a specific user without prompting");
|
||||||
|
parser.parse(argc, argv);
|
||||||
|
|
||||||
|
if (geteuid() != 0)
|
||||||
|
{
|
||||||
|
os::eprintln("error: %s can only be started as root.", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
setsid();
|
||||||
|
|
||||||
|
bool success = os::IPC::Notifier::run_and_wait(
|
||||||
|
[&] {
|
||||||
|
StringView wind_command[] = { "/usr/bin/wind" };
|
||||||
|
os::Process::spawn(wind_command[0], Slice<StringView>(wind_command, 1));
|
||||||
|
},
|
||||||
|
1000);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
os::eprintln("loginui: failed to start wind, timed out");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!username.is_empty())
|
||||||
|
{
|
||||||
|
auto flag = String::format("--user=%s"_sv, username.chars()).release_value();
|
||||||
|
|
||||||
|
StringView startui_command[] = { "/usr/bin/startui", flag.view() };
|
||||||
|
os::Process::exec(startui_command[0], Slice<StringView>(startui_command, 2));
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ui::App app;
|
||||||
|
TRY(app.init());
|
||||||
|
|
||||||
|
auto* window = TRY(ui::Window::create(ui::Rect { 300, 300, 400, 300 }));
|
||||||
|
app.set_main_window(window);
|
||||||
|
|
||||||
|
window->set_title("Log in");
|
||||||
|
window->set_background(BACKGROUND_COLOR);
|
||||||
|
|
||||||
|
ui::VerticalLayout main_layout;
|
||||||
|
window->set_main_widget(main_layout);
|
||||||
|
|
||||||
|
ui::Label label("Username:");
|
||||||
|
main_layout.add_widget(label);
|
||||||
|
|
||||||
|
ui::InputField input(ui::Font::default_font());
|
||||||
|
main_layout.add_widget(input);
|
||||||
|
|
||||||
|
ui::Label error("");
|
||||||
|
error.set_font(ui::Font::default_bold_font());
|
||||||
|
error.set_color(ui::RED);
|
||||||
|
main_layout.add_widget(error);
|
||||||
|
|
||||||
|
Stage stage = Stage::UsernameInput;
|
||||||
|
struct passwd* pw;
|
||||||
|
|
||||||
|
input.on_submit([&](StringView data) {
|
||||||
|
error.set_text("");
|
||||||
|
if (stage == Stage::UsernameInput)
|
||||||
|
{
|
||||||
|
struct passwd* entry = getpwnam(data.chars());
|
||||||
|
if (!entry)
|
||||||
|
{
|
||||||
|
error.set_text("User not found.");
|
||||||
|
input.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pw = entry;
|
||||||
|
stage = Stage::PasswordInput;
|
||||||
|
label.set_text("Password:");
|
||||||
|
|
||||||
|
String title = String::format("Log in: %s"_sv, data.chars()).release_value();
|
||||||
|
window->set_title(title.view());
|
||||||
|
|
||||||
|
input.clear();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const char* passwd = pw->pw_passwd;
|
||||||
|
|
||||||
|
// If the user's password entry is 'x', read their password from the shadow file instead.
|
||||||
|
if (!strcmp(pw->pw_passwd, "x"))
|
||||||
|
{
|
||||||
|
struct spwd* sp = getspnam(pw->pw_name);
|
||||||
|
|
||||||
|
if (!sp)
|
||||||
|
{
|
||||||
|
error.set_text("User not found in shadow file.");
|
||||||
|
input.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
endspent();
|
||||||
|
|
||||||
|
passwd = sp->sp_pwdp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strcmp(passwd, "!"))
|
||||||
|
{
|
||||||
|
error.set_text("User's password is disabled.");
|
||||||
|
input.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(data.chars(), passwd))
|
||||||
|
{
|
||||||
|
error.set_text("Incorrect password.");
|
||||||
|
input.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto flag = String::format("--user=%s"_sv, pw->pw_name).release_value();
|
||||||
|
|
||||||
|
StringView startui_command[] = { "/usr/bin/startui", flag.view() };
|
||||||
|
os::Process::exec(startui_command[0], Slice<StringView>(startui_command, 2));
|
||||||
|
unreachable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return app.run();
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
#include <moon/Keyboard.h>
|
#include <moon/Keyboard.h>
|
||||||
#include <os/ArgumentParser.h>
|
#include <os/ArgumentParser.h>
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
|
#include <os/IPC.h>
|
||||||
#include <os/LocalServer.h>
|
#include <os/LocalServer.h>
|
||||||
#include <os/Process.h>
|
#include <os/Process.h>
|
||||||
#include <os/Security.h>
|
#include <os/Security.h>
|
||||||
@ -115,6 +116,9 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We're ready now.
|
||||||
|
os::IPC::notify_parent();
|
||||||
|
|
||||||
ui::Color background = ui::Color::from_rgb(0x10, 0x10, 0x10);
|
ui::Color background = ui::Color::from_rgb(0x10, 0x10, 0x10);
|
||||||
|
|
||||||
Vector<OwnedPtr<Client>> clients;
|
Vector<OwnedPtr<Client>> clients;
|
||||||
|
@ -107,7 +107,7 @@ namespace os
|
|||||||
|
|
||||||
void call()
|
void call()
|
||||||
{
|
{
|
||||||
return m_function();
|
m_function();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -320,5 +320,78 @@ namespace os
|
|||||||
bool m_ipc_in_progress { false };
|
bool m_ipc_in_progress { false };
|
||||||
u8 m_ipc_saved_id { 0 };
|
u8 m_ipc_saved_id { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief API used to notify a parent process when a child process finishes initialization. The Notifier struct
|
||||||
|
* is the parent part of the API.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
struct Notifier
|
||||||
|
{
|
||||||
|
int pfds[2];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new Notifier.
|
||||||
|
*
|
||||||
|
* This function will create a pipe for the parent and child to communicate.
|
||||||
|
*
|
||||||
|
* @return Notifier The new Notifier object.
|
||||||
|
*/
|
||||||
|
static Notifier create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hook the Notifier into any child process started afterwards.
|
||||||
|
*
|
||||||
|
* This will set an environment variable, which if detected by a child process, will use it to notify the
|
||||||
|
* parent whenever it's ready.
|
||||||
|
*
|
||||||
|
* The recommended order to call this API is:
|
||||||
|
* hook()
|
||||||
|
* fork+exec
|
||||||
|
* unhook()
|
||||||
|
*/
|
||||||
|
void hook();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the previously created environment variable, so that any future child processes will not
|
||||||
|
* notify this Notifier.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void unhook();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Wait for a child process to be ready. If several child processes are hooked by the hook() method,
|
||||||
|
* this method will only catch the first one that notifies the parent.
|
||||||
|
*
|
||||||
|
* @param timeout If positive, specifies the timeout after which the function fails if no notification is
|
||||||
|
* received.
|
||||||
|
* @return true The child is ready.
|
||||||
|
* @return false The method timed out.
|
||||||
|
*/
|
||||||
|
bool wait(int timeout = -1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Combines hook(), unhook() and wait() into one single method. This method takes a function, and
|
||||||
|
* executes it in a "hooked" context, so that any child process started by this function will automatically
|
||||||
|
* detect the parent when it's ready (if it supports the notification API). Then, the function waits for the
|
||||||
|
* child to be ready.
|
||||||
|
*
|
||||||
|
* @param action The function to run.
|
||||||
|
* @param timeout If positive, specifies the timeout after which the function fails if no notification is
|
||||||
|
* received.
|
||||||
|
* @return true The child is ready.
|
||||||
|
* @return false The method timed out.
|
||||||
|
*/
|
||||||
|
static bool run_and_wait(os::Action&& action, int timeout = -1);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Use this function to notify a parent process whenever your program finishes initialization.
|
||||||
|
*
|
||||||
|
* If the parent has not used the Notifier API to request a notification from the child process, this function
|
||||||
|
* does nothing.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void notify_parent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,13 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <luna/String.h>
|
||||||
#include <os/IPC.h>
|
#include <os/IPC.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
namespace os::IPC
|
namespace os::IPC
|
||||||
{
|
{
|
||||||
@ -84,4 +90,60 @@ namespace os::IPC
|
|||||||
Client::Client(OwnedPtr<LocalClient>&& connection) : m_connection(move(connection))
|
Client::Client(OwnedPtr<LocalClient>&& connection) : m_connection(move(connection))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Notifier Notifier::create()
|
||||||
|
{
|
||||||
|
Notifier result;
|
||||||
|
pipe(result.pfds);
|
||||||
|
fcntl(result.pfds[0], F_SETFD, FD_CLOEXEC);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::hook()
|
||||||
|
{
|
||||||
|
auto value = String::format("%d"_sv, pfds[1]).release_value();
|
||||||
|
setenv("LUNA_NOTIFY_FD", value.chars(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Notifier::unhook()
|
||||||
|
{
|
||||||
|
unsetenv("LUNA_NOTIFY_FD");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Notifier::wait(int timeout)
|
||||||
|
{
|
||||||
|
close(pfds[1]);
|
||||||
|
struct pollfd fds[] = { { .fd = pfds[0], .events = POLLIN, .revents = 0 } };
|
||||||
|
|
||||||
|
poll:
|
||||||
|
int result = poll(fds, 1, timeout);
|
||||||
|
if (result < 0 && errno == EINTR) goto poll;
|
||||||
|
|
||||||
|
close(pfds[0]);
|
||||||
|
|
||||||
|
return result > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Notifier::run_and_wait(os::Action&& action, int timeout)
|
||||||
|
{
|
||||||
|
auto notifier = create();
|
||||||
|
notifier.hook();
|
||||||
|
action();
|
||||||
|
notifier.unhook();
|
||||||
|
return notifier.wait(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void notify_parent()
|
||||||
|
{
|
||||||
|
char* fd_string = getenv("LUNA_NOTIFY_FD");
|
||||||
|
if (!fd_string) return;
|
||||||
|
|
||||||
|
int fd = atoi(fd_string);
|
||||||
|
|
||||||
|
u8 data = 0;
|
||||||
|
write(fd, &data, 1);
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
unsetenv("LUNA_NOTIFY_FD");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <os/ArgumentParser.h>
|
#include <os/ArgumentParser.h>
|
||||||
#include <os/Directory.h>
|
#include <os/Directory.h>
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
|
#include <os/IPC.h>
|
||||||
#include <os/Process.h>
|
#include <os/Process.h>
|
||||||
#include <os/Security.h>
|
#include <os/Security.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
@ -38,6 +39,7 @@ struct Service
|
|||||||
Option<uid_t> user {};
|
Option<uid_t> user {};
|
||||||
Option<gid_t> group {};
|
Option<gid_t> group {};
|
||||||
bool wait { false };
|
bool wait { false };
|
||||||
|
bool wait_notify { false };
|
||||||
Option<pid_t> pid {};
|
Option<pid_t> pid {};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,9 +134,13 @@ static Result<void> try_start_service(Service& service)
|
|||||||
new_stdin->set_close_on_exec();
|
new_stdin->set_close_on_exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
os::IPC::Notifier notifier;
|
||||||
|
if (service.wait_notify) { notifier = os::IPC::Notifier::create(); }
|
||||||
|
|
||||||
pid_t pid = TRY(os::Process::fork());
|
pid_t pid = TRY(os::Process::fork());
|
||||||
if (pid == 0)
|
if (pid == 0)
|
||||||
{
|
{
|
||||||
|
if (service.wait_notify) { notifier.hook(); }
|
||||||
auto rc = service_child(service, new_stdout, new_stderr, new_stdin);
|
auto rc = service_child(service, new_stdout, new_stderr, new_stdin);
|
||||||
if (rc.has_error())
|
if (rc.has_error())
|
||||||
{
|
{
|
||||||
@ -157,7 +163,17 @@ static Result<void> try_start_service(Service& service)
|
|||||||
do_log("[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
|
do_log("[init] child process %d exited with code %d\n", pid, WEXITSTATUS(status));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
if (service.wait_notify)
|
||||||
|
{
|
||||||
|
bool success = notifier.wait(1000);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
do_log("[init] service %s with pid %d failed to start successfully\n", service.name.chars(), pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
service.pid = pid;
|
service.pid = pid;
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -273,6 +289,17 @@ static Result<void> load_service(const os::Path& path)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parts[0].view() == "WaitUntilReady")
|
||||||
|
{
|
||||||
|
if (parts[1].view() == "true" || parts[1].view().to_uint().value_or(0) == 1)
|
||||||
|
{
|
||||||
|
service.wait_notify = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
service.wait_notify = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
do_log("[init] skipping unknown entry name %s\n", parts[0].chars());
|
do_log("[init] skipping unknown entry name %s\n", parts[0].chars());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
#include <grp.h>
|
#include <grp.h>
|
||||||
#include <os/ArgumentParser.h>
|
#include <os/ArgumentParser.h>
|
||||||
#include <os/File.h>
|
#include <os/File.h>
|
||||||
|
#include <os/FileSystem.h>
|
||||||
|
#include <os/IPC.h>
|
||||||
#include <os/Main.h>
|
#include <os/Main.h>
|
||||||
#include <os/Process.h>
|
#include <os/Process.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
@ -18,19 +20,6 @@
|
|||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
// Timeout should be provided in milliseconds.
|
|
||||||
Result<void> wait_until_file_created(StringView path, int timeout)
|
|
||||||
{
|
|
||||||
struct stat st;
|
|
||||||
while (stat(path.chars(), &st) < 0)
|
|
||||||
{
|
|
||||||
if (timeout <= 0) return err(ETIMEDOUT);
|
|
||||||
usleep(100000); // FIXME: Implement something like inotify or use signals to avoid polling.
|
|
||||||
timeout -= 100;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<Vector<gid_t>> read_supplementary_groups(const char* name)
|
Result<Vector<gid_t>> read_supplementary_groups(const char* name)
|
||||||
{
|
{
|
||||||
Vector<gid_t> extra_groups;
|
Vector<gid_t> extra_groups;
|
||||||
@ -70,12 +59,6 @@ Result<void> spawn_process_as_user(Slice<StringView> arguments, uid_t user, gid_
|
|||||||
|
|
||||||
Result<int> luna_main(int argc, char** argv)
|
Result<int> luna_main(int argc, char** argv)
|
||||||
{
|
{
|
||||||
if (geteuid() != 0)
|
|
||||||
{
|
|
||||||
os::eprintln("error: %s can only be started as root.", argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringView username;
|
StringView username;
|
||||||
|
|
||||||
os::ArgumentParser parser;
|
os::ArgumentParser parser;
|
||||||
@ -84,6 +67,12 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
parser.add_value_argument(username, 'u', "user", "the user to start the UI session as");
|
parser.add_value_argument(username, 'u', "user", "the user to start the UI session as");
|
||||||
parser.parse(argc, argv);
|
parser.parse(argc, argv);
|
||||||
|
|
||||||
|
if (geteuid() != 0)
|
||||||
|
{
|
||||||
|
os::eprintln("error: %s can only be started as root.", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (username.is_empty())
|
if (username.is_empty())
|
||||||
{
|
{
|
||||||
os::eprintln("error: startui needs a --user parameter to run.");
|
os::eprintln("error: startui needs a --user parameter to run.");
|
||||||
@ -105,9 +94,22 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
|
|
||||||
setsid();
|
setsid();
|
||||||
|
|
||||||
// First of all, start the display server.
|
// First of all, start the display server, in case we haven't been started by loginui.
|
||||||
StringView wind_command[] = { "/usr/bin/wind" };
|
if (!os::FileSystem::exists("/tmp/wind.sock"))
|
||||||
TRY(os::Process::spawn(wind_command[0], Slice<StringView>(wind_command, 1)));
|
{
|
||||||
|
// We need to wait for this one, since its sockets are required later.
|
||||||
|
bool success = os::IPC::Notifier::run_and_wait(
|
||||||
|
[&] {
|
||||||
|
StringView wind_command[] = { "/usr/bin/wind" };
|
||||||
|
os::Process::spawn(wind_command[0], Slice<StringView>(wind_command, 1));
|
||||||
|
},
|
||||||
|
1000);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
os::eprintln("startui: failed to start wind, timed out");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clearenv();
|
clearenv();
|
||||||
chdir(pw->pw_dir);
|
chdir(pw->pw_dir);
|
||||||
@ -116,18 +118,22 @@ Result<int> luna_main(int argc, char** argv)
|
|||||||
setenv("HOME", pw->pw_dir, 1);
|
setenv("HOME", pw->pw_dir, 1);
|
||||||
setenv("SHELL", pw->pw_shell, 1);
|
setenv("SHELL", pw->pw_shell, 1);
|
||||||
|
|
||||||
// Next, start the required UI components.
|
// We also need to wait for this one, since taskbar requires launch.sock.
|
||||||
StringView launch_command[] = { "/usr/bin/launch" };
|
bool success = os::IPC::Notifier::run_and_wait(
|
||||||
TRY(spawn_process_as_user(Slice<StringView>(launch_command, 1), pw->pw_uid, pw->pw_gid, groups.slice()));
|
[&] {
|
||||||
|
StringView launch_command[] = { "/usr/bin/launch" };
|
||||||
TRY(wait_until_file_created("/tmp/wsys.sock", 10000));
|
spawn_process_as_user(Slice<StringView>(launch_command, 1), pw->pw_uid, pw->pw_gid, groups.slice());
|
||||||
TRY(wait_until_file_created("/tmp/launch.sock", 10000));
|
},
|
||||||
|
1000);
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
os::eprintln("startui: failed to start launch server, timed out");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
StringView taskbar_command[] = { "/usr/bin/taskbar" };
|
StringView taskbar_command[] = { "/usr/bin/taskbar" };
|
||||||
TRY(spawn_process_as_user(Slice<StringView>(taskbar_command, 1), pw->pw_uid, pw->pw_gid, system_groups.slice()));
|
TRY(spawn_process_as_user(Slice<StringView>(taskbar_command, 1), pw->pw_uid, pw->pw_gid, system_groups.slice()));
|
||||||
|
|
||||||
TRY(wait_until_file_created("/tmp/wind.sock", 10000));
|
|
||||||
|
|
||||||
// Finally, start init in user mode to manage all configured optional services.
|
// Finally, start init in user mode to manage all configured optional services.
|
||||||
StringView init_command[] = { "/usr/bin/init", "--user" };
|
StringView init_command[] = { "/usr/bin/init", "--user" };
|
||||||
TRY(spawn_process_as_user(Slice<StringView>(init_command, 2), pw->pw_uid, pw->pw_gid, groups.slice()));
|
TRY(spawn_process_as_user(Slice<StringView>(init_command, 2), pw->pw_uid, pw->pw_gid, groups.slice()));
|
||||||
|
Loading…
Reference in New Issue
Block a user