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