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