blob: d156d8e53f27420f8ddf21b8b3641a5d100710e7 [file] [log] [blame]
#include <iostream>
#include <cstdio>
#include <CompactDisplay.h>
#include <DrbdMon.h>
#include <ConfigOption.h>
#include <cstring>
#include <cstdio>
#include <cstdarg>
extern "C"
{
#include <pthread.h>
#include <errno.h>
}
const std::string CompactDisplay::OPT_NO_HEADER_KEY("no-header");
const std::string CompactDisplay::OPT_NO_HOTKEYS_KEY("no-hotkeys");
const std::string CompactDisplay::OPT_ASCII_KEY("ascii");
const std::string CompactDisplay::HDR_SPACER(" | ");
const std::string CompactDisplay::NODE_DSP_PREFIX("Node ");
const std::string CompactDisplay::TRUNC_MARKER("...");
const ConfigOption CompactDisplay::OPT_NO_HEADER(true, OPT_NO_HEADER_KEY);
const ConfigOption CompactDisplay::OPT_NO_HOTKEYS(true, OPT_NO_HOTKEYS_KEY);
const ConfigOption CompactDisplay::OPT_ASCII(true, OPT_ASCII_KEY);
// Generic formats
const char* CompactDisplay::F_NORM = "\x1b[0;32m";
const char* CompactDisplay::F_WARN = "\x1b[1;33;45m";
const char* CompactDisplay::F_ALERT = "\x1b[1;33;41m";
const char* CompactDisplay::F_RESET = "\x1b[0m";
const char* CompactDisplay::F_MARK = "\x1b[1;33;41m";
const char* CompactDisplay::F_HEADER= "\x1b[1;37;44m";
const char* CompactDisplay::F_RES_NORM = "\x1b[0;30;42m";
const char* CompactDisplay::F_VOL_NORM = "\x1b[0;30;46m";
const char* CompactDisplay::F_VOL_CLIENT = "\x1b[0;1;37;44m";
const char* CompactDisplay::F_VOL_MINOR = "\x1b[0;4;36m";
const char* CompactDisplay::F_CONN_NORM = "\x1b[0;37;44m";
const char* CompactDisplay::F_PRIMARY = "\x1b[1;36m";
const char* CompactDisplay::F_SECONDARY = "\x1b[0;36m";
// Foreground color for the 'Primary' role highlighting
// of connections (peer resource role)
const char* CompactDisplay::F_CONN_PRI_FG = "\x1b[1;36;44m";
const char* CompactDisplay::F_RES_NAME = "\x1b[0;4;32m";
const char* CompactDisplay::F_RES_COUNT = "\x1b[0;32mResources: %6llu\x1b[0m";
const char* CompactDisplay::F_PRB_COUNT = "\x1b[0;1;31m (%llu degraded)\x1b[0m";
const char* CompactDisplay::F_CURSOR_POS = "\x1b[%u;%uH";
const char* CompactDisplay::F_HOTKEY = "\x1b[0;30;47m%c\x1b[0;37;44m %s \033[0m";
const char* CompactDisplay::F_ALERT_HOTKEY = "\x1b[0;30;47m%c\x1b[1;33;41m %s \033[0m";
const char* CompactDisplay::UTF8_PRIMARY = "\xE2\x9A\xAB";
const char* CompactDisplay::UTF8_SECONDARY = " "; // otherwise UTF-8 E2 9A AA
const char* CompactDisplay::UTF8_CONN_GOOD = "\xE2\x87\x84";
const char* CompactDisplay::UTF8_CONN_BAD = "\xE2\x86\xAF";
const char* CompactDisplay::UTF8_DISK_GOOD = "\xE2\x97\x8E";
const char* CompactDisplay::UTF8_DISK_BAD = "\xE2\x9C\x97";
const char* CompactDisplay::UTF8_MARK_OFF = "\xE2\x9A\xAC";
const char* CompactDisplay::UTF8_MARK_ON = "\xE2\xA4\xB7";
const char* CompactDisplay::ASCII_PRIMARY = "*";
const char* CompactDisplay::ASCII_SECONDARY = " ";
const char* CompactDisplay::ASCII_CONN_GOOD = "+";
const char* CompactDisplay::ASCII_CONN_BAD = "/";
const char* CompactDisplay::ASCII_DISK_GOOD = "+";
const char* CompactDisplay::ASCII_DISK_BAD = "/";
const char* CompactDisplay::ASCII_MARK_OFF = " ";
const char* CompactDisplay::ASCII_MARK_ON = "+";
const char* CompactDisplay::ANSI_CLEAR = "\x1b[H\x1b[2J";
const char* CompactDisplay::ANSI_CLEAR_LINE = "\x1b[K";
const char* CompactDisplay::ANSI_CURSOR_OFF = "\x1b[?25l";
const char* CompactDisplay::ANSI_CURSOR_ON = "\x1b[?25h";
const char CompactDisplay::HOTKEY_PGUP = '<';
const char CompactDisplay::HOTKEY_PGDN = '>';
const char CompactDisplay::HOTKEY_PGZERO = '1';
const std::string CompactDisplay::LABEL_MESSAGES = "Messages";
const std::string CompactDisplay::LABEL_MONITOR = "Monitor";
const std::string CompactDisplay::LABEL_PROBLEMS = "Problems";
const std::string CompactDisplay::LABEL_STATUS = "Status";
const std::string CompactDisplay::LABEL_PGUP = "PgUp";
const std::string CompactDisplay::LABEL_PGDN = "PgDn";
const std::string CompactDisplay::LABEL_PGZERO = "Pg1";
const uint16_t CompactDisplay::MIN_SIZE_X = 40;
const uint16_t CompactDisplay::MAX_SIZE_X = 1024;
const uint16_t CompactDisplay::MIN_SIZE_Y = 15;
const uint16_t CompactDisplay::MAX_SIZE_Y = 1024;
const int CompactDisplay::RES_NAME_WIDTH = 32;
const int CompactDisplay::ROLE_WIDTH = 10;
const int CompactDisplay::VOL_NR_WIDTH = 2;
const int CompactDisplay::MINOR_NR_WIDTH = 4;
const int CompactDisplay::CONN_NAME_WIDTH = 20;
const int CompactDisplay::CONN_STATE_WIDTH = 20;
const int CompactDisplay::DISK_STATE_WIDTH = 20;
const int CompactDisplay::REPL_STATE_WIDTH = 20;
const uint16_t CompactDisplay::INDENT_STEP_SIZE = 4;
const uint16_t CompactDisplay::OUTPUT_BUFFER_SIZE = 1024;
const uint16_t CompactDisplay::MIN_NODENAME_DSP_LENGTH = 4;
const uint32_t CompactDisplay::MAX_YIELD_LOOP = 10;
// @throws std::bad_alloc
CompactDisplay::CompactDisplay(
ResourcesMap& resources_map_ref,
MessageLog& log_ref,
HotkeysMap& hotkeys_info_ref,
const std::string* const node_name_ref
):
resources_map(resources_map_ref),
log(log_ref),
hotkeys_info(hotkeys_info_ref),
node_name(node_name_ref)
{
// Allocate and initialize the indent buffer
indent_buffer_mgr = std::unique_ptr<char[]>(new char[INDENT_STEP_SIZE + 1]);
indent_buffer = indent_buffer_mgr.get();
for (size_t index = 0; index < INDENT_STEP_SIZE; ++index)
{
indent_buffer[index] = ' ';
}
indent_buffer[INDENT_STEP_SIZE] = '\0';
output_buffer_mgr = std::unique_ptr<char[]>(new char[OUTPUT_BUFFER_SIZE]);
output_buffer = output_buffer_mgr.get();
// Hide the cursor
write_text(ANSI_CURSOR_OFF);
hotkeys_info.append(&HOTKEY_PGZERO, &LABEL_PGZERO);
hotkeys_info.append(&HOTKEY_PGUP, &LABEL_PGUP);
hotkeys_info.append(&HOTKEY_PGDN, &LABEL_PGDN);
node_label = std::unique_ptr<std::string>(new std::string(HDR_SPACER));
*node_label += NODE_DSP_PREFIX;
}
CompactDisplay::~CompactDisplay() noexcept
{
// Show the cursor
write_text(ANSI_CURSOR_ON);
}
void CompactDisplay::clear()
{
write_text(ANSI_CLEAR);
current_x = 0;
current_y = 0;
}
void CompactDisplay::initial_display()
{
clear();
display_header();
if (log.has_entries() && dsp_msg_active)
{
log.display_messages(stdout);
}
else
{
if (!log.has_entries())
{
dsp_msg_active = false;
}
write_fmt("%sReading initial DRBD status...%s\n", F_RES_NAME, F_RESET);
}
// End line
next_line();
display_hotkeys_info();
}
void CompactDisplay::status_display()
{
uint32_t current_page = page;
page_start = (term_y - 4) * current_page;
page_end = (term_y - 4) * (current_page + 1) - 1;
prepare_flags();
clear();
display_header();
if (log.has_entries() && dsp_msg_active)
{
log.display_messages(stdout);
}
else
{
if (!log.has_entries())
{
dsp_msg_active = false;
}
bool resources_listed = list_resources();
if (!resources_listed)
{
if (dsp_problems_active)
{
write_fmt("%sNo resources with problems to display.%s\n", F_RES_NAME, F_RESET);
}
else
{
write_fmt("%sNo active DRBD resources.%s\n", F_RES_NAME, F_RESET);
}
}
}
// End line
next_line();
display_counts();
display_hotkeys_info();
}
void CompactDisplay::display_header() const
{
if (show_header)
{
write_fmt("%s%s%s v%s", F_HEADER, ANSI_CLEAR_LINE,
DrbdMon::PROGRAM_NAME.c_str(), DrbdMon::VERSION.c_str());
if (node_name != nullptr)
{
if (enable_term_size)
{
// "%s v%s" format for PROGRAM_NAME and VERSION
uint16_t x_pos = DrbdMon::PROGRAM_NAME.length() + DrbdMon::VERSION.length() + 2;
if (term_x >= x_pos)
{
uint16_t free_x = term_x - x_pos;
size_t label_length = node_label->length();
if (free_x >= label_length)
{
free_x -= label_length;
size_t name_length = node_name->length();
if (free_x > name_length)
{
// Entire node name fits on the header line
write_text(node_label->c_str());
write_text(node_name->c_str());
}
else
{
if (free_x > MIN_NODENAME_DSP_LENGTH + TRUNC_MARKER.length())
{
free_x -= TRUNC_MARKER.length() + 1;
// Minimum node name length and truncation marker fit on the header line
write_text(node_label->c_str());
size_t write_length = free_x < name_length ? free_x : name_length;
write_buffer(node_name->c_str(), write_length);
write_text(TRUNC_MARKER.c_str());
}
}
}
}
}
else
{
write_text(node_label->c_str());
write_text(node_name->c_str());
}
}
write_char('\n');
// Print right-aligned page number if the terminal size is known
if (enable_term_size && term_y > MIN_SIZE_Y && term_x > 15)
{
write_fmt(F_CURSOR_POS, 2, static_cast<int> (term_x) - 15);
write_text(F_RESET);
write_fmt("Page %5lu\n", static_cast<long> (page) + 1);
}
else
{
write_char('\n');
}
}
}
void CompactDisplay::display_hotkeys_info() const
{
if (show_hotkeys)
{
if (enable_term_size)
{
write_fmt(F_CURSOR_POS, static_cast<int> (term_y), 1);
}
HotkeysMap::NodesIterator hotkeys_iter(hotkeys_info);
size_t count = hotkeys_iter.get_size();
size_t index = 0;
while (index < count)
{
HotkeysMap::Node* node = hotkeys_iter.next();
const char hotkey = *(node->get_key());
const std::string& description = *(node->get_value());
if (index > 0)
{
write_char(' ');
}
write_fmt(F_HOTKEY, hotkey, description.c_str());
++index;
}
if (index > 0)
{
write_char(' ');
}
if (dsp_problems_active)
{
write_fmt(F_HOTKEY, 'p', LABEL_STATUS.c_str());
}
else
{
const char* format = problem_alert ? F_ALERT_HOTKEY : F_HOTKEY;
write_fmt(format, 'p', LABEL_PROBLEMS.c_str());
}
if (dsp_msg_active)
{
if (index > 0)
{
write_char(' ');
}
write_fmt(F_HOTKEY, 'm', LABEL_MONITOR.c_str());
}
else
if (log.has_entries())
{
if (index > 0)
{
write_char(' ');
}
write_fmt(F_ALERT_HOTKEY, 'm', LABEL_MESSAGES.c_str());
}
write_fmt(F_CURSOR_POS, 1, 1);
}
}
void CompactDisplay::display_counts() const
{
if (enable_term_size)
{
write_fmt(F_CURSOR_POS, static_cast<int> (term_y - 1), 1);
}
write_fmt(F_RES_COUNT, static_cast<unsigned long long> (resources_map.get_size()));
if (problem_count > 0)
{
write_fmt(F_PRB_COUNT, static_cast<unsigned long long> (problem_count));
}
}
void CompactDisplay::prepare_flags()
{
problem_alert = false;
problem_count = 0;
ResourcesMap::ValuesIterator res_iter(resources_map);
size_t res_count = res_iter.get_size();
for (size_t res_index = 0; res_index < res_count; ++res_index)
{
DrbdResource& res = *(res_iter.next());
if (res.update_state_flags() != StateFlags::state::NORM)
{
problem_alert = true;
++problem_count;
}
}
}
bool CompactDisplay::list_resources()
{
bool resources_listed {false};
// Reset columns tracking
reset_positions();
ResourcesMap::ValuesIterator res_iter(resources_map);
size_t res_count = res_iter.get_size();
for (size_t res_index = 0; res_index < res_count; ++res_index)
{
DrbdResource& res = *(res_iter.next());
bool marked = res.has_mark_state();
// If the problem display is active, show only resources that
// are marked for having warnings or alerts
if (!dsp_problems_active || marked)
{
resources_listed = true;
// Setup view of the "RES:" label
const char* f_res = F_RES_NORM;
if (res.has_alert_state())
{
f_res = F_ALERT;
}
else
if (res.has_warn_state())
{
f_res = F_WARN;
}
// Setup view of the sub-object warning mark
const char* f_mark = F_RES_NORM;
const char* mark_icon = mark_off;
if (res.has_mark_state())
{
f_mark = F_MARK;
mark_icon = mark_on;
}
// Setup view of the resource's role
const char* f_role = F_SECONDARY;
const char* role_icon = sec_icon;
if (res.has_role_alert())
{
f_role = F_ALERT;
role_icon = " ";
}
else
if (res.get_role() == DrbdRole::resource_role::PRIMARY)
{
f_role = F_PRIMARY;
role_icon = pri_icon;
}
next_line();
// Marker (1) + "RES:" (4) + RES_NAME_WIDTH + " " (1) + role icon (1) + ROLE_WIDTH
if (next_column(6 + RES_NAME_WIDTH + ROLE_WIDTH))
{
write_fmt(
"%s%s%sRES:%s%-*s%s %s%s%-*s%s",
f_mark, mark_icon, f_res, F_RES_NAME, RES_NAME_WIDTH, res.get_name().c_str(), F_RESET,
f_role, role_icon, ROLE_WIDTH, res.get_role_label(), F_RESET
);
}
increase_indent();
list_volumes(res);
list_connections(res);
decrease_indent();
}
}
return resources_listed;
}
void CompactDisplay::list_connections(DrbdResource& res)
{
// The connections list always starts on a new line
next_line();
// Short-display all connections that have a good state
{
ConnectionsMap::ValuesIterator conn_iter = res.connections_iterator();
size_t conn_count = conn_iter.get_size();
for (size_t conn_index = 0; conn_index < conn_count; ++conn_index)
{
DrbdConnection& conn = *(conn_iter.next());
if (!conn.has_mark_state())
{
const char* f_conn = F_CONN_NORM;
// Setup role icon & 'Primary' role highlighting format
const char* f_role = F_CONN_NORM;
const char* role_icon = sec_icon;
if (conn.get_role() == DrbdRole::resource_role::PRIMARY)
{
f_role = F_CONN_PRI_FG;
role_icon = pri_icon;
}
const std::string& conn_name = conn.get_name();
// Mark (1) + connection icon (1) + role icon (1) + name length
if (next_column(3 + conn_name.length()))
{
write_fmt(
"%s%s%s%s%s%s%s",
f_conn, mark_off, conn_good, f_role, role_icon, conn_name.c_str(), F_RESET
);
}
}
}
}
{
ConnectionsMap::ValuesIterator conn_iter = res.connections_iterator();
size_t conn_count = conn_iter.get_size();
for (size_t conn_index = 0; conn_index < conn_count; ++conn_index)
{
DrbdConnection& conn = *(conn_iter.next());
if (conn.has_mark_state())
{
// Setup connection view format
const char* f_conn = F_CONN_NORM;
if (conn.has_alert_state())
{
f_conn = F_ALERT;
}
else
if (conn.has_warn_state())
{
f_conn = F_WARN;
}
// Setup connection state view format
const char* f_conn_state = F_NORM;
const char* conn_icon = conn_good;
if (conn.has_connection_alert())
{
f_conn_state = F_ALERT;
conn_icon = conn_bad;
}
// Setup role state view format
const char* f_role = F_SECONDARY;
const char* role_icon = sec_icon;
if (conn.has_role_alert())
{
f_role = F_ALERT;
role_icon = " ";
}
else
if (conn.get_role() == DrbdRole::resource_role::PRIMARY)
{
f_role = F_PRIMARY;
role_icon = pri_icon;
}
next_line();
// Mark (1) + connection icon (1) + role icon (1) + CONN_NAME_WIDTH +
// " " (1) + CONN_STATE_WIDTH + " " (1) + role icon (1) + ROLE_WIDTH
if (next_column(6 + CONN_NAME_WIDTH + CONN_STATE_WIDTH + ROLE_WIDTH))
{
write_fmt(
"%s%s%s%s%s%-*s%s %s%-*s%s %s%s%-*s%s",
F_MARK, mark_on, f_conn, conn_icon, role_icon, CONN_NAME_WIDTH,
conn.get_name().c_str(), F_RESET, f_conn_state, CONN_STATE_WIDTH,
conn.get_connection_state_label(), F_RESET, f_role, role_icon,
ROLE_WIDTH, conn.get_role_label(), F_RESET
);
}
increase_indent();
list_peer_volumes(conn);
decrease_indent();
}
}
}
}
void CompactDisplay::list_volumes(DrbdResource& res)
{
// Short-display all volumes that have a good state
{
VolumesMap::ValuesIterator vol_iter = res.volumes_iterator();
size_t vol_count = vol_iter.get_size();
for (size_t vol_index = 0; vol_index < vol_count; ++vol_index)
{
DrbdVolume& vol = *(vol_iter.next());
if (!vol.has_warn_state())
{
show_volume(vol, false, false);
}
}
}
// Long-display all volumes that have a faulty state
{
VolumesMap::ValuesIterator vol_iter = res.volumes_iterator();
size_t vol_count = vol_iter.get_size();
for (size_t vol_index = 0; vol_index < vol_count; ++vol_index)
{
DrbdVolume& vol = *(vol_iter.next());
if (vol.has_warn_state())
{
show_volume(vol, false, true);
}
}
}
}
void CompactDisplay::list_peer_volumes(DrbdConnection& conn)
{
VolumesMap::ValuesIterator peer_vol_iter = conn.volumes_iterator();
size_t peer_vol_count = peer_vol_iter.get_size();
for (size_t peer_vol_index = 0; peer_vol_index < peer_vol_count; ++peer_vol_index)
{
DrbdVolume& peer_vol = *(peer_vol_iter.next());
if (peer_vol.has_warn_state())
{
show_volume(peer_vol, true, true);
}
}
}
void CompactDisplay::show_volume(DrbdVolume& vol, bool peer_volume, bool long_format)
{
if (long_format)
{
// Long format view for volumes that have a faulty state
// Set disk view format
const char* f_disk = F_NORM;
if (vol.has_disk_alert())
{
f_disk = F_ALERT;
}
// Set replication view format
const char* f_repl = F_NORM;
if (vol.has_replication_alert())
{
f_repl = F_ALERT;
}
else
if (vol.has_replication_warning())
{
f_repl = F_WARN;
}
bool show_replication = peer_volume;
if (peer_volume)
{
// Hide the 'Established' replication state
if (vol.get_replication_state() == DrbdVolume::repl_state::ESTABLISHED)
{
show_replication = false;
}
}
next_line();
// Disk icon (1) + VOL_NR_WIDTH + " " (1) + DISK_STATE_WIDTH
uint16_t vol_entry_width = 2 + VOL_NR_WIDTH + DISK_STATE_WIDTH;
if (!peer_volume)
{
// Local volume, add in minor number fields
// ":" (1) + MINOR_NR_WIDTH
vol_entry_width += 1 + MINOR_NR_WIDTH;
}
if (show_replication)
{
// Add in replication state fields
// " " (1) + REPL_STATE_WIDTH
vol_entry_width += 1 + REPL_STATE_WIDTH;
}
bool show = next_column(vol_entry_width);
if (show)
{
write_fmt(
"%s%s%*ld",
F_ALERT, disk_bad, VOL_NR_WIDTH, static_cast<long> (vol.get_volume_nr())
);
}
// Hide the minor number of peer volumes, as it is always unknown
if (!peer_volume && show)
{
// Display minor number
write_fmt(":%s%*ld", F_VOL_MINOR, MINOR_NR_WIDTH, static_cast<long> (vol.get_minor_nr()));
}
// Display disk state
if (show)
{
write_fmt(" %s %-*s%s", f_disk, DISK_STATE_WIDTH, vol.get_disk_state_label(), F_RESET);
}
if (show_replication && show)
{
// Display replication state
write_fmt(
" %s%-*s%s", f_repl, REPL_STATE_WIDTH,
vol.get_replication_state_label(), F_RESET
);
}
}
else
{
// Short format view of volumes that have a good state
const char* f_vol = F_VOL_NORM;
if (vol.get_disk_state() == DrbdVolume::disk_state::DISKLESS)
{
f_vol = F_VOL_CLIENT;
}
// Disk icon (1) + VOL_NR_WIDTH + ":" (1) + MINOR_NR_WIDTH
if (next_column(2 + VOL_NR_WIDTH + MINOR_NR_WIDTH))
{
write_fmt(
"%s%s%s%s%*ld:%s%*ld%s",
F_NORM, disk_good, F_RESET,
f_vol, VOL_NR_WIDTH, static_cast<long> (vol.get_volume_nr()),
F_VOL_MINOR, MINOR_NR_WIDTH, static_cast<long> (vol.get_minor_nr()), F_RESET
);
}
}
}
void CompactDisplay::set_terminal_size(uint16_t size_x, uint16_t size_y)
{
if (size_x >= MIN_SIZE_X)
{
if (size_x <= MAX_SIZE_X)
{
term_x = size_x;
}
else
{
term_x = MAX_SIZE_X;
}
}
else
{
term_x = MIN_SIZE_X;
}
if (size_y >= MIN_SIZE_Y)
{
if (size_y <= MAX_SIZE_Y)
{
term_y = size_y;
}
else
{
term_y = MAX_SIZE_Y;
}
}
else
{
term_y = MIN_SIZE_Y;
}
}
void CompactDisplay::increase_indent()
{
++indent_level;
}
void CompactDisplay::decrease_indent()
{
if (indent_level >= 1)
{
--indent_level;
}
}
void CompactDisplay::reset_indent()
{
indent_level = 0;
}
void CompactDisplay::reset_positions()
{
reset_indent();
current_x = 0;
current_y = 0;
}
void CompactDisplay::indent()
{
if (!(enable_term_size && term_y > MIN_SIZE_Y) ||
(current_y >= page_start && current_y <= page_end))
{
for (uint16_t counter = 0; counter < indent_level; ++counter)
{
write_text(indent_buffer);
}
}
current_x += INDENT_STEP_SIZE * indent_level;
}
bool CompactDisplay::next_column(uint16_t length)
{
bool display_flag = false;
if (enable_term_size && term_y > MIN_SIZE_Y)
{
display_flag = current_y >= page_start && current_y <= page_end;
// If this column is at the start of a new line,
// print the indent
if (current_x == 0)
{
indent();
current_x += length;
}
else
{
// Check whether the column fits in at the end
// of the current line
uint16_t end_x = current_x + length + 1;
if (end_x < term_x)
{
if (display_flag)
{
write_char(' ');
}
current_x = end_x;
}
else
{
next_line();
display_flag = current_y >= page_start && current_y <= page_end;
indent();
current_x += length;
}
}
}
else
{
display_flag = true;
if (current_x == 0)
{
indent();
current_x += length;
}
else
{
// No terminal size restrictions,
// allow an infinite number of columns
write_char(' ');
}
}
return display_flag;
}
void CompactDisplay::next_line()
{
if (current_x > 0)
{
if (!(enable_term_size && term_y > MIN_SIZE_Y) ||
(current_y >= page_start && current_y <= page_end))
{
write_char('\n');
}
current_x = 0;
++current_y;
}
}
void CompactDisplay::set_utf8(bool enable)
{
enable_utf8 = enable;
if (enable_utf8)
{
pri_icon = UTF8_PRIMARY;
sec_icon = UTF8_SECONDARY;
conn_good = UTF8_CONN_GOOD;
conn_bad = UTF8_CONN_BAD;
disk_good = UTF8_DISK_GOOD;
disk_bad = UTF8_DISK_BAD;
mark_off = UTF8_MARK_OFF;
mark_on = UTF8_MARK_ON;
}
else
{
pri_icon = ASCII_PRIMARY;
sec_icon = ASCII_SECONDARY;
conn_good = ASCII_CONN_GOOD;
conn_bad = ASCII_CONN_BAD;
disk_good = ASCII_DISK_GOOD;
disk_bad = ASCII_DISK_BAD;
mark_off = ASCII_MARK_OFF;
mark_on = ASCII_MARK_ON;
}
}
// @throws std::bad_alloc
void CompactDisplay::announce_options(Configurator& collector)
{
Configurable& owner = dynamic_cast<Configurable&> (*this);
collector.add_config_option(owner, OPT_NO_HEADER);
collector.add_config_option(owner, OPT_NO_HOTKEYS);
collector.add_config_option(owner, OPT_ASCII);
}
void CompactDisplay::options_help() noexcept
{
std::fputs("DrbdMon display configuration options:\n", stderr);
std::fputs(" --ascii Use only ASCII characters (no Unicode)\n", stderr);
std::fputs(" --no-header Do not display the DrbdMon header line\n", stderr);
std::fputs(" --no-hotkeys Do not display the hotkeys line\n", stderr);
std::fputc('\n', stderr);
std::fflush(stderr);
}
// @throws std::bad_alloc
void CompactDisplay::set_flag(std::string& key)
{
if (key == OPT_NO_HEADER.key)
{
show_header = false;
}
else
if (key == OPT_NO_HOTKEYS.key)
{
show_hotkeys = false;
}
else
if (key == OPT_ASCII.key)
{
set_utf8(false);
}
}
// @throws std::bad_alloc
void CompactDisplay::set_option(std::string& key, std::string& value)
{
// no-op; the CompactDisplay instance does not have any options
// with configurable values at this time
}
void CompactDisplay::key_pressed(const char key)
{
switch (key)
{
case '>':
++page;
status_display();
break;
case '<':
--page;
status_display();
break;
case 'm':
if (dsp_msg_active)
{
dsp_msg_active = false;
}
else
if (log.has_entries())
{
dsp_msg_active = true;
}
status_display();
break;
case 'p':
if (dsp_problems_active)
{
dsp_problems_active = false;
}
else
{
dsp_problems_active = true;
}
// fall-through
case '1':
page = 0;
status_display();
break;
default:
// no-op
break;
}
}
/**
* Writes a single character to the output_fd file descriptor
*
* @param ch The character to write
*/
void CompactDisplay::write_char(const char ch) const noexcept
{
// Repeat temporarily failing write() calls until the byte has been written
uint32_t loop_guard {0};
while (write(output_fd, static_cast<const void*> (&ch), 1) != 1)
{
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
{
break;
}
if (loop_guard < MAX_YIELD_LOOP)
{
// Attempt to yield to other processes before retrying
static_cast<void> (sched_yield());
++loop_guard;
}
else
{
// If yielding to other processes did not lead to any progress,
// suspend for a while
static_cast<void> (nanosleep(&write_retry_delay, nullptr));
}
}
}
/**
* Writes a text string to the output_fd file descriptor
*
* @param text The text string to write
*/
void CompactDisplay::write_text(const char* text) const noexcept
{
size_t length = std::strlen(text);
write_buffer(text, length);
}
/**
* Formats a text string and writes the result to the output_fd file descriptor
*
* @param format Format string
* @param ... Arguments for the format string
*/
void CompactDisplay::write_fmt(const char* format, ...) const noexcept
{
va_list vars;
va_start(vars, format);
size_t safe_length = 0;
{
size_t unsafe_length = vsnprintf(output_buffer, OUTPUT_BUFFER_SIZE, format, vars);
safe_length = unsafe_length < OUTPUT_BUFFER_SIZE ? unsafe_length : OUTPUT_BUFFER_SIZE;
}
va_end(vars);
write_buffer(output_buffer, safe_length);
}
/**
* Writes buffered data to the output_fd file descriptor
*
* Write attempts that fail temporarily or are only partially successful are retried until the
* all the buffered data has been written.
*
* @param buffer The buffered data to write
* @param length Length of the buffered data in the (possibly larger) buffer
*/
void CompactDisplay::write_buffer(const char* buffer, size_t length) const noexcept
{
uint32_t loop_guard {0};
ssize_t written {0};
while (length > 0)
{
// Repeat temporarily failing write() calls until the entire contents of the buffer have been written
written = write(output_fd, static_cast<const void*> (buffer), length);
if (written > 0)
{
length -= written;
}
else
if (written == -1 && (errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR))
{
break;
}
if (written <= 0)
{
if (loop_guard < MAX_YIELD_LOOP)
{
// Attempt to yield to other processes before retrying
static_cast<void> (sched_yield());
++loop_guard;
}
else
{
// If yielding to other processes did not lead to any progress,
// suspend for a while
static_cast<void> (nanosleep(&write_retry_delay, nullptr));
}
}
}
}