Skip to content

Commit bb62714

Browse files
authored
Add fdstream class in order to replace C file io functions (#1262)
1 parent da95311 commit bb62714

File tree

7 files changed

+561
-1
lines changed

7 files changed

+561
-1
lines changed

CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ endif()
232232
check_function_exists(pipe2 HAVE_PIPE2)
233233
check_include_file(sys/wait.h HAVE_SYS_WAIT_H)
234234
check_include_file(sys/mman.h HAVE_SYS_MMAN_H)
235+
check_include_file(sys/uio.h HAVE_SYS_UIO_H)
235236

236237
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
237238
include_directories(${CMAKE_CURRENT_BINARY_DIR})

config.h.in

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#cmakedefine HAVE_PIPE2
4747
#cmakedefine HAVE_SYS_WAIT_H
4848
#cmakedefine HAVE_SYS_MMAN_H
49+
#cmakedefine HAVE_SYS_UIO_H
4950

5051
#ifndef _GNU_SOURCE
5152
#define _GNU_SOURCE

src/lib/fcitx-utils/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ list(APPEND FCITX_UTILS_SOURCES
6060
event.cpp
6161
eventloopinterface.cpp
6262
environ.cpp
63+
fdstreambuf.cpp
6364
)
6465

6566
set(FCITX_UTILS_HEADERS
@@ -103,6 +104,7 @@ set(FCITX_UTILS_HEADERS
103104
testing.h
104105
semver.h
105106
environ.h
107+
fdstreambuf.h
106108
${CMAKE_CURRENT_BINARY_DIR}/fcitxutils_export.h
107109
)
108110

src/lib/fcitx-utils/fdstreambuf.cpp

+365
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025-2025 CSSlayer <[email protected]>
3+
*
4+
* SPDX-License-Identifier: LGPL-2.1-or-later
5+
*/
6+
#include "fdstreambuf.h"
7+
#include <sys/types.h>
8+
#include <unistd.h>
9+
#include <algorithm>
10+
#include <cassert>
11+
#include <cstring>
12+
#include <ios>
13+
#include <limits>
14+
#include <memory>
15+
#include <streambuf>
16+
#include <utility>
17+
#include "config.h"
18+
#include "fs.h"
19+
#include "macros.h"
20+
#include "unixfd.h"
21+
22+
#ifdef HAVE_SYS_UIO_H
23+
#include <sys/uio.h>
24+
#endif
25+
26+
namespace fcitx {
27+
28+
namespace {
29+
30+
inline constexpr int IBufferSize = 8192;
31+
inline constexpr int OBufferSize = 8192;
32+
33+
// Wrapper handling partial write.
34+
std::streamsize xwrite(int fd, const char *s, std::streamsize bytesToWrite) {
35+
const std::streamsize n = bytesToWrite;
36+
37+
while (true) {
38+
const std::streamsize bytesWritten = fs::safeWrite(fd, s, bytesToWrite);
39+
if (bytesWritten < 0) {
40+
break;
41+
}
42+
bytesToWrite -= bytesWritten;
43+
if (bytesToWrite == 0) {
44+
break;
45+
}
46+
47+
s += bytesWritten;
48+
}
49+
50+
return n - bytesToWrite;
51+
}
52+
53+
#ifdef HAVE_SYS_UIO_H
54+
std::streamsize xwritev(int fd, const char *s1, std::streamsize bytesToWrite1,
55+
const char *s2, std::streamsize bytesToWrite2) {
56+
assert(bytesToWrite1 >= 0 && bytesToWrite2 >= 0);
57+
const std::streamsize n = bytesToWrite1 + bytesToWrite2;
58+
59+
struct iovec iov[2];
60+
iov[1].iov_base = const_cast<char *>(s2);
61+
iov[1].iov_len = bytesToWrite2;
62+
63+
// Use writev to write s1 & s2
64+
while (bytesToWrite1 > 0) {
65+
iov[0].iov_base = const_cast<char *>(s1);
66+
iov[0].iov_len = bytesToWrite1;
67+
68+
std::streamsize bytesWritten;
69+
do {
70+
bytesWritten = writev(fd, iov, 2);
71+
} while (bytesWritten == -1 && errno == EINTR);
72+
if (bytesWritten < 0) {
73+
break;
74+
}
75+
76+
if (bytesToWrite1 < bytesWritten) {
77+
// s2 is also partially done, update bytesToWrite2.
78+
const std::streamsize bytesWritten2 = bytesWritten - bytesToWrite1;
79+
bytesToWrite2 -= bytesWritten2;
80+
s2 += bytesToWrite2;
81+
bytesToWrite1 = 0;
82+
} else {
83+
bytesToWrite1 -= bytesWritten;
84+
}
85+
}
86+
87+
if (bytesToWrite1 == 0 && bytesToWrite2 > 0) {
88+
bytesToWrite2 -= xwrite(fd, s2, bytesToWrite2);
89+
}
90+
91+
return n - bytesToWrite1 - bytesToWrite2;
92+
}
93+
#endif
94+
95+
} // namespace
96+
97+
class IFDStreamBufPrivate : public QPtrHolder<IFDStreamBuf> {
98+
public:
99+
IFDStreamBufPrivate(IFDStreamBuf *q) : QPtrHolder(q) {
100+
buffer_ = std::make_unique<char[]>(IBufferSize);
101+
resetBuffer(0);
102+
}
103+
104+
void resetBuffer(size_t nread) {
105+
FCITX_Q();
106+
q->setg(buffer_.get(), buffer_.get(), buffer_.get() + nread);
107+
}
108+
109+
int fd_ = -1;
110+
UnixFD fdOwner_;
111+
112+
std::unique_ptr<char[]> buffer_;
113+
};
114+
115+
IFDStreamBuf::IFDStreamBuf(UnixFD fd)
116+
: d_ptr(std::make_unique<IFDStreamBufPrivate>(this)) {
117+
FCITX_D();
118+
d->fd_ = fd.fd();
119+
d->fdOwner_ = std::move(fd);
120+
}
121+
122+
IFDStreamBuf::IFDStreamBuf(int fd)
123+
: d_ptr(std::make_unique<IFDStreamBufPrivate>(this)) {
124+
FCITX_D();
125+
d->fd_ = fd;
126+
}
127+
128+
IFDStreamBuf::~IFDStreamBuf() {}
129+
130+
bool IFDStreamBuf::is_open() const noexcept {
131+
FCITX_D();
132+
return d->fd_ != -1;
133+
}
134+
135+
int IFDStreamBuf::fd() const noexcept {
136+
FCITX_D();
137+
return d->fd_;
138+
}
139+
140+
IFDStreamBuf *IFDStreamBuf::close() {
141+
FCITX_D();
142+
d->fd_ = -1;
143+
d->fdOwner_.reset();
144+
return this;
145+
}
146+
147+
IFDStreamBuf::int_type IFDStreamBuf::underflow() {
148+
FCITX_D();
149+
if (gptr() >= egptr()) {
150+
auto bytesRead = fs::safeRead(d->fd_, d->buffer_.get(), IBufferSize);
151+
if (bytesRead <= 0) {
152+
// EOF
153+
return traits_type::eof();
154+
}
155+
156+
// Reset the buffer
157+
d->resetBuffer(bytesRead);
158+
}
159+
160+
// Return the next character
161+
return traits_type::to_int_type(*gptr());
162+
}
163+
164+
std::streamsize IFDStreamBuf::xsgetn(char *s, std::streamsize bytesToRead) {
165+
FCITX_D();
166+
const std::streamsize bytesAvailable = egptr() - gptr();
167+
168+
if (bytesToRead < bytesAvailable + IBufferSize) {
169+
// For small read, reuse std::streambuf logic
170+
// It will call overflow().
171+
return std::streambuf::xsgetn(s, bytesToRead);
172+
}
173+
174+
assert(bytesToRead >= bytesAvailable);
175+
const std::streamsize n = bytesToRead;
176+
177+
// Copy all existing buffer to output
178+
s = std::copy(gptr(), egptr(), s);
179+
bytesToRead -= bytesAvailable;
180+
181+
while (bytesToRead > 0) {
182+
const auto bytesRead = fs::safeRead(d->fd_, s, bytesToRead);
183+
if (bytesRead <= 0) {
184+
// EOF
185+
break;
186+
}
187+
188+
s += bytesRead;
189+
bytesToRead -= bytesRead;
190+
}
191+
192+
d->resetBuffer(0);
193+
194+
return n - bytesToRead;
195+
}
196+
197+
IFDStreamBuf::pos_type
198+
IFDStreamBuf::seekoff(off_type off, std::ios_base::seekdir dir,
199+
std::ios_base::openmode /*unused*/) {
200+
FCITX_D();
201+
if (!is_open()) {
202+
return -1;
203+
}
204+
205+
if (off != 0 || dir != std::ios_base::cur) {
206+
d->resetBuffer(0);
207+
}
208+
209+
if constexpr (sizeof(off_type) > sizeof(off_t)) {
210+
if (off > std::numeric_limits<off_t>::max() ||
211+
off < std::numeric_limits<off_t>::min()) {
212+
return -1;
213+
}
214+
}
215+
return lseek(fd(), off, dir);
216+
}
217+
218+
IFDStreamBuf::pos_type
219+
IFDStreamBuf::seekpos(pos_type pos, std::ios_base::openmode /*unused*/) {
220+
std::fpos<mbstate_t> f;
221+
return seekoff(pos - pos_type(0), std::ios_base::beg);
222+
}
223+
224+
class OFDStreamBufPrivate : public QPtrHolder<OFDStreamBuf> {
225+
public:
226+
OFDStreamBufPrivate(OFDStreamBuf *q) : QPtrHolder(q) {
227+
buffer_ = std::make_unique<char[]>(OBufferSize);
228+
resetBuffer();
229+
}
230+
231+
~OFDStreamBufPrivate() {
232+
FCITX_Q();
233+
if (q->is_open()) {
234+
q->sync();
235+
}
236+
}
237+
238+
void resetBuffer() {
239+
FCITX_Q();
240+
q->setp(buffer_.get(), buffer_.get() + OBufferSize - 1);
241+
}
242+
243+
int fd_ = -1;
244+
UnixFD fdOwner_;
245+
246+
std::unique_ptr<char[]> buffer_;
247+
};
248+
249+
OFDStreamBuf::OFDStreamBuf(UnixFD fd)
250+
: d_ptr(std::make_unique<OFDStreamBufPrivate>(this)) {
251+
FCITX_D();
252+
d->fd_ = fd.fd();
253+
d->fdOwner_ = std::move(fd);
254+
}
255+
256+
OFDStreamBuf::OFDStreamBuf(int fd)
257+
: d_ptr(std::make_unique<OFDStreamBufPrivate>(this)) {
258+
FCITX_D();
259+
d->fd_ = fd;
260+
}
261+
262+
OFDStreamBuf::~OFDStreamBuf() {}
263+
264+
bool OFDStreamBuf::is_open() const noexcept {
265+
FCITX_D();
266+
return d->fd_ != -1;
267+
}
268+
269+
int OFDStreamBuf::fd() const noexcept {
270+
FCITX_D();
271+
return d->fd_;
272+
}
273+
274+
OFDStreamBuf *OFDStreamBuf::close() {
275+
FCITX_D();
276+
d->fd_ = -1;
277+
d->fdOwner_.reset();
278+
279+
return this;
280+
}
281+
282+
OFDStreamBuf::int_type OFDStreamBuf::overflow(int_type ch) {
283+
FCITX_D();
284+
const char *p = pbase();
285+
std::streamsize bytesToWrite = pptr() - p;
286+
287+
const bool isEOF = traits_type::eq_int_type(ch, traits_type::eof());
288+
289+
if (!isEOF) {
290+
// We always reserver 1 byte in buffer, so even if
291+
// pptr == epptr, we can still write 1 byte.
292+
*pptr() = ch;
293+
++bytesToWrite;
294+
}
295+
296+
const auto bytesWritten = xwrite(d->fd_, p, bytesToWrite);
297+
d->resetBuffer();
298+
299+
if (bytesToWrite == bytesWritten) {
300+
return traits_type::not_eof(ch);
301+
}
302+
return traits_type::eof();
303+
}
304+
305+
int OFDStreamBuf::sync() {
306+
const auto ret = overflow(traits_type::eof());
307+
return traits_type::eq_int_type(ret, traits_type::eof()) ? 0 : -1;
308+
}
309+
310+
std::streamsize OFDStreamBuf::xsputn(const char *s, std::streamsize n) {
311+
FCITX_D();
312+
const std::streamsize bufferCapacity = epptr() - pptr();
313+
if (n < std::min<std::streamsize>(OBufferSize / 2, bufferCapacity)) {
314+
// For small write, reuse std::streambuf logic.
315+
return std::streambuf::xsputn(s, n);
316+
}
317+
318+
// Flush buffer if not empty.
319+
if (pbase() != pptr()) {
320+
#ifdef HAVE_SYS_UIO_H
321+
const std::streamsize bytesInBuffer = pptr() - pbase();
322+
const std::streamsize bytesWritten =
323+
xwritev(d->fd_, pbase(), bytesInBuffer, s, n);
324+
d->resetBuffer();
325+
// Return only bytes written belong to s.
326+
return (bytesWritten > bytesInBuffer) ? (bytesWritten - bytesInBuffer)
327+
: 0;
328+
#else
329+
if (sync() < 0) {
330+
return 0;
331+
}
332+
#endif
333+
}
334+
335+
return xwrite(d->fd_, s, n);
336+
}
337+
338+
OFDStreamBuf::pos_type
339+
OFDStreamBuf::seekoff(off_type off, std::ios_base::seekdir dir,
340+
std::ios_base::openmode /*unused*/) {
341+
FCITX_D();
342+
if (!is_open()) {
343+
return -1;
344+
}
345+
346+
if (off != 0 || dir != std::ios_base::cur) {
347+
d->resetBuffer();
348+
}
349+
350+
if constexpr (sizeof(off_type) > sizeof(off_t)) {
351+
if (off > std::numeric_limits<off_t>::max() ||
352+
off < std::numeric_limits<off_t>::min()) {
353+
return -1;
354+
}
355+
}
356+
return lseek(fd(), off, dir);
357+
}
358+
359+
OFDStreamBuf::pos_type
360+
OFDStreamBuf::seekpos(pos_type pos, std::ios_base::openmode /*unused*/) {
361+
std::fpos<mbstate_t> f;
362+
return seekoff(pos - pos_type(0), std::ios_base::beg);
363+
}
364+
365+
} // namespace fcitx

0 commit comments

Comments
 (0)