FlatImage
A configurable Linux containerization system
Loading...
Searching...
No Matches
fd.hpp
Go to the documentation of this file.
1
8
9#pragma once
10
11#include <fcntl.h>
12#include <sys/types.h>
13#include <chrono>
14#include <cstdint>
15#include <ctime>
16#include <functional>
17#include <span>
18
19#include "../linux.hpp"
20
21namespace ns_linux::ns_fd
22{
23
24constexpr uint32_t const SECONDS_TIMEOUT = 5;
25constexpr uint32_t const SIZE_BUFFER_READ = 16384;
26constexpr auto const TIMEOUT_RETRY = std::chrono::milliseconds(50);
27
35[[nodiscard]] inline Value<void> redirect_fd_to_fd(pid_t ppid, int fd_src, int fd_dst)
36{
37 // Validate file descriptors
38 return_if (ppid < 0, Error("E::Invalid pid to wait for: {}", ppid));
39 return_if (fd_src < 0, Error("E::Invalid source file descriptor: {}", fd_src));
40 return_if (fd_dst < 0, Error("E::Invalid destination file descriptor: {}", fd_dst));
41 // Read lambda
42 auto f_rw = [](int fd_src, int fd_dst) -> Value<bool>
43 {
44 alignas(16) char buf[SIZE_BUFFER_READ];
45
46 ssize_t n = ns_linux::read_with_timeout(fd_src
47 , std::chrono::milliseconds(100)
48 , std::span(buf, sizeof(buf))
49 );
50 // EOF
51 if(n == 0)
52 {
53 return false;
54 }
55 // Possible non-recoverable error
56 else if(n < 0)
57 {
58 // Timeout or would block - keep trying
59 if((errno == EAGAIN) or (errno == ETIMEDOUT))
60 {
61 return true;
62 }
63 // Non-recoverable error
64 return Error("E::Failed to read from file descriptor '{}' with error '{}'"
65 , fd_src
66 , strerror(errno)
67 );
68 }
69 // n > 0, try to write read data
70 return_if(::write(fd_dst, buf, n) < 0
71 , Error("D::Could not write to file descriptor '{}' with error '{}'", fd_dst, strerror(errno))
72 );
73 return true;
74 };
75 // Try to read from the src fifo with 50ms delays
76 while (::kill(ppid, 0) == 0 and Pop(f_rw(fd_src, fd_dst)))
77 {
78 std::this_thread::sleep_for(std::chrono::milliseconds(50));
79 }
80 // After the process exited, check for any leftover output
81 std::ignore = Pop(f_rw(fd_src, fd_dst));
82 return {};
83}
84
93[[nodiscard]] inline Value<void> redirect_file_to_fd(pid_t ppid, fs::path const& path_file, int fd_dst)
94{
95 // Try to open file within a timeout
96 int fd_src = ns_linux::open_with_timeout(path_file.c_str()
97 , std::chrono::seconds(SECONDS_TIMEOUT)
98 , O_RDONLY
99 );
100 if (fd_src == -1)
101 {
102 return Error("E::Failed to open file '{}' with error '{}'", path_file, strerror(errno));
103 }
104 // Redirect file to stdout
105 Pop(redirect_fd_to_fd(ppid, fd_src, fd_dst));
106 // Close file
107 close(fd_src);
108 return {};
109}
110
111
120[[nodiscard]] inline Value<void> redirect_fd_to_file(pid_t ppid
121 , int fd_src
122 , fs::path const& path_file)
123{
124 // Try to open file within a timeout
125 int fd_dst = ns_linux::open_with_timeout(path_file.c_str()
126 , std::chrono::seconds(SECONDS_TIMEOUT)
127 , O_WRONLY
128 );
129 if (fd_dst < 0)
130 {
131 return Error("E::Failed to open file '{}' with error '{}'", path_file, strerror(errno));
132 }
133 // Redirect file to stdout
134 Pop(redirect_fd_to_fd(ppid, fd_src, fd_dst));
135 // Close file
136 close(fd_dst);
137 return {};
138}
139
151[[nodiscard]] inline Value<void> redirect_fd_to_stream(pid_t ppid
152 , int fd_src
153 , std::ostream& stream_dst
154 , std::function<std::string(std::string const&)> transform = [](std::string e) -> std::string { return e; })
155{
156 // Validate file descriptors
157 return_if(fd_src < 0, Error("E::Invalid src file descriptor"));
158 // aligas is used because this gives out SIGILL when allocating the buffer
159 // if the memory is un-aligned. Pain.
160 for (alignas(16) char buf[SIZE_BUFFER_READ]; ::kill(ppid, 0) == 0;)
161 {
162 ssize_t n = ns_linux::read_with_timeout(fd_src, TIMEOUT_RETRY, std::span(buf, sizeof(buf)));
163 if (n > 0)
164 {
165 // Split on both newlines and carriage returns, filter empty/whitespace-only lines
166 std::string chunk(buf, n);
167 // Replace all \r with \n to handle Windows-style line endings and progress updates
168 std::ranges::replace(chunk, '\r', '\n');
169 // Split by newline and process each line
170 std::ranges::for_each(chunk
171 | std::views::split('\n')
172 | std::views::transform([](auto&& e){ return std::string{e.begin(), e.end()}; })
173 | std::views::filter([](auto const& s){ return not s.empty(); })
174 | std::views::filter([](auto const& s){ return not std::ranges::all_of(s, ::isspace); })
175 | std::views::transform([&](auto&& e){ return transform(e); })
176 , [&](auto line) { stream_dst << line << '\n'; }
177 );
178 stream_dst.flush();
179 }
180 // If the fd was given EOF (closed), then stop.
181 if(n == 0)
182 {
183 break;
184 }
185 // Possible error when n < 0 that is not a timeout nor a retry
186 return_if(n < 0 and (errno != EAGAIN) and (errno != ETIMEDOUT)
187 , Error("E::Failed to read from file descriptor '{}' with error '{}'", fd_src, strerror(errno));
188 );
189 // Retry
190 std::this_thread::sleep_for(TIMEOUT_RETRY);
191 }
192 return {};
193}
194
205[[nodiscard]] inline Value<void> redirect_stream_to_fd(pid_t ppid, std::istream& stream_src, int fd_dst)
206{
207 using namespace std::chrono_literals;
208 // Align to avoid SIGILL
209 alignas(16) char buf[SIZE_BUFFER_READ];
210 // Validate file descriptors
211 return_if(fd_dst < 0, Error("E::Invalid src file descriptor"));
212 // Query stream for data & forward to fd
213 for(; ::kill(ppid, 0) == 0; std::this_thread::sleep_for(TIMEOUT_RETRY))
214 {
215 // Use a non-blocking approach, checking if there's data available to read.
216 // in_avail: Returns the number of characters available for non-blocking
217 // read (either the size of the get area or the number of characters ready
218 // for reading from the associated character sequence), or -1 if no
219 // characters are available in the associated sequence as far as showmanyc()
220 // can tell.
221 if (std::streamsize avail = stream_src.rdbuf()->in_avail(); avail > 0)
222 {
223 // Read up to available or buffer size
224 std::streamsize to_read = std::min(avail, static_cast<std::streamsize>(sizeof(buf)));
225 stream_src.read(buf, to_read);
226 // How much was actually read
227 std::streamsize n = stream_src.gcount();
228 // The return of gcount is the number of read bytes by the last unformatted input operation
229 // which includes basic_istream::read. Regarding negative values:
230 // "Except in the constructors of std::strstreambuf, negative values of std::streamsize are never used."
231 continue_if (n <= 0);
232 // Write to file descriptor
233 ssize_t bytes = ns_linux::write_with_timeout(fd_dst, 1s, std::span(buf, n));
234 return_if (bytes < 0
235 , Error("E::Could not write data to file descriptor '{}': {}", fd_dst, strerror(errno));
236 );
237 }
238 // Custom stringstream streams with have eof on no data to read
239 // instead of hanging like std::cin
240 if (stream_src.eof())
241 {
242 stream_src.clear();
243 }
244 }
245 return {};
246}
247
248} // namespace ns_linux::ns_fd
Value< void > redirect_fd_to_stream(pid_t ppid, int fd_src, std::ostream &stream_dst, std::function< std::string(std::string const &)> transform=[](std::string e) -> std::string { return e;})
Redirects the output of a file descriptor to a stream.
Definition fd.hpp:151
Value< void > redirect_file_to_fd(pid_t ppid, fs::path const &path_file, int fd_dst)
Redirects the output of a file to a file descriptor.
Definition fd.hpp:93
Value< void > redirect_fd_to_fd(pid_t ppid, int fd_src, int fd_dst)
Redirects the output of one file descriptor as input of another.
Definition fd.hpp:35
Value< void > redirect_stream_to_fd(pid_t ppid, std::istream &stream_src, int fd_dst)
Redirects the output of a stream to a file descriptor.
Definition fd.hpp:205
Value< void > redirect_fd_to_file(pid_t ppid, int fd_src, fs::path const &path_file)
Redirects the output of a file descriptor to a file.
Definition fd.hpp:120
A library with helpers for linux operations.
int open_with_timeout(fs::path const &path_file_src, std::chrono::milliseconds timeout, int oflag)
Opens a given file with a timeout.
Definition linux.hpp:143
ssize_t write_with_timeout(int fd, std::chrono::milliseconds const &timeout, std::span< Data > buf)
Writes to the file descriptor with a timeout.
Definition linux.hpp:115
ssize_t read_with_timeout(int fd, std::chrono::milliseconds const &timeout, std::span< Data > buf)
Reads from the file descriptor with a timeout.
Definition linux.hpp:89
Enhanced expected type with integrated logging capabilities.
Definition expected.hpp:44