1//! @file io.cpp
2//! @author ryftchen
3//! @brief The definitions (io) in the utility module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2026 ryftchen. All rights reserved.
6
7#include "io.hpp"
8
9#include <sys/epoll.h>
10#include <sys/file.h>
11#include <unistd.h>
12#include <cstring>
13#include <deque>
14#include <filesystem>
15#include <iostream>
16#include <ranges>
17
18namespace utility::io
19{
20//! @brief Function version number.
21//! @return version number (major.minor.patch)
22const char* version() noexcept
23{
24 static const char* const ver = "0.1.0";
25 return ver;
26}
27
28//! @brief Execute the command line.
29//! @param command - target command line to be executed
30//! @return command line output
31std::string executeCommand(const std::string& command)
32{
33 auto* const pipe = ::popen(command: command.c_str(), modes: "r"); // NOLINT(cert-env33-c)
34 if (!pipe)
35 {
36 throw std::runtime_error{"Could not open pipe when trying to execute command."};
37 }
38
39 std::string output{};
40 for (std::vector<char> buffer(4096);;)
41 {
42 const std::size_t readLen = std::fread(ptr: buffer.data(), size: sizeof(char), n: buffer.size(), stream: pipe);
43 if (readLen == 0)
44 {
45 break;
46 }
47 output.append(s: buffer.data(), n: readLen);
48 }
49
50 const int status = ::pclose(stream: pipe);
51 if (WIFEXITED(status) && (WEXITSTATUS(status) != EXIT_SUCCESS))
52 {
53 throw std::runtime_error{"The command returned exit code " + std::to_string(WEXITSTATUS(status)) + '.'};
54 }
55 if (WIFSIGNALED(status))
56 {
57 throw std::runtime_error{"The command was terminated by signal " + std::to_string(WTERMSIG(status)) + '.'};
58 }
59 return output;
60}
61
62//! @brief Wait for input from the user.
63//! @param operation - handling for inputs (interrupt waiting if the return value is true, otherwise continue waiting)
64//! @param timeout - timeout period (ms)
65void waitForUserInput(const std::function<bool(const std::string&)>& operation, const int timeout)
66{
67 const int epollFD = ::epoll_create1(flags: 0);
68 if (epollFD == -1)
69 {
70 throw std::runtime_error{"Could not create epoll when trying to wait for user input."};
71 }
72
73 ::epoll_event event{};
74 event.events = ::EPOLLIN;
75 event.data.fd = STDIN_FILENO;
76 if (::epoll_ctl(epfd: epollFD, EPOLL_CTL_ADD, STDIN_FILENO, event: &event) == -1)
77 {
78 ::close(fd: epollFD);
79 throw std::runtime_error{"Could not control epoll when trying to wait for user input."};
80 }
81
82 for (;;)
83 {
84 const int status = ::epoll_wait(epfd: epollFD, events: &event, maxevents: 1, timeout: timeout);
85 if (status == -1)
86 {
87 ::close(fd: epollFD);
88 throw std::runtime_error{"Failed to wait epoll when waiting for user input."};
89 }
90 if (status == 0)
91 {
92 break;
93 }
94
95 if ((event.events & ::EPOLLIN) != 0)
96 {
97 std::string input{};
98 std::getline(is&: std::cin, str&: input);
99 if (!operation || operation(input))
100 {
101 break;
102 }
103 }
104 }
105
106 ::close(fd: epollFD);
107}
108
109//! @brief Read lines from the file.
110//! @param filename - file path
111//! @param lock - lock or not
112//! @param reverse - reverse or not
113//! @param limit - maximum number of lines
114//! @return file contents
115std::vector<std::string> readFileLines(
116 const std::string_view filename, const bool lock, const bool reverse, const int limit)
117{
118 FileReader reader(filename);
119 reader.open();
120 if (lock)
121 {
122 reader.lock();
123 }
124
125 auto& stream = reader.stream();
126 stream.seekg(0, std::ios::beg);
127 std::vector<std::string> contents{};
128 if (std::string line{}; !reverse)
129 {
130 contents.reserve(n: (limit >= 0) ? limit : 1024);
131 while (std::getline(is&: stream, str&: line))
132 {
133 if ((limit >= 0) && (static_cast<int>(contents.size()) == limit))
134 {
135 break;
136 }
137 contents.emplace_back(args: std::move(line));
138 }
139 contents.shrink_to_fit();
140 }
141 else
142 {
143 std::deque<std::string> buffer{};
144 while (std::getline(is&: stream, str&: line))
145 {
146 buffer.emplace_back(args: std::move(line));
147 if ((limit >= 0) && (static_cast<int>(buffer.size()) > limit))
148 {
149 buffer.pop_front();
150 }
151 }
152 contents.reserve(n: buffer.size());
153 for (auto& temp : std::ranges::reverse_view(buffer))
154 {
155 contents.emplace_back(args: std::move(temp));
156 }
157 }
158
159 if (lock)
160 {
161 reader.unlock();
162 }
163 reader.close();
164 return contents;
165}
166
167FDStreamBuffer::~FDStreamBuffer()
168{
169 reset();
170}
171
172void FDStreamBuffer::set(const int newFD)
173{
174 if (fd == newFD)
175 {
176 return;
177 }
178
179 if (fd >= 0)
180 {
181 sync();
182 ::close(fd: fd);
183 }
184 setg(gbeg: nullptr, gnext: nullptr, gend: nullptr);
185 setp(pbeg: nullptr, pend: nullptr);
186 readBuffer.fill(u: 0);
187 writeBuffer.fill(u: 0);
188 fd = newFD;
189}
190
191void FDStreamBuffer::reset()
192{
193 if (fd < 0)
194 {
195 return;
196 }
197
198 flush();
199 ::close(fd: fd);
200 setg(gbeg: nullptr, gnext: nullptr, gend: nullptr);
201 setp(pbeg: nullptr, pend: nullptr);
202 readBuffer.fill(u: 0);
203 writeBuffer.fill(u: 0);
204 fd = -1;
205}
206
207int FDStreamBuffer::flush()
208{
209 if (fd < 0)
210 {
211 return 0;
212 }
213
214 const char* ptr = pbase();
215 while (ptr < pptr())
216 {
217 const ::ssize_t writtenSize = ::write(fd: fd, buf: ptr, n: pptr() - ptr);
218 if (writtenSize <= 0)
219 {
220 return -1;
221 }
222 ptr += writtenSize;
223 }
224 setp(pbeg: writeBuffer.data(), pend: writeBuffer.data() + writeBuffer.size());
225 return 0;
226}
227
228FDStreamBuffer::int_type FDStreamBuffer::underflow()
229{
230 if (gptr() != egptr())
231 {
232 throw std::runtime_error{"Read pointer has not reached the end of the buffer."};
233 }
234 if (fd < 0)
235 {
236 return traits_type::eof();
237 }
238
239 const ::ssize_t readSize = ::read(fd: fd, buf: readBuffer.data(), nbytes: readBuffer.size());
240 if (readSize <= 0)
241 {
242 return traits_type::eof();
243 }
244 setg(gbeg: readBuffer.data(), gnext: readBuffer.data(), gend: readBuffer.data() + readSize);
245 return traits_type::to_int_type(c: *gptr());
246}
247
248FDStreamBuffer::int_type FDStreamBuffer::overflow(const int_type c)
249{
250 if (pptr() != epptr())
251 {
252 throw std::runtime_error{"Write pointer has not reached the end of the buffer."};
253 }
254 if (fd < 0)
255 {
256 return traits_type::eof();
257 }
258
259 if (sync() == -1)
260 {
261 return traits_type::eof();
262 }
263 if (c != traits_type::eof())
264 {
265 *pptr() = traits_type::to_char_type(c: c);
266 pbump(n: 1);
267 }
268 return c;
269}
270
271int FDStreamBuffer::sync()
272{
273 return flush();
274}
275
276std::streampos FDStreamBuffer::seekoff(
277 const std::streamoff off, const std::ios_base::seekdir way, const std::ios_base::openmode mode)
278{
279 if ((fd < 0) || (((mode & std::ios_base::out) != 0) && (sync() == -1)))
280 {
281 return -1;
282 }
283
284 ::off_t newOffset = 0;
285 switch (way)
286 {
287 case std::ios_base::beg:
288 newOffset = off;
289 break;
290 case std::ios_base::cur:
291 newOffset = ((((mode & std::ios_base::in) != 0) && gptr()) ? (off - (egptr() - gptr())) : off)
292 + ::lseek(fd: fd, offset: 0, SEEK_CUR);
293 break;
294 case std::ios_base::end:
295 newOffset = off + ::lseek(fd: fd, offset: 0, SEEK_END);
296 break;
297 default:
298 return -1;
299 }
300
301 if (::lseek(fd: fd, offset: newOffset, SEEK_SET) == -1)
302 {
303 return -1;
304 }
305 setg(gbeg: nullptr, gnext: nullptr, gend: nullptr);
306 return newOffset;
307}
308
309std::streampos FDStreamBuffer::seekpos(const std::streampos sp, const std::ios_base::openmode mode)
310{
311 return seekoff(off: sp, way: std::ios_base::beg, mode);
312}
313
314std::streamsize FDStreamBuffer::showmanyc()
315{
316 return ((fd >= 0) && gptr() && egptr()) ? (egptr() - gptr()) : 0;
317}
318
319FileReader::~FileReader()
320{
321 unlock();
322 close();
323}
324
325bool FileReader::isOpened() const
326{
327 return fd >= 0;
328}
329
330void FileReader::open()
331{
332 if (isOpened())
333 {
334 return;
335 }
336
337 fd = ::open(file: name.c_str(), O_CREAT | O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
338 if (fd < 0)
339 {
340 throw std::runtime_error{
341 "Failed to open " + std::filesystem::path{name}.filename().string() + " file for reading."};
342 }
343 strBuf.set(fd);
344}
345
346void FileReader::close()
347{
348 if (!isOpened())
349 {
350 return;
351 }
352
353 strBuf.reset();
354 fd = -1;
355}
356
357bool FileReader::isLocked() const
358{
359 return lockActive;
360}
361
362void FileReader::lock()
363{
364 if (isLocked())
365 {
366 return;
367 }
368
369 if (::flock(fd: fd, LOCK_SH | LOCK_NB) != 0)
370 {
371 throw std::runtime_error{"Failed to lock file descriptor " + std::to_string(val: fd) + " for reading."};
372 }
373 lockActive = true;
374}
375
376void FileReader::unlock()
377{
378 if (!isLocked())
379 {
380 return;
381 }
382
383 ::flock(fd: fd, LOCK_UN);
384 lockActive = false;
385}
386
387std::istream& FileReader::stream() noexcept
388{
389 return input;
390}
391
392FileWriter::~FileWriter()
393{
394 unlock();
395 close();
396}
397
398bool FileWriter::isOpened() const
399{
400 return fd >= 0;
401}
402
403void FileWriter::open(const bool overwrite)
404{
405 if (isOpened())
406 {
407 return;
408 }
409
410 fd = ::open(
411 file: name.c_str(), O_CREAT | (overwrite ? O_TRUNC : O_APPEND) | O_WRONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
412 if (fd < 0)
413 {
414 throw std::runtime_error{
415 "Failed to open " + std::filesystem::path{name}.filename().string() + " file for writing."};
416 }
417 strBuf.set(fd);
418}
419
420void FileWriter::close()
421{
422 if (!isOpened())
423 {
424 return;
425 }
426
427 strBuf.reset();
428 fd = -1;
429}
430
431bool FileWriter::isLocked() const
432{
433 return lockActive;
434}
435
436void FileWriter::lock()
437{
438 if (isLocked())
439 {
440 return;
441 }
442
443 if (::flock(fd: fd, LOCK_EX | LOCK_NB) != 0)
444 {
445 throw std::runtime_error{"Failed to lock file descriptor " + std::to_string(val: fd) + " for writing."};
446 }
447 lockActive = true;
448}
449
450void FileWriter::unlock()
451{
452 if (!isLocked())
453 {
454 return;
455 }
456
457 ::flock(fd: fd, LOCK_UN);
458 lockActive = false;
459}
460
461std::ostream& FileWriter::stream() noexcept
462{
463 return output;
464}
465} // namespace utility::io
466