SDDS ToolKit Programs and Libraries for C and Python
Loading...
Searching...
No Matches
SDDSIO.cc
Go to the documentation of this file.
1/**
2 * @file SDDSIO.cc
3 * @brief Public byte-source and byte-sink adapters for the C++17 SDDS interface.
4 *
5 * @details Implements path, standard stream, borrowed or owned FILE, C++
6 * stream, and caller-owned memory adapters with explicit capability reporting.
7 *
8 * @copyright
9 * - (c) 2026 The University of Chicago
10 *
11 * @license
12 * This file is distributed under the terms of the Software License Agreement
13 * found in the file LICENSE included with this distribution.
14 */
15
16#include "SDDS.hpp"
17
18#include <algorithm>
19#include <cerrno>
20#include <cstring>
21#include <istream>
22#include <limits>
23#include <ostream>
24
25#if defined(_WIN32)
26# include <io.h>
27#else
28# include <unistd.h>
29#endif
30
31namespace sdds {
32namespace {
33
34[[noreturn]] void unsupported(const char *operation) {
35 throw StateError(ErrorKind::State, std::string("source does not support ") + operation);
36}
37
38[[noreturn]] void ioFailure(const char *operation) {
39 throw IoError(ErrorKind::Io, std::string(operation) + ": " + std::strerror(errno));
40}
41
42class FileInput final : public InputSource {
43 public:
44 FileInput(FILE *file, bool owned) : file_(file), owned_(owned) {
45 initialize();
46 }
47 explicit FileInput(std::filesystem::path path)
48 : file_(std::fopen(path.string().c_str(), "rb")), owned_(true),
49 path_(std::move(path)), reopenable_(true) {
50 initialize();
51 }
52 void initialize() {
53 if (!file_)
54 throw IoError(ErrorKind::Io,
55 path_.empty() ? "null FILE input" :
56 "unable to open input: " + std::string(std::strerror(errno)),
57 path_);
58 const auto position = tellFile();
59 seekable_ = position.has_value();
60 if (!seekable_) std::clearerr(file_);
61 }
62 ~FileInput() override { try { close(); } catch (...) {} }
63
64 std::size_t read(void *data, std::size_t size) override {
65 requireOpen();
66 const std::size_t count = std::fread(data, 1, size, file_);
67 if (count < size && std::ferror(file_)) ioFailure("FILE read failed");
68 return count;
69 }
70 bool eof() const override { return !file_ || std::feof(file_) != 0; }
71 SourceCapabilities capabilities() const noexcept override {
72 return {true, false, seekable_, false, false, false, reopenable_};
73 }
74 std::uint64_t tell() const override {
75 requireOpen();
76 const auto value = tellFile();
77 if (!value) ioFailure("FILE tell failed");
78 return *value;
79 }
80 void seek(std::uint64_t offset) override {
81 requireOpen();
82 if (!seekable_) unsupported("seek");
83#if defined(_WIN32)
84 if (_fseeki64(file_, static_cast<__int64>(offset), SEEK_SET) != 0)
85#else
86 if (offset > static_cast<std::uint64_t>(std::numeric_limits<off_t>::max()) ||
87 fseeko(file_, static_cast<off_t>(offset), SEEK_SET) != 0)
88#endif
89 ioFailure("FILE seek failed");
90 std::clearerr(file_);
91 }
92 void close() override {
93 if (!file_) return;
94 if (reopenable_) reopenOffset_ = tell();
95 FILE *closing = file_;
96 file_ = nullptr;
97 if (owned_ && std::fclose(closing) != 0) ioFailure("FILE close failed");
98 }
99 void reopen() override {
100 if (!reopenable_) unsupported("reopen");
101 if (file_) return;
102 file_ = std::fopen(path_.string().c_str(), "rb");
103 if (!file_) ioFailure("FILE reopen failed");
104 seekable_ = true;
105 seek(reopenOffset_);
106 }
107
108 private:
109 void requireOpen() const { if (!file_) unsupported("access after close"); }
110 std::optional<std::uint64_t> tellFile() const {
111#if defined(_WIN32)
112 const auto value = _ftelli64(file_);
113#else
114 const auto value = ftello(file_);
115#endif
116 if (value < 0) return std::nullopt;
117 return static_cast<std::uint64_t>(value);
118 }
119 FILE *file_ = nullptr;
120 bool owned_ = false;
121 bool seekable_ = false;
122 std::filesystem::path path_;
123 std::uint64_t reopenOffset_ = 0;
124 bool reopenable_ = false;
125};
126
127class FileOutput final : public OutputSink {
128 public:
129 FileOutput(FILE *file, bool owned) : file_(file), owned_(owned) {
130 initialize();
131 }
132 FileOutput(std::filesystem::path path, bool truncateFile)
133 : file_(std::fopen(path.string().c_str(), truncateFile ? "wb" : "r+b")),
134 owned_(true), path_(std::move(path)), reopenable_(true) {
135 initialize();
136 }
137 void initialize() {
138 if (!file_)
139 throw IoError(ErrorKind::Io,
140 path_.empty() ? "null FILE output" :
141 "unable to open output: " + std::string(std::strerror(errno)),
142 path_);
143 seekable_ = tellFile().has_value();
144 if (!seekable_) std::clearerr(file_);
145 }
146 ~FileOutput() override { try { close(); } catch (...) {} }
147
148 void write(const void *data, std::size_t size) override {
149 requireOpen();
150 if (size && std::fwrite(data, 1, size, file_) != size) ioFailure("FILE write failed");
151 }
152 SourceCapabilities capabilities() const noexcept override {
153 return {false, true, seekable_, seekable_, true, seekable_, reopenable_};
154 }
155 std::uint64_t tell() const override {
156 requireOpen();
157 const auto value = tellFile();
158 if (!value) ioFailure("FILE tell failed");
159 return *value;
160 }
161 void seek(std::uint64_t offset) override {
162 requireOpen();
163 if (!seekable_) unsupported("seek");
164#if defined(_WIN32)
165 if (_fseeki64(file_, static_cast<__int64>(offset), SEEK_SET) != 0)
166#else
167 if (offset > static_cast<std::uint64_t>(std::numeric_limits<off_t>::max()) ||
168 fseeko(file_, static_cast<off_t>(offset), SEEK_SET) != 0)
169#endif
170 ioFailure("FILE seek failed");
171 }
172 void truncate(std::uint64_t length) override {
173 requireOpen();
174 flush();
175#if defined(_WIN32)
176 if (_chsize_s(_fileno(file_), length) != 0)
177#else
178 if (length > static_cast<std::uint64_t>(std::numeric_limits<off_t>::max()) ||
179 ftruncate(fileno(file_), static_cast<off_t>(length)) != 0)
180#endif
181 ioFailure("FILE truncate failed");
182 }
183 void flush() override {
184 requireOpen();
185 if (std::fflush(file_) != 0) ioFailure("FILE flush failed");
186 }
187 void sync() override {
188 flush();
189#if defined(_WIN32)
190 if (_commit(_fileno(file_)) != 0)
191#else
192 if (::fsync(fileno(file_)) != 0)
193#endif
194 ioFailure("FILE sync failed");
195 }
196 void close() override {
197 if (!file_) return;
198 if (reopenable_) reopenOffset_ = tell();
199 FILE *closing = file_;
200 file_ = nullptr;
201 if (owned_) {
202 if (std::fclose(closing) != 0) ioFailure("FILE close failed");
203 } else if (std::fflush(closing) != 0) {
204 ioFailure("FILE flush failed");
205 }
206 }
207 void reopen() override {
208 if (!reopenable_) unsupported("reopen");
209 if (file_) return;
210 file_ = std::fopen(path_.string().c_str(), "r+b");
211 if (!file_) ioFailure("FILE reopen failed");
212 seekable_ = true;
213 seek(reopenOffset_);
214 }
215
216 private:
217 void requireOpen() const { if (!file_) unsupported("access after close"); }
218 std::optional<std::uint64_t> tellFile() const {
219#if defined(_WIN32)
220 const auto value = _ftelli64(file_);
221#else
222 const auto value = ftello(file_);
223#endif
224 if (value < 0) return std::nullopt;
225 return static_cast<std::uint64_t>(value);
226 }
227 FILE *file_ = nullptr;
228 bool owned_ = false;
229 bool seekable_ = false;
230 std::filesystem::path path_;
231 std::uint64_t reopenOffset_ = 0;
232 bool reopenable_ = false;
233};
234
235class StreamInput final : public InputSource {
236 public:
237 explicit StreamInput(std::istream &stream) : stream_(&stream) {
238 const auto position = stream_->tellg();
239 seekable_ = position != std::istream::pos_type(-1);
240 stream_->clear();
241 }
242 std::size_t read(void *data, std::size_t size) override {
243 requireOpen();
244 stream_->read(static_cast<char *>(data), static_cast<std::streamsize>(size));
245 const auto count = stream_->gcount();
246 if (stream_->bad()) throw IoError(ErrorKind::Io, "C++ stream read failed");
247 return static_cast<std::size_t>(count);
248 }
249 bool eof() const override { return !stream_ || stream_->eof(); }
250 SourceCapabilities capabilities() const noexcept override {
251 return {true, false, seekable_, false, false, false, false};
252 }
253 std::uint64_t tell() const override {
254 requireOpen();
255 const auto value = stream_->tellg();
256 if (value == std::istream::pos_type(-1)) throw IoError(ErrorKind::Io, "C++ stream tell failed");
257 return static_cast<std::uint64_t>(value);
258 }
259 void seek(std::uint64_t offset) override {
260 requireOpen();
261 if (!seekable_) unsupported("seek");
262 stream_->clear();
263 stream_->seekg(static_cast<std::streamoff>(offset));
264 if (!*stream_) throw IoError(ErrorKind::Io, "C++ stream seek failed");
265 }
266 void close() override { stream_ = nullptr; }
267
268 private:
269 void requireOpen() const { if (!stream_) unsupported("access after close"); }
270 std::istream *stream_ = nullptr;
271 bool seekable_ = false;
272};
273
274class StreamOutput final : public OutputSink {
275 public:
276 explicit StreamOutput(std::ostream &stream) : stream_(&stream) {
277 const auto position = stream_->tellp();
278 seekable_ = position != std::ostream::pos_type(-1);
279 stream_->clear();
280 }
281 void write(const void *data, std::size_t size) override {
282 requireOpen();
283 stream_->write(static_cast<const char *>(data), static_cast<std::streamsize>(size));
284 if (!*stream_) throw IoError(ErrorKind::Io, "C++ stream write failed");
285 }
286 SourceCapabilities capabilities() const noexcept override {
287 return {false, true, seekable_, false, true, false, false};
288 }
289 std::uint64_t tell() const override {
290 requireOpen();
291 const auto value = stream_->tellp();
292 if (value == std::ostream::pos_type(-1)) throw IoError(ErrorKind::Io, "C++ stream tell failed");
293 return static_cast<std::uint64_t>(value);
294 }
295 void seek(std::uint64_t offset) override {
296 requireOpen();
297 if (!seekable_) unsupported("seek");
298 stream_->clear();
299 stream_->seekp(static_cast<std::streamoff>(offset));
300 if (!*stream_) throw IoError(ErrorKind::Io, "C++ stream seek failed");
301 }
302 void flush() override {
303 requireOpen();
304 stream_->flush();
305 if (!*stream_) throw IoError(ErrorKind::Io, "C++ stream flush failed");
306 }
307 void close() override { if (stream_) flush(); stream_ = nullptr; }
308
309 private:
310 void requireOpen() const { if (!stream_) unsupported("access after close"); }
311 std::ostream *stream_ = nullptr;
312 bool seekable_ = false;
313};
314
315class MemoryInput final : public InputSource {
316 public:
317 MemoryInput(const void *data, std::size_t size)
318 : data_(static_cast<const std::uint8_t *>(data)), size_(size) {
319 if (!data_ && size_) throw StateError(ErrorKind::State, "null memory input");
320 }
321 std::size_t read(void *data, std::size_t size) override {
322 requireOpen();
323 const std::size_t count = std::min(size, size_ - position_);
324 if (count) std::memcpy(data, data_ + position_, count);
325 position_ += count;
326 return count;
327 }
328 bool eof() const override { return !open_ || position_ == size_; }
329 SourceCapabilities capabilities() const noexcept override {
330 return {true, false, true, false, false, false, false};
331 }
332 std::uint64_t tell() const override { requireOpen(); return position_; }
333 void seek(std::uint64_t offset) override {
334 requireOpen();
335 if (offset > size_) throw StateError(ErrorKind::State, "memory input seek is out of range");
336 position_ = static_cast<std::size_t>(offset);
337 }
338 void close() override { open_ = false; }
339
340 private:
341 void requireOpen() const { if (!open_) unsupported("access after close"); }
342 const std::uint8_t *data_ = nullptr;
343 std::size_t size_ = 0;
344 std::size_t position_ = 0;
345 bool open_ = true;
346};
347
348class MemoryOutput final : public OutputSink {
349 public:
350 explicit MemoryOutput(std::vector<std::uint8_t> &data) : data_(&data) {}
351 void write(const void *data, std::size_t size) override {
352 requireOpen();
353 if (size > std::numeric_limits<std::size_t>::max() - position_)
354 throw LimitError(ErrorKind::Limit, "memory output size overflow");
355 if (data_->size() < position_ + size) data_->resize(position_ + size);
356 if (size) std::memcpy(data_->data() + position_, data, size);
357 position_ += size;
358 }
359 SourceCapabilities capabilities() const noexcept override {
360 return {false, true, true, true, true, true, false};
361 }
362 std::uint64_t tell() const override { requireOpen(); return position_; }
363 void seek(std::uint64_t offset) override {
364 requireOpen();
365 if (offset > data_->size()) throw StateError(ErrorKind::State, "memory output seek is out of range");
366 position_ = static_cast<std::size_t>(offset);
367 }
368 void truncate(std::uint64_t length) override {
369 requireOpen();
370 if (length > std::numeric_limits<std::size_t>::max())
371 throw LimitError(ErrorKind::Limit, "memory output size overflow");
372 data_->resize(static_cast<std::size_t>(length));
373 position_ = std::min(position_, data_->size());
374 }
375 void flush() override { requireOpen(); }
376 void sync() override { requireOpen(); }
377 void close() override { open_ = false; }
378
379 private:
380 void requireOpen() const { if (!open_) unsupported("access after close"); }
381 std::vector<std::uint8_t> *data_ = nullptr;
382 std::size_t position_ = 0;
383 bool open_ = true;
384};
385
386} // namespace
387
388std::uint64_t InputSource::tell() const { unsupported("tell"); }
389void InputSource::seek(std::uint64_t) { unsupported("seek"); }
390void InputSource::reopen() { unsupported("reopen"); }
391std::uint64_t OutputSink::tell() const { unsupported("tell"); }
392void OutputSink::seek(std::uint64_t) { unsupported("seek"); }
393void OutputSink::truncate(std::uint64_t) { unsupported("truncate"); }
394void OutputSink::sync() { unsupported("sync"); }
395void OutputSink::reopen() { unsupported("reopen"); }
396
397std::unique_ptr<InputSource> inputFromPath(const std::filesystem::path &path) {
398 return std::make_unique<FileInput>(path);
399}
400std::unique_ptr<InputSource> inputFromStdin() {
401 return std::make_unique<FileInput>(stdin, false);
402}
403
404std::unique_ptr<InputSource> inputFromFile(FILE *file, bool takeOwnership) {
405 return std::make_unique<FileInput>(file, takeOwnership);
406}
407std::unique_ptr<InputSource> inputFromStream(std::istream &stream) {
408 return std::make_unique<StreamInput>(stream);
409}
410std::unique_ptr<InputSource> inputFromMemory(const void *data, std::size_t size) {
411 return std::make_unique<MemoryInput>(data, size);
412}
413std::unique_ptr<OutputSink> outputToPath(const std::filesystem::path &path, bool truncate) {
414 return std::make_unique<FileOutput>(path, truncate);
415}
416std::unique_ptr<OutputSink> outputToStdout() {
417 return std::make_unique<FileOutput>(stdout, false);
418}
419std::unique_ptr<OutputSink> outputToFile(FILE *file, bool takeOwnership) {
420 return std::make_unique<FileOutput>(file, takeOwnership);
421}
422std::unique_ptr<OutputSink> outputToStream(std::ostream &stream) {
423 return std::make_unique<StreamOutput>(stream);
424}
425std::unique_ptr<OutputSink> outputToMemory(std::vector<std::uint8_t> &data) {
426 return std::make_unique<MemoryOutput>(data);
427}
428
429} // namespace sdds
write(SddsFile sdds_file, output_file)
Mostly backward compatible with the PyLHC sdds module write() function.
Definition sdds.py:1967
SddsFile read(input_file)
Mostly backward compatible with the PyLHC sdds module read() function.
Definition sdds.py:1870