1 #include "utils/file.hpp"
2
3 #include <dirent.h>
4 #include <fcntl.h>
5 #include <glob.h>
6 #include <sys/stat.h>
7
8 #include <cstdio>
9 #include <cstdlib>
10 #include <fstream>
11 #include <streambuf>
12
13 #include "errors.hpp"
14 #include "utils/env.hpp"
15 #include "utils/string.hpp"
16
17 POLYBAR_NS
18
19 // implementation of file_ptr {{{
20
file_ptr(const string & path,const string & mode)21 file_ptr::file_ptr(const string& path, const string& mode) : m_path(string(path)), m_mode(string(mode)) {
22 m_ptr = fopen(m_path.c_str(), m_mode.c_str());
23 }
24
~file_ptr()25 file_ptr::~file_ptr() {
26 if (m_ptr != nullptr) {
27 fclose(m_ptr);
28 }
29 }
30
operator bool()31 file_ptr::operator bool() {
32 return static_cast<const file_ptr&>(*this);
33 }
operator bool() const34 file_ptr::operator bool() const {
35 return m_ptr != nullptr;
36 }
37
operator FILE*()38 file_ptr::operator FILE*() {
39 return static_cast<const file_ptr&>(*this);
40 }
operator FILE*() const41 file_ptr::operator FILE*() const {
42 return m_ptr;
43 }
44
operator int()45 file_ptr::operator int() {
46 return static_cast<const file_ptr&>(*this);
47 }
operator int() const48 file_ptr::operator int() const {
49 return fileno(*this);
50 }
51
52 // }}}
53 // implementation of file_descriptor {{{
54
file_descriptor(const string & path,int flags)55 file_descriptor::file_descriptor(const string& path, int flags) {
56 if ((m_fd = open(path.c_str(), flags)) == -1) {
57 throw system_error("Failed to open file descriptor");
58 }
59 }
60
file_descriptor(int fd,bool autoclose)61 file_descriptor::file_descriptor(int fd, bool autoclose) : m_fd(fd), m_autoclose(autoclose) {
62 if (m_fd != -1 && !*this) {
63 throw system_error("Given file descriptor (" + to_string(m_fd) + ") is not valid");
64 }
65 }
66
~file_descriptor()67 file_descriptor::~file_descriptor() {
68 if (m_autoclose) {
69 close();
70 }
71 }
72
operator =(const int fd)73 file_descriptor& file_descriptor::operator=(const int fd) {
74 if (m_autoclose) {
75 close();
76 }
77 m_fd = fd;
78 return *this;
79 }
80
operator int()81 file_descriptor::operator int() {
82 return static_cast<const file_descriptor&>(*this);
83 }
operator int() const84 file_descriptor::operator int() const {
85 return m_fd;
86 }
87
operator bool()88 file_descriptor::operator bool() {
89 return static_cast<const file_descriptor&>(*this);
90 }
operator bool() const91 file_descriptor::operator bool() const {
92 errno = 0; // reset since fcntl only changes it on error
93 if ((fcntl(m_fd, F_GETFD) == -1) || errno == EBADF) {
94 errno = EBADF;
95 return false;
96 }
97 return true;
98 }
99
close()100 void file_descriptor::close() {
101 if (m_fd != -1 && ::close(m_fd) == -1) {
102 throw system_error("Failed to close file descriptor");
103 }
104 m_fd = -1;
105 }
106
107 // }}}
108 // implementation of file_streambuf {{{
109
~fd_streambuf()110 fd_streambuf::~fd_streambuf() {
111 close();
112 }
113
operator int()114 fd_streambuf::operator int() {
115 return static_cast<const fd_streambuf&>(*this);
116 }
operator int() const117 fd_streambuf::operator int() const {
118 return m_fd;
119 }
120
open(int fd)121 void fd_streambuf::open(int fd) {
122 if (m_fd) {
123 close();
124 }
125 m_fd = fd;
126 setg(m_in, m_in, m_in);
127 setp(m_out, m_out + bufsize - 1);
128 }
129
close()130 void fd_streambuf::close() {
131 if (m_fd) {
132 sync();
133 m_fd = -1;
134 }
135 }
136
sync()137 int fd_streambuf::sync() {
138 if (pbase() != pptr()) {
139 auto size = pptr() - pbase();
140 auto bytes = write(m_fd, m_out, size);
141 if (bytes > 0) {
142 std::copy(pbase() + bytes, pptr(), pbase());
143 setp(pbase(), epptr());
144 pbump(size - bytes);
145 }
146 }
147 return pptr() != epptr() ? 0 : -1;
148 }
149
overflow(int c)150 int fd_streambuf::overflow(int c) {
151 if (!traits_type::eq_int_type(c, traits_type::eof())) {
152 *pptr() = traits_type::to_char_type(c);
153 pbump(1);
154 }
155 return sync() == -1 ? traits_type::eof() : traits_type::not_eof(c);
156 }
157
underflow()158 int fd_streambuf::underflow() {
159 if (gptr() == egptr()) {
160 std::streamsize pback(std::min(gptr() - eback(), std::ptrdiff_t(16 - sizeof(int))));
161 std::copy(egptr() - pback, egptr(), eback());
162 int bytes(read(m_fd, eback() + pback, bufsize));
163 setg(eback(), eback() + pback, eback() + pback + std::max(0, bytes));
164 }
165 return gptr() == egptr() ? traits_type::eof() : traits_type::to_int_type(*gptr());
166 }
167
168 // }}}
169
170 namespace file_util {
171 /**
172 * Checks if the given file exist
173 *
174 * May also return false if the file status cannot be read
175 *
176 * Sets errno when returning false
177 */
exists(const string & filename)178 bool exists(const string& filename) {
179 struct stat buffer {};
180 return stat(filename.c_str(), &buffer) == 0;
181 }
182
183 /**
184 * Checks if the given path exists and is a file
185 */
is_file(const string & filename)186 bool is_file(const string& filename) {
187 struct stat buffer {};
188
189 if (stat(filename.c_str(), &buffer) != 0) {
190 return false;
191 }
192
193 return S_ISREG(buffer.st_mode);
194 }
195
196 /**
197 * Picks the first existing file out of given entries
198 */
pick(const vector<string> & filenames)199 string pick(const vector<string>& filenames) {
200 for (auto&& f : filenames) {
201 if (exists(f)) {
202 return f;
203 }
204 }
205 return "";
206 }
207
208 /**
209 * Gets the contents of the given file
210 */
contents(const string & filename)211 string contents(const string& filename) {
212 try {
213 string contents;
214 string line;
215 std::ifstream in(filename, std::ifstream::in);
216 while (std::getline(in, line)) {
217 contents += line + '\n';
218 }
219 return contents;
220 } catch (const std::ifstream::failure& e) {
221 return "";
222 }
223 }
224
225 /**
226 * Writes the contents of the given file
227 */
write_contents(const string & filename,const string & contents)228 void write_contents(const string& filename, const string& contents) {
229 std::ofstream out(filename, std::ofstream::out);
230 if (!(out << contents)) {
231 throw std::system_error(errno, std::system_category(), "failed to write to " + filename);
232 }
233 }
234
235 /**
236 * Checks if the given file is a named pipe
237 */
is_fifo(const string & filename)238 bool is_fifo(const string& filename) {
239 struct stat buffer {};
240 return stat(filename.c_str(), &buffer) == 0 && S_ISFIFO(buffer.st_mode);
241 }
242
243 /**
244 * Get glob results using given pattern
245 */
glob(string pattern)246 vector<string> glob(string pattern) {
247 glob_t result{};
248 vector<string> ret;
249
250 // Manually expand tilde to fix builds using versions of libc
251 // that doesn't provide the GLOB_TILDE flag (musl for example)
252 if (pattern[0] == '~') {
253 pattern.replace(0, 1, env_util::get("HOME"));
254 }
255
256 if (::glob(pattern.c_str(), 0, nullptr, &result) == 0) {
257 for (size_t i = 0_z; i < result.gl_pathc; ++i) {
258 ret.emplace_back(result.gl_pathv[i]);
259 }
260 globfree(&result);
261 }
262
263 return ret;
264 }
265
266 /**
267 * Path expansion
268 */
expand(const string & path)269 const string expand(const string& path) {
270 string ret;
271 /*
272 * This doesn't capture all cases for absolute paths but the other cases
273 * (tilde and env variable) have the initial '/' character in their
274 * expansion already and will thus not require adding '/' to the beginning.
275 */
276 bool is_absolute = path.size() > 0 && path.at(0) == '/';
277 vector<string> p_exploded = string_util::split(path, '/');
278 for (auto& section : p_exploded) {
279 switch (section[0]) {
280 case '$':
281 section = env_util::get(section.substr(1));
282 break;
283 case '~':
284 section = env_util::get("HOME");
285 break;
286 }
287 }
288 ret = string_util::join(p_exploded, "/");
289 // Don't add an initial slash for relative paths
290 if (ret[0] != '/' && is_absolute) {
291 ret.insert(0, 1, '/');
292 }
293 return ret;
294 }
295
296 /*
297 * Search for polybar config and returns the path if found
298 */
get_config_path()299 string get_config_path() {
300 string confpath;
301 if (env_util::has("XDG_CONFIG_HOME")) {
302 confpath = env_util::get("XDG_CONFIG_HOME") + "/polybar/config";
303 if (exists(confpath)) {
304 return confpath;
305 }
306 }
307 if (env_util::has("HOME")) {
308 confpath = env_util::get("HOME") + "/.config/polybar/config";
309 if (exists(confpath)) {
310 return confpath;
311 }
312 }
313 return "";
314 }
315
316 /**
317 * Return a list of file names in a directory.
318 */
list_files(const string & dirname)319 vector<string> list_files(const string& dirname) {
320 vector<string> files;
321 DIR* dir;
322 if ((dir = opendir(dirname.c_str())) != NULL) {
323 struct dirent* ent;
324 while ((ent = readdir(dir)) != NULL) {
325 // Type can be unknown for filesystems that do not support d_type
326 if ((ent->d_type & DT_REG) ||
327 ((ent->d_type & DT_UNKNOWN) && strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0)) {
328 files.push_back(ent->d_name);
329 }
330 }
331 closedir(dir);
332 return files;
333 }
334 throw system_error("Failed to open directory stream for " + dirname);
335 }
336 } // namespace file_util
337
338 POLYBAR_NS_END
339