1 #include <sys/statvfs.h> 2 #include <fstream> 3 4 #include "drawtypes/label.hpp" 5 #include "drawtypes/progressbar.hpp" 6 #include "drawtypes/ramp.hpp" 7 #include "modules/fs.hpp" 8 #include "utils/factory.hpp" 9 #include "utils/math.hpp" 10 #include "utils/string.hpp" 11 12 #include "modules/meta/base.inl" 13 14 POLYBAR_NS 15 16 // Columns in /proc/self/mountinfo 17 #define MOUNTINFO_DIR 4 18 #define MOUNTINFO_TYPE 7 19 #define MOUNTINFO_FSNAME 8 20 21 namespace modules { 22 template class module<fs_module>; 23 24 /** 25 * Bootstrap the module by reading config values and 26 * setting up required components 27 */ fs_module(const bar_settings & bar,string name_)28 fs_module::fs_module(const bar_settings& bar, string name_) : timer_module<fs_module>(bar, move(name_)) { 29 m_mountpoints = m_conf.get_list(name(), "mount"); 30 m_remove_unmounted = m_conf.get(name(), "remove-unmounted", m_remove_unmounted); 31 m_fixed = m_conf.get(name(), "fixed-values", m_fixed); 32 m_spacing = m_conf.get(name(), "spacing", m_spacing); 33 set_interval(30s); 34 35 // Add formats and elements 36 m_formatter->add( 37 FORMAT_MOUNTED, TAG_LABEL_MOUNTED, {TAG_LABEL_MOUNTED, TAG_BAR_FREE, TAG_BAR_USED, TAG_RAMP_CAPACITY}); 38 m_formatter->add(FORMAT_UNMOUNTED, TAG_LABEL_UNMOUNTED, {TAG_LABEL_UNMOUNTED}); 39 40 if (m_formatter->has(TAG_LABEL_MOUNTED)) { 41 m_labelmounted = load_optional_label(m_conf, name(), TAG_LABEL_MOUNTED, "%mountpoint% %percentage_free%%"); 42 } 43 if (m_formatter->has(TAG_LABEL_UNMOUNTED)) { 44 m_labelunmounted = load_optional_label(m_conf, name(), TAG_LABEL_UNMOUNTED, "%mountpoint% is not mounted"); 45 } 46 if (m_formatter->has(TAG_BAR_FREE)) { 47 m_barfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_FREE); 48 } 49 if (m_formatter->has(TAG_BAR_USED)) { 50 m_barused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_USED); 51 } 52 if (m_formatter->has(TAG_RAMP_CAPACITY)) { 53 m_rampcapacity = load_ramp(m_conf, name(), TAG_RAMP_CAPACITY); 54 } 55 56 // Warn about "unreachable" format tag 57 if (m_formatter->has(TAG_LABEL_UNMOUNTED) && m_remove_unmounted) { 58 m_log.warn("%s: Defined format tag \"%s\" will never be used (reason: `remove-unmounted = true`)", name(), 59 string{TAG_LABEL_UNMOUNTED}); 60 } 61 } 62 63 /** 64 * Update mountpoints 65 */ update()66 bool fs_module::update() { 67 m_mounts.clear(); 68 69 vector<vector<string>> mountinfo; 70 std::ifstream filestream("/proc/self/mountinfo"); 71 string line; 72 73 // Get details for mounted filesystems 74 while (std::getline(filestream, line)) { 75 auto cols = string_util::split(line, ' '); 76 if (std::find(m_mountpoints.begin(), m_mountpoints.end(), cols[MOUNTINFO_DIR]) != m_mountpoints.end()) { 77 mountinfo.emplace_back(move(cols)); 78 } 79 } 80 81 // Get data for defined mountpoints 82 for (auto&& mountpoint : m_mountpoints) { 83 auto details = std::find_if(mountinfo.begin(), mountinfo.end(), 84 [&](const vector<string>& m) { return m.size() > 4 && m[4] == mountpoint; }); 85 86 m_mounts.emplace_back(new fs_mount{mountpoint, details != mountinfo.end()}); 87 struct statvfs buffer {}; 88 89 if (!m_mounts.back()->mounted) { 90 m_log.warn("%s: Mountpoint %s is not mounted", name(), mountpoint); 91 } else if (statvfs(mountpoint.c_str(), &buffer) == -1) { 92 m_log.err("%s: Failed to query filesystem (statvfs() error: %s)", name(), strerror(errno)); 93 } else { 94 auto& mount = m_mounts.back(); 95 mount->mountpoint = details->at(MOUNTINFO_DIR); 96 mount->type = details->at(MOUNTINFO_TYPE); 97 mount->fsname = details->at(MOUNTINFO_FSNAME); 98 99 // see: https://en.cppreference.com/w/cpp/filesystem/space 100 mount->bytes_total = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_blocks); 101 mount->bytes_free = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_bfree); 102 mount->bytes_used = mount->bytes_total - mount->bytes_free; 103 mount->bytes_avail = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_bavail); 104 105 mount->percentage_free = math_util::percentage<double>(mount->bytes_avail, mount->bytes_used + mount->bytes_avail); 106 mount->percentage_used = math_util::percentage<double>(mount->bytes_used, mount->bytes_used + mount->bytes_avail); 107 } 108 } 109 110 if (m_remove_unmounted) { 111 for (auto&& mount : m_mounts) { 112 if (!mount->mounted) { 113 m_log.info("%s: Removing mountpoint \"%s\" (reason: `remove-unmounted = true`)", name(), mount->mountpoint); 114 m_mountpoints.erase( 115 std::remove(m_mountpoints.begin(), m_mountpoints.end(), mount->mountpoint), m_mountpoints.end()); 116 m_mounts.erase(std::remove(m_mounts.begin(), m_mounts.end(), mount), m_mounts.end()); 117 } 118 } 119 } 120 121 return true; 122 } 123 124 /** 125 * Generate the module output 126 */ get_output()127 string fs_module::get_output() { 128 string output; 129 130 for (m_index = 0_z; m_index < m_mounts.size(); ++m_index) { 131 if (!output.empty()) { 132 m_builder->space(m_spacing); 133 } 134 output += timer_module::get_output(); 135 } 136 137 return output; 138 } 139 140 /** 141 * Select format based on fs state 142 */ get_format() const143 string fs_module::get_format() const { 144 return m_mounts[m_index]->mounted ? FORMAT_MOUNTED : FORMAT_UNMOUNTED; 145 } 146 147 /** 148 * Output content using configured format tags 149 */ build(builder * builder,const string & tag) const150 bool fs_module::build(builder* builder, const string& tag) const { 151 auto& mount = m_mounts[m_index]; 152 153 if (tag == TAG_BAR_FREE) { 154 builder->node(m_barfree->output(mount->percentage_free)); 155 } else if (tag == TAG_BAR_USED) { 156 builder->node(m_barused->output(mount->percentage_used)); 157 } else if (tag == TAG_RAMP_CAPACITY) { 158 builder->node(m_rampcapacity->get_by_percentage(mount->percentage_free)); 159 } else if (tag == TAG_LABEL_MOUNTED) { 160 m_labelmounted->reset_tokens(); 161 m_labelmounted->replace_token("%mountpoint%", mount->mountpoint); 162 m_labelmounted->replace_token("%type%", mount->type); 163 m_labelmounted->replace_token("%fsname%", mount->fsname); 164 m_labelmounted->replace_token("%percentage_free%", to_string(mount->percentage_free)); 165 m_labelmounted->replace_token("%percentage_used%", to_string(mount->percentage_used)); 166 m_labelmounted->replace_token( 167 "%total%", string_util::filesize(mount->bytes_total, m_fixed ? 2 : 0, m_fixed, m_bar.locale)); 168 m_labelmounted->replace_token( 169 "%free%", string_util::filesize(mount->bytes_avail, m_fixed ? 2 : 0, m_fixed, m_bar.locale)); 170 m_labelmounted->replace_token( 171 "%used%", string_util::filesize(mount->bytes_used, m_fixed ? 2 : 0, m_fixed, m_bar.locale)); 172 builder->node(m_labelmounted); 173 } else if (tag == TAG_LABEL_UNMOUNTED) { 174 m_labelunmounted->reset_tokens(); 175 m_labelunmounted->replace_token("%mountpoint%", mount->mountpoint); 176 builder->node(m_labelunmounted); 177 } else { 178 return false; 179 } 180 181 return true; 182 } 183 } 184 185 POLYBAR_NS_END 186