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