diff --git a/Makefile b/Makefile
index 32625be8..d6196f08 100644
--- a/Makefile
+++ b/Makefile
@@ -18,6 +18,7 @@ games \
init \
kblayout \
kblayout-compiler \
+login \
mbr \
mkinitrd \
regress \
diff --git a/login/.gitignore b/login/.gitignore
new file mode 100644
index 00000000..dd1a6026
--- /dev/null
+++ b/login/.gitignore
@@ -0,0 +1,3 @@
+login
+*.o
+arrow.inc
diff --git a/login/Makefile b/login/Makefile
new file mode 100644
index 00000000..6b88aa28
--- /dev/null
+++ b/login/Makefile
@@ -0,0 +1,41 @@
+include ../build-aux/platform.mak
+include ../build-aux/compiler.mak
+include ../build-aux/version.mak
+include ../build-aux/dirs.mak
+
+OPTLEVEL?=$(DEFAULT_OPTLEVEL)
+CFLAGS?=$(OPTLEVEL)
+
+CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\"
+CFLAGS:=$(CXXFLAGS) -Wall -Wextra
+
+BINARY=login
+
+OBJS=\
+framebuffer.o \
+graphical.o \
+login.o \
+pixel.o \
+vgafont.o \
+
+all: $(BINARY)
+
+.PHONY: all install clean
+
+$(BINARY): $(OBJS)
+ $(CC) $(OBJS) -o $(BINARY) $(CXXFLAGS) $(LIBS)
+
+%.o: %.c arrow.inc
+ $(CC) -std=gnu11 $(CPPFLAGS) $(CFLAGS) -c $< -o $@
+
+arrow.inc: arrow.rgb
+ carray -cs --identifier=arrow arrow.rgb -o $@
+
+install: all
+ mkdir -p $(DESTDIR)$(SBINDIR)
+ install $(BINARY) $(DESTDIR)$(SBINDIR)
+ mkdir -p $(DESTDIR)$(MANDIR)/man8
+ cp login.8 $(DESTDIR)$(MANDIR)/man8/login.8
+
+clean:
+ rm -f $(BINARY) $(OBJS) *.o arrow.inc
diff --git a/login/arrow.rgb b/login/arrow.rgb
new file mode 100644
index 00000000..6d861ba5
Binary files /dev/null and b/login/arrow.rgb differ
diff --git a/login/framebuffer.c b/login/framebuffer.c
new file mode 100644
index 00000000..a43ba4ae
--- /dev/null
+++ b/login/framebuffer.c
@@ -0,0 +1,176 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ framebuffer.c
+ Framebuffer utilities.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+#include
+
+#include "framebuffer.h"
+#include "pixel.h"
+#include "vgafont.h"
+
+struct framebuffer framebuffer_crop(struct framebuffer fb,
+ size_t left,
+ size_t top,
+ size_t width,
+ size_t height)
+{
+ // Crop the framebuffer horizontally.
+ if ( fb.xres < left )
+ left = fb.xres;
+ fb.buffer += left;
+ fb.xres -= left;
+ if ( width < fb.xres )
+ fb.xres = width;
+
+ // Crop the framebuffer vertically.
+ if ( fb.yres < top )
+ top = fb.yres;
+ fb.buffer += top * fb.pitch;
+ fb.yres -= top;
+ if ( height < fb.yres )
+ fb.yres = height;
+
+ return fb;
+}
+
+void framebuffer_copy_to_framebuffer(const struct framebuffer dst,
+ const struct framebuffer src)
+{
+ for ( size_t y = 0; y < src.yres; y++ )
+ for ( size_t x = 0; x < src.xres; x++ )
+ framebuffer_set_pixel(dst, x, y, framebuffer_get_pixel(src, x, y));
+}
+
+void framebuffer_copy_to_framebuffer_blend(const struct framebuffer dst,
+ const struct framebuffer src)
+{
+ for ( size_t y = 0; y < src.yres; y++ )
+ {
+ for ( size_t x = 0; x < src.xres; x++ )
+ {
+ uint32_t bg = framebuffer_get_pixel(dst, x, y);
+ uint32_t fg = framebuffer_get_pixel(src, x, y);
+ framebuffer_set_pixel(dst, x, y, blend_pixel(bg, fg));
+ }
+ }
+}
+
+struct framebuffer framebuffer_crop_int(struct framebuffer fb,
+ int left,
+ int top,
+ int width,
+ int height)
+{
+ if ( left < 0 ) { width -= -left; left = 0; }
+ if ( top < 0 ) { top -= -height; top -= 0; }
+ if ( width < 0 ) { width = 0; }
+ if ( height < 0 ) { height = 0; }
+ return framebuffer_crop(fb, left, top, width, height);
+}
+
+struct framebuffer framebuffer_cut_left_x(struct framebuffer fb, int offset)
+{
+ fb = framebuffer_crop_int(fb, offset, 0, fb.xres - offset, fb.yres);
+ return fb;
+}
+
+struct framebuffer framebuffer_cut_right_x(struct framebuffer fb, int offset)
+{
+ fb = framebuffer_crop_int(fb, 0, 0, fb.xres - offset, fb.yres);
+ return fb;
+}
+
+struct framebuffer framebuffer_cut_top_y(struct framebuffer fb, int offset)
+{
+ fb = framebuffer_crop_int(fb, 0, offset, fb.xres, fb.yres - offset);
+ return fb;
+}
+
+struct framebuffer framebuffer_cut_bottom_y(struct framebuffer fb, int offset)
+{
+ fb = framebuffer_crop_int(fb, 0, 0, fb.xres, fb.yres - offset);
+ return fb;
+}
+
+struct framebuffer framebuffer_center_x(struct framebuffer fb, int x, int width)
+{
+ x = x - width / 2;
+ if ( x < 0 ) { width -= -x; x = 0; }
+ if ( width < 0 ) { width = 0; }
+ fb = framebuffer_crop(fb, x, 0, width, fb.yres);
+ return fb;
+}
+
+struct framebuffer framebuffer_center_y(struct framebuffer fb, int y, int height)
+{
+ y = y - height / 2;
+ if ( y < 0 ) { height -= -y; y = 0; }
+ if ( height < 0 ) { height = 0; }
+ fb = framebuffer_crop(fb, 0, y, fb.xres, height);
+ return fb;
+}
+
+struct framebuffer framebuffer_right_x(struct framebuffer fb, int x, int width)
+{
+ x = x - width;
+ if ( x < 0 ) { width -= -x; x = 0; }
+ if ( width < 0 ) { width = 0; }
+ fb = framebuffer_crop(fb, x, 0, width, fb.yres);
+ return fb;
+}
+
+struct framebuffer framebuffer_bottom_y(struct framebuffer fb, int y, int height)
+{
+ y = y - height;
+ if ( y < 0 ) { height -= -y; y = 0; }
+ if ( height < 0 ) { height = 0; }
+ fb = framebuffer_crop(fb, 0, y, fb.xres, height);
+ return fb;
+}
+
+struct framebuffer framebuffer_center_text_x(struct framebuffer fb, int x, const char* str)
+{
+ int width = (FONT_WIDTH + 1) * strlen(str);
+ return framebuffer_center_x(fb, x, width);
+}
+
+struct framebuffer framebuffer_center_text_y(struct framebuffer fb, int y, const char* str)
+{
+ (void) str;
+ int height = FONT_HEIGHT;
+ return framebuffer_center_y(fb, y, height);
+}
+
+struct framebuffer framebuffer_right_text_x(struct framebuffer fb, int x, const char* str)
+{
+ int width = (FONT_WIDTH + 1) * strlen(str);
+ return framebuffer_right_x(fb, x, width);
+}
+
+struct framebuffer framebuffer_bottom_text_y(struct framebuffer fb, int y, const char* str)
+{
+ (void) str;
+ int height = FONT_HEIGHT;
+ return framebuffer_bottom_y(fb, y, height);
+}
diff --git a/login/framebuffer.h b/login/framebuffer.h
new file mode 100644
index 00000000..d67800f8
--- /dev/null
+++ b/login/framebuffer.h
@@ -0,0 +1,83 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ framebuffer.h
+ Framebuffer utilities.
+
+*******************************************************************************/
+
+#ifndef FRAMEBUFFER_H
+#define FRAMEBUFFER_H
+
+#include
+#include
+
+struct framebuffer
+{
+ size_t pitch;
+ uint32_t* buffer;
+ size_t xres;
+ size_t yres;
+};
+
+static inline uint32_t framebuffer_get_pixel(const struct framebuffer fb,
+ size_t x,
+ size_t y)
+{
+ if ( fb.xres <= x || fb.yres <= y )
+ return 0;
+ return fb.buffer[y * fb.pitch + x];
+}
+
+static inline void framebuffer_set_pixel(const struct framebuffer fb,
+ size_t x,
+ size_t y,
+ uint32_t value)
+{
+ if ( fb.xres <= x || fb.yres <= y )
+ return;
+ fb.buffer[y * fb.pitch + x] = value;
+}
+
+struct framebuffer framebuffer_crop(struct framebuffer fb,
+ size_t left,
+ size_t top,
+ size_t width,
+ size_t height);
+void framebuffer_copy_to_framebuffer(const struct framebuffer dst,
+ const struct framebuffer src);
+void framebuffer_copy_to_framebuffer_blend(const struct framebuffer dst,
+ const struct framebuffer src);
+struct framebuffer framebuffer_crop_int(struct framebuffer fb,
+ int left,
+ int top,
+ int width,
+ int height);
+struct framebuffer framebuffer_cut_left_x(struct framebuffer fb, int offset);
+struct framebuffer framebuffer_cut_right_x(struct framebuffer fb, int offset);
+struct framebuffer framebuffer_cut_top_y(struct framebuffer fb, int offset);
+struct framebuffer framebuffer_cut_bottom_y(struct framebuffer fb, int offset);
+struct framebuffer framebuffer_center_x(struct framebuffer fb, int x, int width);
+struct framebuffer framebuffer_center_y(struct framebuffer fb, int y, int height);
+struct framebuffer framebuffer_right_x(struct framebuffer fb, int x, int width);
+struct framebuffer framebuffer_bottom_y(struct framebuffer fb, int y, int height);
+struct framebuffer framebuffer_center_text_x(struct framebuffer fb, int x, const char* str);
+struct framebuffer framebuffer_center_text_y(struct framebuffer fb, int y, const char* str);
+struct framebuffer framebuffer_right_text_x(struct framebuffer fb, int x, const char* str);
+struct framebuffer framebuffer_bottom_text_y(struct framebuffer fb, int y, const char* str);
+
+#endif
diff --git a/login/graphical.c b/login/graphical.c
new file mode 100644
index 00000000..3065fc1b
--- /dev/null
+++ b/login/graphical.c
@@ -0,0 +1,845 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ graphical.c
+ Graphical login.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// TODO: The Sortix doesn't expose this at the moment.
+#if !defined(HOST_NAME_MAX) && defined(__sortix__)
+#include
+#endif
+
+#include "framebuffer.h"
+#include "login.h"
+#include "pixel.h"
+#include "vgafont.h"
+
+#include "arrow.inc"
+
+enum stage
+{
+ STAGE_USERNAME,
+ STAGE_PASSWORD,
+ STAGE_CHECKING,
+};
+
+struct textbox
+{
+ char text[256];
+ size_t used;
+ size_t offset;
+ const char* standin;
+ bool password;
+};
+
+static uint32_t arrow_buffer[48 * 48];
+static struct framebuffer arrow_framebuffer = { 48, arrow_buffer, 48, 48 };
+
+static inline void arrow_initialize()
+{
+ static bool done = false;
+ if ( done )
+ return;
+ memcpy(arrow_buffer, arrow, sizeof(arrow));
+ done = true;
+}
+
+static struct textbox textbox_username;
+static struct textbox textbox_password;
+
+struct glogin
+{
+ struct check chk;
+ int fd_mouse;
+ struct dispmsg_crtc_mode mode;
+ struct framebuffer fade_from_fb;
+ struct timespec fade_from_begin;
+ struct timespec fade_from_end;
+ bool fading_from;
+ uint32_t* last_fb_buffer;
+ size_t last_fb_buffer_size;
+ int pointer_x;
+ int pointer_y;
+ size_t mouse_byte_count;
+ uint8_t mouse_bytes[3];
+ enum stage stage;
+ bool animating;
+ const char* warning;
+ bool pointer_working;
+};
+
+static struct glogin state;
+
+static bool get_graphical_mode(struct dispmsg_crtc_mode* mode)
+{
+ struct dispmsg_get_crtc_mode msg;
+ memset(&msg, 0, sizeof(msg));
+ msg.msgid = DISPMSG_GET_CRTC_MODE;
+ msg.device = 0; // TODO: Multi-screen support!
+ msg.connector = 0; // TODO: Multi-screen support!
+ if ( dispmsg_issue(&msg, sizeof(msg)) != 0 )
+ {
+ warn("dispmsg_issue: DISPMSG_GET_CRTC_MODE");
+ return false;
+ }
+ *mode = msg.mode;
+ return true;
+}
+
+static bool is_graphical_mode(struct dispmsg_crtc_mode* mode)
+{
+ return (mode->control & DISPMSG_CONTROL_VALID) &&
+ !(mode->control & DISPMSG_CONTROL_VGA) &&
+ mode->fb_format == 32;
+}
+
+static void textbox_initialize(struct textbox* textbox, const char* standin)
+{
+ memset(textbox, 0, sizeof(*textbox));
+ textbox->standin = standin;
+}
+
+static void textbox_reset(struct textbox* textbox)
+{
+ explicit_bzero(textbox->text, sizeof(textbox->text));
+ textbox->used = 0;
+ textbox->offset = 0;
+}
+
+static void textbox_type_char(struct textbox* textbox, char c)
+{
+ if ( textbox->used + 1 == sizeof(textbox->text) )
+ return;
+ memmove(textbox->text + textbox->offset + 1,
+ textbox->text + textbox->offset,
+ textbox->used - textbox->offset + 1);
+ textbox->text[textbox->offset++] = c;
+ textbox->used++;
+}
+
+static void textbox_type_backspace(struct textbox* textbox)
+{
+ if ( textbox->offset == 0 )
+ return;
+ memmove(textbox->text + textbox->offset - 1,
+ textbox->text + textbox->offset,
+ textbox->used - textbox->offset + 1);
+ textbox->offset--;
+ textbox->used--;
+}
+
+void render_right_text(struct framebuffer fb, const char* str, uint32_t color)
+{
+ size_t len = strlen(str);
+ for ( size_t i = 0; i < len; i++ )
+ {
+ int x = fb.xres - ((int) FONT_WIDTH+1) * ((int) len - (int) i);
+ render_char(framebuffer_crop(fb, x, 0, fb.xres, fb.yres), str[i], color);
+ }
+}
+
+void render_right_text_if_needed(struct framebuffer fb, const char* str, uint32_t color)
+{
+ size_t len = strlen(str);
+ size_t shown_len = fb.xres / (FONT_WIDTH+1);
+ if ( len <= shown_len )
+ render_text(fb, str, color);
+ else
+ render_right_text(fb, str, color);
+}
+
+static void render_background(struct framebuffer fb)
+{
+ uint32_t bg_color = make_color(0x89 * 2/3, 0xc7 * 2/3, 0xff * 2/3);
+ for ( size_t y = 0; y < fb.yres; y++ )
+ for ( size_t x = 0; x < fb.xres; x++ )
+ framebuffer_set_pixel(fb, x, y, bg_color);
+}
+
+static void render_pointer(struct framebuffer fb)
+{
+ int p_hwidth = arrow_framebuffer.xres / 2;
+ int p_hheight = arrow_framebuffer.yres / 2;
+ int p_x = state.pointer_x - p_hwidth;
+ int p_y = state.pointer_y - p_hheight;
+ struct framebuffer arrow_render = arrow_framebuffer;
+ if ( p_x < 0 )
+ {
+ arrow_render = framebuffer_crop(arrow_render, -p_x, 0, arrow_render.xres, arrow_render.yres);
+ p_x = 0;
+ }
+ if ( p_y < 0 )
+ {
+ arrow_render = framebuffer_crop(arrow_render, 0, -p_y, arrow_render.xres, arrow_render.yres);
+ p_y = 0;
+ }
+ struct framebuffer fb_dst = framebuffer_crop(fb, p_x, p_y, fb.xres, fb.yres);
+ framebuffer_copy_to_framebuffer_blend(fb_dst, arrow_render);
+}
+
+static char* brand_line()
+{
+ char version[64];
+ version[0] = '\0';
+ kernelinfo("version", version, sizeof(version));
+ char* result = NULL;
+ asprintf(&result, "%s %s - %s",
+ BRAND_OPERATING_SYSTEM_NAME,
+ version,
+ BRAND_DISTRIBUTION_WEBSITE);
+ return result;
+}
+
+static void render_information(struct framebuffer fb)
+{
+ struct framebuffer textfb;
+
+ char* brandstr = brand_line();
+ if ( brandstr )
+ {
+ textfb = fb;
+ textfb = framebuffer_center_text_x(textfb, fb.xres/2, brandstr);
+ textfb = framebuffer_bottom_text_y(textfb, fb.yres, brandstr);
+ render_text(textfb, brandstr, make_color(255, 255, 255));
+ free(brandstr);
+ }
+}
+
+static void render_textbox(struct framebuffer fb, struct textbox* textbox)
+{
+ for ( int y = 0; y < (int) fb.yres; y++ )
+ {
+ for ( int x = 0; x < (int) fb.xres; x++ )
+ {
+ uint32_t color;
+ if ( x == 0 || x == (int) fb.xres - 1 ||
+ y == 0 || y == (int) fb.yres - 1 )
+ color = make_color(32, 32, 32);
+ else
+ color = make_color(255, 255, 255);
+ framebuffer_set_pixel(fb, x, y, color);
+ }
+ }
+
+ fb = framebuffer_cut_left_x(fb, 6);
+ fb = framebuffer_cut_right_x(fb, 6);
+ fb = framebuffer_cut_top_y(fb, 6);
+ fb = framebuffer_cut_bottom_y(fb, 6);
+ if ( !textbox->used )
+ render_right_text_if_needed(fb, textbox->standin, make_color(160, 160, 160));
+ else if ( textbox->password )
+ {
+ int x = 0;
+ while ( x + (FONT_WIDTH+1) <= (int) fb.xres )
+ {
+ render_char(framebuffer_crop(fb, x, 0, fb.xres, fb.yres), '*',
+ make_color(200, 200, 200));
+ x += (FONT_WIDTH+1);
+ }
+ }
+ else
+ render_right_text_if_needed(fb, textbox->text, make_color(0, 0, 0));
+}
+
+static void render_form(struct framebuffer fb)
+{
+ int typearea_width = (FONT_WIDTH + 1) * 25;
+ int typearea_height = FONT_HEIGHT;
+ int textbox_margin = 6;
+ int textbox_width = typearea_width + 2 * textbox_margin;
+ int textbox_height = typearea_height + 2 * textbox_margin;
+ int form_margin = 10;
+ int form_width = textbox_width + 2 * form_margin;
+ int form_height = textbox_height + 2 * form_margin;
+ int BORDER_WIDTH = 8;
+ int TITLE_HEIGHT = 28;
+ int b0 = 0;
+ int b1 = 1;
+ int b2 = 2;
+ int b3 = BORDER_WIDTH;
+ int t0 = TITLE_HEIGHT;
+ int window_width = BORDER_WIDTH + form_width + BORDER_WIDTH;
+ int window_height = TITLE_HEIGHT + form_height + BORDER_WIDTH;
+
+ if ( state.warning )
+ {
+ struct framebuffer warnfb = fb;
+ int y = (fb.yres - 50 - window_height) / 2 - 2 * FONT_HEIGHT;
+ warnfb = framebuffer_cut_top_y(warnfb, y);
+ int w = strlen(state.warning) * (FONT_WIDTH+1);
+ warnfb = framebuffer_center_x(warnfb, fb.xres / 2, w);
+ render_text(warnfb, state.warning, make_color(255, 0, 0));
+ }
+
+ fb = framebuffer_center_x(fb, fb.xres / 2, window_width);
+ fb = framebuffer_center_y(fb, (fb.yres - 50) / 2, window_height);
+
+ uint32_t glass_color = make_color_a(200, 200, 255, 192);
+ uint32_t title_color = make_color_a(16, 16, 16, 240);
+
+ for ( int y = 0; y < (int) fb.yres; y++ )
+ {
+ for ( int x = 0; x < (int) fb.xres; x++ )
+ {
+ uint32_t color;
+ if ( x == b0 || x == (int) fb.xres - (b0+1) ||
+ y == b0 || y == (int) fb.yres - (b0+1) )
+ color = make_color_a(0, 0, 0, 32);
+ else if ( x == b1 || x == (int) fb.xres - (b1+1) ||
+ y == b1 || y == (int) fb.yres - (b1+1) )
+ color = make_color_a(0, 0, 0, 64);
+ else if ( x == b2 || x == (int) fb.xres - (b2+1) ||
+ y == b2 || y == (int) fb.yres - (b2+1) )
+ color = make_color(240, 240, 250);
+ else if ( x < (b3-1) || x > (int) fb.xres - (b3+1-1) ||
+ y < (t0-1) || y > (int) fb.yres - (b3+1-1) )
+ color = glass_color;
+ else if ( x == (b3-1) || x == (int) fb.xres - (b3+1-1) ||
+ y == (t0-1) || y == (int) fb.yres - (b3+1-1) )
+ color = make_color(64, 64, 64);
+ else
+ continue;
+ uint32_t bg = framebuffer_get_pixel(fb, x, y);
+ framebuffer_set_pixel(fb, x, y, blend_pixel(bg, color));
+ }
+ }
+
+ fb = framebuffer_cut_left_x(fb, BORDER_WIDTH);
+ fb = framebuffer_cut_right_x(fb, BORDER_WIDTH);
+
+ char hostname[HOST_NAME_MAX + 1];
+ hostname[0] = '\0';
+ gethostname(hostname, sizeof(hostname));
+ const char* tt = hostname;
+ size_t tt_length = strlen(tt);
+ size_t tt_max_width = fb.xres;
+ size_t tt_desired_width = tt_length * (FONT_WIDTH+1);
+ size_t tt_width = tt_desired_width < tt_max_width ? tt_desired_width : tt_max_width;
+ size_t tt_height = FONT_HEIGHT;
+ size_t tt_pos_x = BORDER_WIDTH + (tt_max_width - tt_width) / 2;
+ size_t tt_pos_y = (TITLE_HEIGHT - FONT_HEIGHT) / 2 + 2;
+ uint32_t tt_color = title_color;
+ render_text(framebuffer_crop(fb, tt_pos_x, tt_pos_y, tt_width, tt_height), tt, tt_color);
+
+ fb = framebuffer_cut_top_y(fb, TITLE_HEIGHT);
+ fb = framebuffer_cut_bottom_y(fb, BORDER_WIDTH);
+
+ for ( int y = 0; y < (int) fb.yres; y++ )
+ {
+ for ( int x = 0; x < (int) fb.xres; x++ )
+ {
+ framebuffer_set_pixel(fb, x, y, make_color(214, 214, 214));
+ }
+ }
+
+ struct framebuffer boxfb = fb;
+ boxfb = framebuffer_cut_left_x(boxfb, form_margin);
+ boxfb = framebuffer_cut_right_x(boxfb, form_margin);
+ boxfb = framebuffer_cut_top_y(boxfb, form_margin);
+ boxfb = framebuffer_cut_bottom_y(boxfb, form_margin);
+ switch ( state.stage )
+ {
+ case STAGE_USERNAME: render_textbox(boxfb, &textbox_username); break;
+ case STAGE_PASSWORD: render_textbox(boxfb, &textbox_password); break;
+ default: break;
+ }
+}
+
+static void render_progress(struct framebuffer fb)
+{
+ state.animating = true;
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ float time = (float) now.tv_sec + (float) now.tv_nsec * 10E-9;
+ float rotslow_cos = cos(-time / 30.0f * M_PI * 2.0);
+ float rotslow_sin = sin(-time / 30.0f * M_PI * 2.0);
+ int size = 32;
+ int width = 4;
+ float widthf = ((float) width / (float) size) * 2.0f;
+ float innersq = (1.0f - widthf) * (1.0f - widthf);
+ float outersq = (1.0f) * (1.0f);
+ fb = framebuffer_center_x(fb, fb.xres / 2, size);
+ fb = framebuffer_center_y(fb, (fb.yres - 50) / 2, size);
+ for ( size_t y = 0; y < fb.yres; y++ )
+ {
+ float yfi = ((float) y / (float) size) * 2.0f - 1.0f;
+ for ( size_t x = 0; x < fb.xres; x++ )
+ {
+ float xfi = ((float) x / (float) size) * 2.0f - 1.0f;
+ float distsq = xfi * xfi + yfi * yfi;
+ if ( distsq < innersq )
+ continue;
+ if ( distsq > outersq )
+ continue;
+ float af = fabs((distsq - innersq) / (outersq - innersq) * 2.0f - 1.0f);
+ af = 1.0 - af * af;
+ uint8_t a = (uint8_t) (af * 255.0f);
+ float xf = xfi;
+ float yf = yfi;
+ xf = rotslow_cos * xf + rotslow_sin * yf;
+ yf = -rotslow_sin * xf + rotslow_cos * yf;
+ if ( -widthf < yf && yf < widthf )
+ continue;
+ uint8_t r = 0;
+ uint8_t g = 127.5 + 127.5 * xf;
+ uint8_t b = 255;
+ uint32_t bg = framebuffer_get_pixel(fb, x, y);
+ uint32_t fg = make_color_a(r, g, b, a);
+ framebuffer_set_pixel(fb, x, y, blend_pixel(bg, fg));
+ }
+ }
+}
+
+static void render_login(struct framebuffer fb)
+{
+ render_background(fb);
+ if ( false )
+ render_information(fb);
+ switch ( state.stage )
+ {
+ case STAGE_USERNAME: render_form(fb); break;
+ case STAGE_PASSWORD: render_form(fb); break;
+ case STAGE_CHECKING: render_progress(fb); break;
+ }
+ if ( state.pointer_working )
+ render_pointer(fb);
+}
+
+static void glogin_fade_from_end(struct glogin* state)
+{
+ state->fading_from = false;
+ free(state->fade_from_fb.buffer);
+}
+
+static uint32_t* glogin_malloc_fb_buffer(struct glogin* state,
+ size_t size)
+{
+ if ( state->last_fb_buffer )
+ {
+ if ( state->last_fb_buffer_size == size )
+ {
+ uint32_t* result = state->last_fb_buffer;
+ state->last_fb_buffer = NULL;
+ return result;
+ }
+ free(state->last_fb_buffer);
+ state->last_fb_buffer = NULL;
+ }
+ uint32_t* result = (uint32_t*) malloc(size);
+ if ( !result )
+ {
+ glogin_fade_from_end(state);
+ result = (uint32_t*) malloc(size);
+ }
+ return result;
+}
+
+static void glogin_free_fb_buffer(struct glogin* state,
+ uint32_t* buffer,
+ size_t size)
+{
+ if ( state->last_fb_buffer )
+ free(state->last_fb_buffer);
+ state->last_fb_buffer = buffer;
+ state->last_fb_buffer_size = size;
+}
+
+static bool screen_capture(struct glogin* state, struct framebuffer* fb)
+{
+ fb->xres = state->mode.view_xres;
+ fb->yres = state->mode.view_yres;
+ fb->pitch = state->mode.view_xres;
+ size_t size = sizeof(uint32_t) * fb->xres * fb->yres;
+ fb->buffer = (uint32_t*) glogin_malloc_fb_buffer(state, size);
+ if ( !fb->buffer )
+ return false;
+ struct dispmsg_write_memory msg;
+ memset(&msg, 0, sizeof(msg));
+ msg.msgid = DISPMSG_READ_MEMORY;
+ msg.device = 0; // TODO: Multi-screen support!
+ msg.offset = 0; // TODO: mode.fb_location!
+ msg.size = fb->xres * fb->yres * sizeof(fb->buffer[0]);
+ msg.src = (uint8_t*) fb->buffer;
+ if ( dispmsg_issue(&msg, sizeof(msg)) != 0 )
+ {
+ warn("dispmsg_issue: DISPMSG_READ_MEMORY");
+ return false;
+ }
+ return true;
+}
+
+static bool begin_render(struct glogin* state, struct framebuffer* fb)
+{
+ if ( !get_graphical_mode(&state->mode) )
+ return false;
+ fb->xres = state->mode.view_xres;
+ fb->yres = state->mode.view_yres;
+ fb->pitch = state->mode.view_xres;
+ size_t size = sizeof(uint32_t) * fb->xres * fb->yres;
+ fb->buffer = (uint32_t*) glogin_malloc_fb_buffer(state, size);
+ if ( !fb->buffer )
+ {
+ warn("malloc");
+ return false;
+ }
+ return true;
+}
+
+static bool finish_render(struct glogin* state, struct framebuffer* fb)
+{
+ struct dispmsg_write_memory msg;
+ memset(&msg, 0, sizeof(msg));
+ msg.msgid = DISPMSG_WRITE_MEMORY;
+ msg.device = 0; // TODO: Multi-screen support!
+ msg.offset = 0; // TODO: mode.fb_location!
+ msg.size = sizeof(uint32_t) * fb->xres * fb->yres;
+ msg.src = (uint8_t*) fb->buffer;
+ if ( dispmsg_issue(&msg, sizeof(msg)) != 0 )
+ {
+ warn("dispmsg_issue: DISPMSG_WRITE_MEMORY");
+ free(fb->buffer);
+ return false;
+ }
+ glogin_free_fb_buffer(state, fb->buffer, msg.size);
+ return true;
+}
+
+static bool render(struct glogin* state)
+{
+ state->animating = false;
+ struct framebuffer fb;
+ if ( !begin_render(state, &fb) )
+ return false;
+ render_login(fb);
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ if ( state->fading_from && timespec_lt(now, state->fade_from_end) )
+ {
+ struct timespec duration_ts =
+ timespec_sub(state->fade_from_end, state->fade_from_begin);
+ struct timespec elapsed_ts = timespec_sub(now, state->fade_from_begin);
+ float duration = (float) duration_ts.tv_sec + (float) duration_ts.tv_nsec * 10E-9;
+ float elapsed = (float) elapsed_ts.tv_sec + (float) elapsed_ts.tv_nsec * 10E-9;
+ float fade_from_alpha_f = 255.0 * elapsed / duration;
+ if ( fade_from_alpha_f < 0.0f ) fade_from_alpha_f = 0.0f;
+ if ( fade_from_alpha_f > 255.0f ) fade_from_alpha_f = 255.0f;
+ uint8_t fade_from_alpha = (uint8_t) fade_from_alpha_f;
+ uint32_t and_mask = ~make_color(0, 0, 0);
+ uint32_t or_mask = make_color_a(0, 0, 0, 255 - fade_from_alpha);
+ for ( int y = 0; y < (int) state->fade_from_fb.yres; y++ )
+ {
+ for ( int x = 0; x < (int) state->fade_from_fb.xres; x++ )
+ {
+ uint32_t color = framebuffer_get_pixel(state->fade_from_fb, x, y);
+ color = (color & and_mask) | or_mask;
+ framebuffer_set_pixel(state->fade_from_fb, x, y, color);
+ }
+ }
+ framebuffer_copy_to_framebuffer_blend(fb, state->fade_from_fb);
+ state->animating = true;
+ }
+
+ else if ( state->fading_from )
+ glogin_fade_from_end(state);
+ if ( !finish_render(state, &fb) )
+ return false;
+ return true;
+}
+
+static void think(struct glogin* state)
+{
+ if ( state->stage == STAGE_CHECKING )
+ {
+ bool result;
+ if ( !check_end(&state->chk, &result, true) )
+ {
+ sched_yield();
+ return;
+ }
+ if ( result )
+ {
+ if ( !login(textbox_username.text) )
+ state->warning = strerror(errno);
+ state->stage = STAGE_USERNAME;
+ textbox_reset(&textbox_username);
+ }
+ else
+ {
+ state->stage = STAGE_USERNAME;
+ textbox_reset(&textbox_username);
+ if ( errno == EACCES )
+ state->warning = "Invalid username/password";
+ else
+ state->warning = strerror(errno);
+ }
+ }
+}
+
+static void keyboard_event(struct glogin* state, uint32_t codepoint)
+{
+ if ( codepoint == '\n' )
+ {
+ state->warning = NULL;
+ switch ( state->stage )
+ {
+ case STAGE_USERNAME:
+ if ( !strcmp(textbox_username.text, "exit") )
+ exit(0);
+ if ( !strcmp(textbox_username.text, "poweroff") )
+ exit(0);
+ if ( !strcmp(textbox_username.text, "reboot") )
+ exit(1);
+ state->stage = STAGE_PASSWORD;
+ textbox_reset(&textbox_password);
+ break;
+ case STAGE_PASSWORD:
+ state->stage = STAGE_CHECKING;
+ if ( !check_begin(&state->chk, textbox_username.text,
+ textbox_password.text, true) )
+ {
+ state->stage = STAGE_USERNAME;
+ state->warning = strerror(errno);
+ }
+ break;
+ case STAGE_CHECKING:
+ break;
+ }
+ return;
+ }
+ struct textbox* textbox = NULL;
+ switch ( state->stage )
+ {
+ case STAGE_USERNAME: textbox = &textbox_username; break;
+ case STAGE_PASSWORD: textbox = &textbox_password; break;
+ case STAGE_CHECKING: break;
+ }
+ if ( textbox && codepoint < 128 )
+ {
+ if ( codepoint == '\b' || codepoint == 127 )
+ textbox_type_backspace(textbox);
+ else
+ textbox_type_char(textbox, (char) codepoint);
+ }
+}
+
+static void mouse_event(struct glogin* state, unsigned char byte)
+{
+ state->pointer_working = true;
+ if ( state->mouse_byte_count == 0 && !(byte & 1 << 3) )
+ return;
+ if ( state->mouse_byte_count < 3 )
+ state->mouse_bytes[state->mouse_byte_count++] = byte;
+ if ( state->mouse_byte_count < 3 )
+ return;
+ state->mouse_byte_count = 0;
+ unsigned char* bytes = state->mouse_bytes;
+ int xm = bytes[1];
+ int ym = bytes[2];
+ if ( xm && bytes[0] & (1 << 4) )
+ xm = xm - 256;
+ if ( ym && bytes[0] & (1 << 5) )
+ ym = ym - 256;
+ if ( (bytes[0] & (1 << 6)) || (bytes[0] & (1 << 7)) )
+ {
+ xm = 0;
+ ym = 0;
+ }
+ ym = -ym;
+ int old_pointer_x = state->pointer_x;
+ int old_pointer_y = state->pointer_y;
+ if ( xm*xm + ym*ym >= 2*2 )
+ {
+ xm *= 2;
+ ym *= 2;
+ }
+ else if ( xm*xm + ym*ym >= 5*5 )
+ {
+ xm *= 3;
+ ym *= 3;
+ }
+ state->pointer_x += xm;
+ state->pointer_y += ym;
+ if ( state->pointer_x < 0 )
+ state->pointer_x = 0;
+ if ( state->pointer_y < 0 )
+ state->pointer_y = 0;
+ if ( state->mode.view_xres <= (size_t) state->pointer_x )
+ state->pointer_x = state->mode.view_xres;
+ if ( state->mode.view_yres <= (size_t) state->pointer_y )
+ state->pointer_y = state->mode.view_yres;
+ xm = state->pointer_x - old_pointer_x;
+ ym = state->pointer_y - old_pointer_y;
+ if ( (bytes[0] & 1 << 0) )
+ {
+ (void) xm;
+ (void) ym;
+ }
+}
+
+void glogin_destroy(struct glogin* state)
+{
+ if ( 0 <= state->fd_mouse )
+ close(state->fd_mouse);
+ if ( state->fading_from )
+ free(state->fade_from_fb.buffer);
+}
+
+bool glogin_init(struct glogin* state)
+{
+ memset(state, 0, sizeof(*state));
+ state->fd_mouse = -1;
+
+ if ( !get_graphical_mode(&state->mode) )
+ {
+ warn("dispmsg_issue");
+ glogin_destroy(state);
+ return false;
+ }
+ if ( !is_graphical_mode(&state->mode) ||
+ state->mode.view_xres < 128 ||
+ state->mode.view_yres < 128 )
+ {
+ glogin_destroy(state);
+ return false;
+ }
+ if ( !load_font() )
+ {
+ warn("/dev/vgafont");
+ glogin_destroy(state);
+ return false;
+ }
+ state->fd_mouse = open("/dev/mouse", O_RDONLY | O_CLOEXEC);
+ if ( settermmode(0, TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_NONBLOCK) < 0 )
+ {
+ warn("settermmode");
+ return false;
+ }
+ fsync(0);
+ arrow_initialize();
+ textbox_initialize(&textbox_username, "Username");
+ textbox_initialize(&textbox_password, "Password");
+ textbox_password.password = true;
+ state->pointer_x = state->mode.view_xres / 2;
+ state->pointer_y = state->mode.view_yres / 2;
+ if ( screen_capture(state, &state->fade_from_fb) )
+ {
+ state->fading_from = true;
+ clock_gettime(CLOCK_MONOTONIC, &state->fade_from_begin);
+ struct timespec duration = timespec_make(0, 150*1000*1000);
+ state->fade_from_end = timespec_add(state->fade_from_begin, duration);
+ }
+ return true;
+}
+
+int glogin_main(struct glogin* state)
+{
+ while ( true )
+ {
+ think(state);
+ if ( !render(state) )
+ break;
+ struct pollfd pfds[2];
+ memset(pfds, 0, sizeof(pfds));
+ pfds[0].fd = -1;
+ pfds[1].fd = -1;
+ if ( state->stage != STAGE_CHECKING )
+ {
+ pfds[0].fd = 0;
+ pfds[0].events = POLLIN;
+ pfds[0].revents = 0;
+ }
+ if ( 0 <= state->fd_mouse )
+ {
+ pfds[1].fd = state->fd_mouse;
+ pfds[1].events = POLLIN;
+ pfds[1].revents = 0;
+ }
+ nfds_t nfds = 2;
+ struct timespec wake_now_ts = timespec_make(0, 0);
+ struct timespec* wake = state->animating ? &wake_now_ts : NULL;
+ int num_events = ppoll(pfds, nfds, wake, NULL);
+ if ( num_events < 0 )
+ {
+ warn("poll");
+ break;
+ }
+ for ( nfds_t i = 0; i < nfds; i++ )
+ {
+ if ( pfds[i].fd == -1 )
+ continue;
+ if ( (pfds[i].revents & POLLERR) ||
+ (pfds[i].revents & POLLHUP) ||
+ (pfds[i].revents & POLLNVAL) )
+ {
+ warnx("poll failure on %s", i == 0 ? "keyboard" : "mouse");
+ break;
+ }
+ }
+ if ( pfds[0].fd != -1 && pfds[0].revents )
+ {
+ uint32_t codepoint;
+ while ( read(0, &codepoint, sizeof(codepoint)) == sizeof(codepoint) )
+ keyboard_event(state, codepoint);
+ }
+ if ( pfds[1].fd != -1 && pfds[1].revents )
+ {
+ unsigned char events[64];
+ ssize_t amount = read(state->fd_mouse, events, sizeof(events));
+ for ( ssize_t i = 0; i < amount; i++ )
+ mouse_event(state, events[i]);
+ }
+ }
+ return -1;
+}
+
+int graphical(void)
+{
+ if ( access("/etc/login.conf.textual", F_OK) == 0 )
+ return -1;
+ if ( !glogin_init(&state) )
+ return -1;
+ int result = glogin_main(&state);
+ glogin_destroy(&state);
+ return result;
+}
diff --git a/login/login.8 b/login/login.8
new file mode 100644
index 00000000..ab3545a1
--- /dev/null
+++ b/login/login.8
@@ -0,0 +1,74 @@
+.Dd $Mdocdate: October 6 2015 $
+.Dt LOGIN 8
+.Os
+.Sh NAME
+.Nm login
+.Nd authenticate users and run personal session
+.Sh SYNOPSIS
+.Nm login
+.Sh DESCRIPTION
+.Nm login
+interactively authenticates users by asking them to enter their username and
+password. The passwords are checked against the password hashes in
+.Pa /etc/passwd
+as described in
+.Xr passwd 5 .
+.Nm login
+creates a session as the requested user upon successful authentication.
+.Pp
+.Nm login
+has a graphical interface if the display is graphical and uses a textual
+interface otherwise. The textual interface is forced if
+.Pa /etc/login.conf.textual
+exists. The process remains running in the background and takes
+over again when the user session exits.
+.Pp
+Type a special username to perform special options:
+.Pp
+.Bl -tag -width "poweroff" -compact -offset indent
+.It exit
+alias for poweroff
+.It poweroff
+exit asking for powering off the computer
+.It reboot
+exit asking for rebooting the computer
+.El
+.Sh SECURITY
+There is currently no method to confirm the login screen is in fact real other
+than witnessing a pristine boot. Local users can log in and show a counterfeit
+login screen that look and behave like the real
+.Nm login
+program and trick the next user into revealing their password.
+.Sh ENVIRONMENT
+.Nm login
+sets the following environment variables to match the authenticated user:
+.Bl -tag -width "LOGNAME"
+.It Ev HOME
+home directory
+.It Ev LOGNAME
+username
+.It Ev SHELL
+shell
+.It Ev USER
+username
+.El
+.Sh FILES
+.Bl -tag -width "/etc/passwd" -compact
+.It Pa /etc/passwd
+user database (see
+.Xr passwd 5 )
+.It Pa /etc/login.conf.textual
+textual interface is forced if this file exists
+.El
+.Sh EXIT STATUS
+.Nm login
+exits 0 if the computer should power off, exits 1 if the computer should
+reboot, or exits 2 on fatal failure and the boot should halt.
+.Sh SEE ALSO
+.Xr crypt_checkpass 3 ,
+.Xr passwd 5 ,
+.Xr init 8 ,
+.Xr login 8
+.Sh BUGS
+.Nm login
+only supports a single monitor. The mouse code is less than perfect.
diff --git a/login/login.c b/login/login.c
new file mode 100644
index 00000000..375d2eb1
--- /dev/null
+++ b/login/login.c
@@ -0,0 +1,438 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ login.c
+ Authenticates users.
+
+*******************************************************************************/
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// TODO: The Sortix doesn't expose this at the moment.
+#if !defined(HOST_NAME_MAX) && defined(__sortix__)
+#include
+#endif
+
+#include "login.h"
+
+static void on_interrupt_signal(int signum)
+{
+ if ( signum == SIGINT )
+ dprintf(1, "^C");
+ if ( signum == SIGQUIT )
+ dprintf(1, "^\\");
+}
+
+bool check_real(const char* username, const char* password)
+{
+ char fakehashbuf[128];
+ char goodhashbuf[128];
+ size_t fakematch = 0;
+ size_t goodmatch = 0;
+ const char* fakehash = NULL;
+ const char* goodhash = NULL;
+ setpwent();
+ struct passwd* pwd;
+ while ( (errno = 0, pwd = getpwent()) )
+ {
+ if ( !strcmp(username, pwd->pw_name) )
+ {
+ strlcpy(goodhashbuf, pwd->pw_passwd, sizeof(goodhashbuf));
+ goodhash = goodhashbuf;
+ goodmatch++;
+ }
+ else
+ {
+ strlcpy(fakehashbuf, pwd->pw_passwd, sizeof(fakehashbuf));
+ fakehash = fakehashbuf;
+ fakematch++;
+ }
+ }
+ int errnum = errno;
+ endpwent();
+ if ( errnum != 0 )
+ return errno = errnum, false;
+ if ( 1 < goodmatch )
+ return errno = EACCES, false;
+ errno = 0;
+ (void) fakehash;
+ return crypt_checkpass(password, goodhash) == 0;
+}
+
+bool check_begin(struct check* chk,
+ const char* username,
+ const char* password,
+ bool restrict_termmode)
+{
+ memset(chk, 0, sizeof(*chk));
+ if ( tcgetattr(0, &chk->tio) )
+ return false;
+ int pipe_fds[2];
+ if ( pipe2(pipe_fds, O_CLOEXEC) < 0 )
+ return false;
+ sigset_t sigttou;
+ sigemptyset(&sigttou);
+ sigaddset(&sigttou, SIGTTOU);
+ sigprocmask(SIG_BLOCK, &sigttou, &chk->oldset);
+ if ( (chk->pid = fork()) < 0 )
+ return close(pipe_fds[0]), close(pipe_fds[1]), false;
+ int success = -2;
+ if ( chk->pid == 0 )
+ {
+ sigdelset(&chk->oldset, SIGINT);
+ sigdelset(&chk->oldset, SIGQUIT);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ unsigned int termmode = TERMMODE_UNICODE | TERMMODE_SIGNAL |
+ TERMMODE_UTF8 | TERMMODE_LINEBUFFER |
+ TERMMODE_ECHO;
+ if ( restrict_termmode )
+ termmode = TERMMODE_SIGNAL;
+ if ( setpgid(0, 0) < 0 ||
+ close(pipe_fds[0]) < 0 ||
+ tcsetpgrp(0, getpgid(0)) ||
+ sigprocmask(SIG_SETMASK, &chk->oldset, NULL) < 0 ||
+ settermmode(0, termmode) < 0 ||
+ !check_real(username, password) ||
+ write(pipe_fds[1], &success, sizeof(success)) < 0 )
+ {
+ assert(1 <= errno);
+ write(pipe_fds[1], &errno, sizeof(errno));
+ }
+ _exit(0);
+ }
+ close(pipe_fds[1]);
+ chk->pipe = pipe_fds[0];
+ return true;
+}
+
+bool check_end(struct check* chk, bool* result, bool try)
+{
+ if ( try && !chk->pipe_nonblock )
+ {
+ fcntl(chk->pipe, F_SETFL, fcntl(chk->pipe, F_GETFL) | O_NONBLOCK);
+ chk->pipe_nonblock = true;
+ }
+ while ( chk->errnum_done < sizeof(chk->errnum_bytes) )
+ {
+ ssize_t amount = read(chk->pipe, chk->errnum_bytes + chk->errnum_done,
+ sizeof(chk->errnum_bytes) - chk->errnum_done);
+ if ( amount <= 0 )
+ {
+ if ( amount == 0 )
+ errno = EOF;
+ break;
+ }
+ chk->errnum_done += amount;
+ }
+ int code;
+ pid_t wait_ret = waitpid(chk->pid, &code, try ? WNOHANG : 0);
+ if ( try && wait_ret == 0 )
+ return false;
+ tcsetattr(0, TCSAFLUSH, &chk->tio);
+ tcsetpgrp(0, getpgid(0));
+ sigprocmask(SIG_SETMASK, &chk->oldset, NULL);
+ if ( wait_ret < 0 )
+ return *result = false, true;
+ if ( chk->errnum_done < sizeof(chk->errnum_bytes) )
+ chk->errnum = EEOF;
+ if ( WIFSIGNALED(code) )
+ chk->errnum = EINTR;
+ else if ( !(WIFEXITED(code) && WEXITSTATUS(code) == 0) )
+ chk->errnum = EINVAL;
+ int success = -2;
+ if ( chk->errnum < 1 && chk->errnum != success )
+ chk->errnum = EINVAL;
+ if ( chk->errnum != success )
+ return errno = chk->errnum, *result = false, true;
+ return *result = true, true;
+}
+
+bool check(const char* username, const char* password)
+{
+ struct check chk;
+ if ( !check_begin(&chk, username, password, false) )
+ return false;
+ bool result;
+ check_end(&chk, &result, false);
+ return result;
+}
+
+static int setcloexecfrom(int from)
+{
+ int fd = from - 1;
+ while ( (fd = fcntl(fd, F_NEXTFD)) != -1 )
+ {
+ int flags = fcntl(fd, F_GETFD);
+ if ( flags < 0 )
+ return -1;
+ if ( !(flags & FD_CLOEXEC) && fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0 )
+ return -1;
+ }
+ return 0;
+}
+
+bool login(const char* username)
+{
+ char login_pid[sizeof(pid_t) * 3];
+ snprintf(login_pid, sizeof(login_pid), "%" PRIiPID, getpid());
+ struct passwd* pwd = getpwnam(username);
+ if ( !pwd )
+ return false;
+ struct termios tio;
+ if ( tcgetattr(0, &tio) )
+ return false;
+ int pipe_fds[2];
+ if ( pipe2(pipe_fds, O_CLOEXEC) < 0 )
+ return false;
+ sigset_t oldset, sigttou;
+ sigemptyset(&sigttou);
+ sigaddset(&sigttou, SIGTTOU);
+ sigprocmask(SIG_BLOCK, &sigttou, &oldset);
+ pid_t child_pid = fork();
+ if ( child_pid < 0 )
+ return close(pipe_fds[0]), close(pipe_fds[1]), false;
+ if ( child_pid == 0 )
+ {
+ sigdelset(&oldset, SIGINT);
+ sigdelset(&oldset, SIGQUIT);
+ sigdelset(&oldset, SIGTSTP);
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ (void) (
+ setpgid(0, 0) < 0 ||
+ close(pipe_fds[0]) < 0 ||
+ setgid(pwd->pw_gid) < 0 ||
+ setuid(pwd->pw_uid) < 0 ||
+ setenv("LOGIN_PID", login_pid, 1) < 0 ||
+ setenv("LOGNAME", pwd->pw_name, 1) < 0 ||
+ setenv("USER", pwd->pw_name, 1) < 0 ||
+ chdir(pwd->pw_dir) < 0 ||
+ setenv("HOME", pwd->pw_dir, 1) < 0 ||
+ setenv("SHELL", pwd->pw_shell, 1) < 0 ||
+ close(0) < 0 ||
+ close(1) < 0 ||
+ close(2) < 0 ||
+ open("/dev/tty", O_RDONLY) != 0 ||
+ open("/dev/tty", O_WRONLY) != 1 ||
+ open("/dev/tty", O_WRONLY) != 2 ||
+ setcloexecfrom(3) < 0 ||
+ tcsetpgrp(0, getpgid(0)) ||
+ sigprocmask(SIG_SETMASK, &oldset, NULL) < 0 ||
+ settermmode(0, TERMMODE_NORMAL) < 0 ||
+ execlp(pwd->pw_shell, pwd->pw_shell, (const char*) NULL));
+ write(pipe_fds[1], &errno, sizeof(errno));
+ _exit(127);
+ }
+ close(pipe_fds[1]);
+ int errnum;
+ if ( readall(pipe_fds[0], &errnum, sizeof(errnum)) < (ssize_t) sizeof(errnum) )
+ errnum = 0;
+ close(pipe_fds[0]);
+ int child_status;
+ if ( waitpid(child_pid, &child_status, 0) < 0 )
+ errnum = errno;
+ tcsetattr(0, TCSAFLUSH, &tio);
+ tcsetpgrp(0, getpgid(0));
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ dprintf(1, "\e[H\e[2J");
+ fsync(1);
+ if ( errnum != 0 )
+ return errno = errnum, false;
+ return true;
+}
+
+static bool read_terminal_line(char* buffer, size_t size)
+{
+ assert(size);
+ size--;
+ sigset_t intset;
+ sigemptyset(&intset);
+ sigaddset(&intset, SIGINT);
+ sigaddset(&intset, SIGQUIT);
+ bool newline = false;
+ size_t sofar = 0;
+ while ( !newline && sofar < size )
+ {
+ sigset_t oldset;
+ sigprocmask(SIG_UNBLOCK, &intset, &oldset);
+ ssize_t amount = read(0, buffer + sofar, size - sofar);
+ sigprocmask(SIG_SETMASK, &oldset, NULL);
+ if ( amount <= 0 )
+ return false;
+ for ( ssize_t i = 0; i < amount; i++ )
+ {
+ if ( buffer[sofar + i] != '\n' )
+ continue;
+ newline = true;
+ amount = i;
+ break;
+ }
+ sofar += amount;
+ }
+ while ( !newline )
+ {
+ char c;
+ if ( read(0, &c, 1) <= 0 )
+ return false;
+ newline = c == '\n';
+ }
+ buffer[sofar] = '\0';
+ return true;
+}
+
+int textual(void)
+{
+ unsigned int termmode = TERMMODE_UNICODE | TERMMODE_SIGNAL | TERMMODE_UTF8 |
+ TERMMODE_LINEBUFFER | TERMMODE_ECHO;
+ if ( settermmode(0, termmode) < 0 )
+ err(2, "settermmode");
+ unsigned int pw_termmode = termmode & ~(TERMMODE_ECHO);
+
+ while ( true )
+ {
+ char hostname[HOST_NAME_MAX + 1];
+ hostname[0] = '\0';
+ gethostname(hostname, sizeof(hostname));
+ printf("%s login: ", hostname);
+ fflush(stdout);
+ char username[256];
+ errno = 0;
+ if ( !read_terminal_line(username, sizeof(username)) )
+ {
+ printf("\n");
+ if ( errno && errno != EINTR )
+ {
+ warn("fgets");
+ sleep(1);
+ }
+ printf("\n");
+ continue;
+ }
+
+ if ( !strcmp(username, "exit") )
+ exit(0);
+ if ( !strcmp(username, "poweroff") )
+ exit(0);
+ if ( !strcmp(username, "reboot") )
+ exit(1);
+
+ if ( settermmode(0, pw_termmode) < 0 )
+ err(2, "settermmode");
+ printf("Password (will not echo): ");
+ fflush(stdout);
+ char password[256];
+ errno = 0;
+ bool password_success = read_terminal_line(password, sizeof(password));
+ printf("\n");
+ if ( settermmode(0, termmode) < 0 )
+ err(2, "settermmode");
+ if ( !password_success )
+ {
+ if ( errno && errno != EINTR )
+ {
+ warn("fgets");
+ sleep(1);
+ }
+ printf("\n");
+ continue;
+ }
+
+ bool result = check(username, password);
+ explicit_bzero(password, sizeof(password));
+ if ( !result )
+ {
+ const char* msg = "Invalid username/password";
+ if ( errno != EACCES )
+ msg = strerror(errno);
+ printf("%s\n", msg);
+ printf("\n");
+ continue;
+ }
+
+ if ( !login(username) )
+ {
+ warn("logging in as %s", username);
+ printf("\n");
+ continue;
+ }
+ }
+
+ return 0;
+}
+
+int main(void)
+{
+ setlocale(LC_ALL, "");
+ if ( getuid() != 0 )
+ errx(2, "must be user root");
+ if ( getgid() != 0 )
+ errx(2, "must be group root");
+ if ( !isatty(0) )
+ {
+ close(0);
+ if ( open("/dev/tty", O_RDONLY) != 0 )
+ err(2, "/dev/tty");
+ }
+ if ( !isatty(1) )
+ {
+ close(1);
+ if ( open("/dev/tty", O_WRONLY) != 0 )
+ err(2, "/dev/tty");
+ }
+ if ( !isatty(2) )
+ {
+ if ( dup2(1, 2) < 0 )
+ err(2, "dup2");
+ }
+ if ( tcgetpgrp(0) != getpgid(0) )
+ errx(2, "must be in foreground process group");
+ if ( getpgid(0) != getpid() )
+ errx(2, "must be progress group leader");
+ struct sigaction sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sa_handler = on_interrupt_signal;
+ sigaddset(&sa.sa_mask, SIGINT);
+ sigaddset(&sa.sa_mask, SIGQUIT);
+ sigaction(SIGINT, &sa, NULL);
+ sigaction(SIGQUIT, &sa, NULL);
+ sigaddset(&sa.sa_mask, SIGTSTP);
+ sigprocmask(SIG_BLOCK, &sa.sa_mask, NULL);
+ int result = -1;
+ if ( result == -1 )
+ result = graphical();
+ if ( result == -1 )
+ result = textual();
+ return result;
+}
diff --git a/login/login.h b/login/login.h
new file mode 100644
index 00000000..a745a4af
--- /dev/null
+++ b/login/login.h
@@ -0,0 +1,52 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ login.h
+ Authenticates users.
+
+*******************************************************************************/
+
+#ifndef LOGIN_H
+#define LOGIN_H
+
+struct check
+{
+ sigset_t oldset;
+ struct termios tio;
+ pid_t pid;
+ int pipe;
+ union
+ {
+ int errnum;
+ unsigned char errnum_bytes[sizeof(int)];
+ };
+ unsigned int errnum_done;
+ bool pipe_nonblock;
+};
+
+bool login(const char* username);
+bool check_real(const char* username, const char* password);
+bool check_begin(struct check* chk,
+ const char* username,
+ const char* password,
+ bool restrict_termmode);
+bool check_end(struct check* chk, bool* result, bool try);
+bool check(const char* username, const char* password);
+int graphical(void);
+int textual(void);
+
+#endif
diff --git a/login/pixel.c b/login/pixel.c
new file mode 100644
index 00000000..8bc838e0
--- /dev/null
+++ b/login/pixel.c
@@ -0,0 +1,41 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ pixel.c
+ Pixel utilities.
+
+*******************************************************************************/
+
+#include
+
+#include "pixel.h"
+
+uint32_t blend_pixel(uint32_t bg_value, uint32_t fg_value)
+{
+ union color_rgba8 fg; fg.value = fg_value;
+ union color_rgba8 bg; bg.value = bg_value;
+ if ( fg.a == 255 )
+ return fg.value;
+ if ( fg.a == 0 )
+ return bg.value;
+ union color_rgba8 ret;
+ ret.a = 255;
+ ret.r = ((255-fg.a)*bg.r + fg.a*fg.r) / 256;
+ ret.g = ((255-fg.a)*bg.g + fg.a*fg.g) / 256;
+ ret.b = ((255-fg.a)*bg.b + fg.a*fg.b) / 256;
+ return ret.value;
+}
diff --git a/login/pixel.h b/login/pixel.h
new file mode 100644
index 00000000..a963b316
--- /dev/null
+++ b/login/pixel.h
@@ -0,0 +1,60 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ pixel.h
+ Pixel utilities.
+
+*******************************************************************************/
+
+#ifndef PIXEL_H
+#define PIXEL_H
+
+#include
+
+// TODO: This isn't the only pixel format in the world!
+union color_rgba8
+{
+ struct
+ {
+ uint8_t b;
+ uint8_t g;
+ uint8_t r;
+ uint8_t a;
+ };
+ uint32_t value;
+};
+
+__attribute__((used))
+static inline uint32_t make_color_a(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ union color_rgba8 color;
+ color.r = r;
+ color.g = g;
+ color.b = b;
+ color.a = a;
+ return color.value;
+}
+
+__attribute__((used))
+static inline uint32_t make_color(uint8_t r, uint8_t g, uint8_t b)
+{
+ return make_color_a(r, g, b, 255);
+}
+
+uint32_t blend_pixel(uint32_t bg_value, uint32_t fg_value);
+
+#endif
diff --git a/login/vgafont.c b/login/vgafont.c
new file mode 100644
index 00000000..300e5ca4
--- /dev/null
+++ b/login/vgafont.c
@@ -0,0 +1,75 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ vgafont.c
+ VGA font.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+
+#include "framebuffer.h"
+#include "vgafont.h"
+
+unsigned char font[FONT_CHARSIZE * FONT_NUMCHARS];
+
+bool load_font()
+{
+ static bool done = false;
+ if ( done )
+ return true;
+ int fd = open("/dev/vgafont", O_RDONLY);
+ if ( fd < 0 )
+ return false;
+ if ( readall(fd, font, sizeof(font)) != sizeof(font) )
+ return false;
+ close(fd);
+ return done = true;
+}
+
+void render_char(struct framebuffer fb, char c, uint32_t color)
+{
+ unsigned char uc = (unsigned char) c;
+
+ uint32_t buffer[FONT_HEIGHT * (FONT_WIDTH+1)];
+ for ( size_t y = 0; y < FONT_HEIGHT; y++ )
+ {
+ unsigned char line_bitmap = font[uc * FONT_CHARSIZE + y];
+ for ( size_t x = 0; x < FONT_WIDTH; x++ )
+ buffer[y * (FONT_WIDTH+1) + x] = line_bitmap & 1U << (7 - x) ? color : 0;
+ buffer[y * (FONT_WIDTH+1) + 8] = 0; //line_bitmap & 1U << 0 ? color : 0;
+ }
+
+ struct framebuffer character_fb;
+ character_fb.xres = FONT_WIDTH + 1;
+ character_fb.yres = FONT_HEIGHT;
+ character_fb.pitch = character_fb.xres;
+ character_fb.buffer = buffer;
+
+ framebuffer_copy_to_framebuffer_blend(fb, character_fb);
+}
+
+void render_text(struct framebuffer fb, const char* str, uint32_t color)
+{
+ for ( size_t i = 0; str[i]; i++ )
+ render_char(framebuffer_crop(fb, (FONT_WIDTH+1) * i, 0, fb.xres, fb.yres),
+ str[i], color);
+}
diff --git a/login/vgafont.h b/login/vgafont.h
new file mode 100644
index 00000000..2acb7bff
--- /dev/null
+++ b/login/vgafont.h
@@ -0,0 +1,41 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2014, 2015.
+
+ This program is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation, either version 3 of the License, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ more details.
+
+ You should have received a copy of the GNU General Public License along with
+ this program. If not, see .
+
+ vgafont.h
+ VGA font.
+
+*******************************************************************************/
+
+#ifndef VGAFONT_H
+#define VGAFONT_H
+
+#include
+
+#include "framebuffer.h"
+
+#define FONT_WIDTH 8
+#define FONT_HEIGHT 16
+#define FONT_NUMCHARS 256
+#define FONT_CHARSIZE ((FONT_WIDTH * FONT_HEIGHT) / 8)
+
+extern uint8_t font[FONT_CHARSIZE * FONT_NUMCHARS];
+
+bool load_font();
+void render_char(struct framebuffer fb, char c, uint32_t color);
+void render_text(struct framebuffer fb, const char* str, uint32_t color);
+
+#endif
diff --git a/share/man/man7/user-guide.7 b/share/man/man7/user-guide.7
index de129f74..4215e8d7 100644
--- a/share/man/man7/user-guide.7
+++ b/share/man/man7/user-guide.7
@@ -13,9 +13,16 @@ You will be presented a with standard Unix command line environment upon booting
the live environment.
.Ss Shutdown
.Xr init 8
-spawns a session after boot. This is a root shell if booted in
+spawns a session after boot. This is
+.Xr login 8
+if the system is booted in multi-user mode. This is a root shell if booted in
single-user mode.
.Pp
+To power off from the login screen, login as user
+.Sy poweroff .
+To reboot, login as user
+.Sy reboot .
+.Pp
To power off from a single-user boot root shell, run
.Sy exit 0
in the shell. To reboot, run
diff --git a/trianglix/trianglix.cpp b/trianglix/trianglix.cpp
index 14bbdcbd..ec7d97d8 100644
--- a/trianglix/trianglix.cpp
+++ b/trianglix/trianglix.cpp
@@ -780,7 +780,9 @@ class action** administration::list_actions(size_t* num_actions)
{
class action** actions = new action*[4 + 1];
size_t index = 0;
+#if 0 // TODO: Until crypt_newhash is used for the password.
actions[index++] = new action("Create user", new create_user());
+#endif
actions[index++] = new action("Enable Runes", new decide_runes(true));
actions[index++] = new action("Disable Runes", new decide_runes(false));
actions[index++] = new action("Trinit Core", new core());
@@ -788,6 +790,23 @@ class action** administration::list_actions(size_t* num_actions)
return *num_actions = index, actions;
}
+class exiter : public object
+{
+public:
+ exiter() { }
+ virtual ~exiter() { }
+
+public:
+ virtual enum object_type type() { return TYPE_FILE; }
+ virtual void invoke();
+
+};
+
+void exiter::invoke()
+{
+ exit(0);
+}
+
class desktop : public object
{
public:
@@ -811,7 +830,7 @@ class action** desktop::list_actions(size_t* num_actions)
actions[3] = new action("Shell", new path_program("sh"));
actions[4] = new action("Development", new development());
actions[5] = new action("Administration", new administration());
- actions[6] = new action("Logout", parent_object);
+ actions[6] = new action("Logout", new exiter());
return actions;
}
@@ -830,109 +849,6 @@ class object* log_user_in(struct passwd* user)
return new desktop();
}
-class poweroff : public object
-{
-public:
- poweroff() { }
- virtual ~poweroff() { }
-
-public:
- virtual enum object_type type() { return TYPE_FILE; }
- virtual void invoke();
-
-};
-
-void poweroff::invoke()
-{
- exit(0);
-}
-
-class login : public object
-{
-public:
- login(const char* username) : username(strdup(username)) { }
- virtual ~login() { }
-
-public:
- virtual enum object_type type() { return TYPE_DIRECTORY; }
- virtual class object* factory();
- virtual const char* title() { return "Authentication required "; }
- virtual const char* prompt() { return "Enter Password:"; }
- virtual bool is_password_prompt() { return true; }
- virtual class action** list_actions(size_t* num_actions);
- virtual class object* command_line(const char* command);
-
-private:
- char* username;
-
-};
-
-class user_selection : public object
-{
-public:
- user_selection() { }
- virtual ~user_selection() { }
-
-public:
- virtual enum object_type type() { return TYPE_DIRECTORY; }
- virtual const char* title() { return "User Selection"; }
- virtual class action** list_actions(size_t* num_actions);
- virtual class object* command_line(const char* command);
-
-};
-
-class object* login::factory()
-{
- if ( struct passwd* user = getpwnam(username) )
- if ( !user->pw_passwd[0] )
- return log_user_in(user);
- return NULL;
-}
-
-class action** login::list_actions(size_t* num_actions)
-{
- return *num_actions = 0, new class action*[0];
-}
-
-class object* login::command_line(const char* password)
-{
- error_string = "";
- if ( struct passwd* user = getpwnam(username) )
- {
- if ( !strcmp(user->pw_passwd, password) )
- return log_user_in(user);
- else
- return error_string = "Invalid password", (class object*) NULL;
- }
- return error_string = "No such user", (class object*) NULL;
-}
-
-class action** user_selection::list_actions(size_t* num_actions)
-{
- size_t num_users = 0;
- FILE* fp = openpw();
- while ( fgetpwent(fp) )
- num_users++;
- fseeko(fp, 0, SEEK_SET);
- action** actions = new class action*[num_users + 1];
- size_t which_user = 0;
- while ( struct passwd* user = fgetpwent(fp) )
- actions[which_user++] =
- new action(user->pw_gecos ? user->pw_gecos : user->pw_name,
- new login(user->pw_name));
- fclose(fp);
- actions[num_users] = new action("Poweroff", new poweroff);
- return *num_actions = num_users + 1, actions;
-}
-
-class object* user_selection::command_line(const char* command)
-{
- error_string = "";
- if ( getpwnam(command) )
- return new login(command);
- return error_string = "No such user", (class object*) NULL;
-}
-
class FrameBufferInfo;
struct Desktop;
struct RenderInfo;
@@ -1764,7 +1680,7 @@ static void InitializeDesktop(struct Desktop* desktop)
desktop->rshift = false;
desktop->actions = NULL;
desktop->num_actions = 0;
- desktop->object = new user_selection();
+ desktop->object = new class desktop();
UpdateActionList(desktop);
}
diff --git a/utils/command-not-found.cpp b/utils/command-not-found.cpp
index e5b96ffc..d81f114e 100644
--- a/utils/command-not-found.cpp
+++ b/utils/command-not-found.cpp
@@ -23,6 +23,7 @@
*******************************************************************************/
#include
+#include
#include
void suggest_editor(const char* filename)
@@ -49,6 +50,40 @@ void suggest_unmount(const char* filename)
fprintf(stderr, " Command 'unmount' from package 'utils'\n");
}
+void suggest_logout(const char* filename)
+{
+ fprintf(stderr, "No command '%s' found, did you mean:\n", filename);
+ fprintf(stderr, " Exiting your shell normally to logout.\n");
+}
+
+void suggest_poweroff(const char* filename)
+{
+ fprintf(stderr, "No command '%s' found, did you mean:\n", filename);
+ if ( getenv("LOGIN_PID") )
+ {
+ fprintf(stderr, " Exiting your shell normally to logout.\n");
+ fprintf(stderr, " Login as user 'poweroff' to power off computer.\n");
+ }
+ else
+ {
+ fprintf(stderr, " Exiting your shell normally to poweroff.\n");
+ }
+}
+
+void suggest_reboot(const char* filename)
+{
+ fprintf(stderr, "No command '%s' found, did you mean:\n", filename);
+ if ( getenv("LOGIN_PID") )
+ {
+ fprintf(stderr, " Exiting your shell normally to logout.\n");
+ fprintf(stderr, " Login as user 'reboot' to reboot computer.\n");
+ }
+ else
+ {
+ fprintf(stderr, " Exiting your shell with 'exit 1' to reboot.\n");
+ }
+}
+
int main(int argc, char* argv[])
{
const char* filename = 2 <= argc ? argv[1] : argv[0];
@@ -65,6 +100,15 @@ int main(int argc, char* argv[])
suggest_extfs(filename);
else if ( !strcmp(filename, "umount") )
suggest_unmount(filename);
+ else if ( !strcmp(filename, "logout") ||
+ !strcmp(filename, "logoff") )
+ suggest_logout(filename);
+ else if ( !strcmp(filename, "poweroff") ||
+ !strcmp(filename, "halt") ||
+ !strcmp(filename, "shutdown") )
+ suggest_poweroff(filename);
+ else if ( !strcmp(filename, "reboot") )
+ suggest_reboot(filename);
fprintf(stderr, "%s: command not found\n", filename);
return 127;
}