archive_r_ruby 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/NOTICE +71 -0
  4. data/VERSION +1 -0
  5. data/ext/archive_r/archive_r_ext.cc +9 -8
  6. data/ext/archive_r/vendor/archive_r/LICENSE +21 -0
  7. data/ext/archive_r/vendor/archive_r/NOTICE +71 -0
  8. data/ext/archive_r/vendor/archive_r/include/archive_r/data_stream.h +2 -2
  9. data/ext/archive_r/vendor/archive_r/include/archive_r/entry.h +8 -8
  10. data/ext/archive_r/vendor/archive_r/include/archive_r/multi_volume_stream_base.h +2 -2
  11. data/ext/archive_r/vendor/archive_r/include/archive_r/path_hierarchy_utils.h +0 -1
  12. data/ext/archive_r/vendor/archive_r/include/archive_r/platform_compat.h +8 -8
  13. data/ext/archive_r/vendor/archive_r/include/archive_r/traverser.h +2 -2
  14. data/ext/archive_r/vendor/archive_r/src/archive_stack_cursor.cc +10 -34
  15. data/ext/archive_r/vendor/archive_r/src/archive_stack_cursor.h +1 -3
  16. data/ext/archive_r/vendor/archive_r/src/archive_stack_orchestrator.cc +5 -6
  17. data/ext/archive_r/vendor/archive_r/src/archive_stack_orchestrator.h +2 -3
  18. data/ext/archive_r/vendor/archive_r/src/archive_type.cc +6 -13
  19. data/ext/archive_r/vendor/archive_r/src/archive_type.h +3 -3
  20. data/ext/archive_r/vendor/archive_r/src/entry.cc +2 -17
  21. data/ext/archive_r/vendor/archive_r/src/entry_fault_error.cc +23 -23
  22. data/ext/archive_r/vendor/archive_r/src/entry_impl.h +1 -2
  23. data/ext/archive_r/vendor/archive_r/src/multi_volume_manager.cc +3 -8
  24. data/ext/archive_r/vendor/archive_r/src/multi_volume_manager.h +2 -4
  25. data/ext/archive_r/vendor/archive_r/src/multi_volume_stream_base.cc +23 -14
  26. data/ext/archive_r/vendor/archive_r/src/path_hierarchy.cc +1 -4
  27. data/ext/archive_r/vendor/archive_r/src/path_hierarchy_utils.cc +14 -36
  28. data/ext/archive_r/vendor/archive_r/src/simple_profiler.h +64 -75
  29. data/ext/archive_r/vendor/archive_r/src/system_file_stream.cc +21 -26
  30. data/ext/archive_r/vendor/archive_r/src/system_file_stream.h +2 -2
  31. data/ext/archive_r/vendor/archive_r/src/traverser.cc +18 -42
  32. data/lib/archive_r.rb +5 -1
  33. metadata +11 -5
  34. data/LICENSE.txt +0 -97
  35. data/ext/archive_r/vendor/archive_r/LICENSE.txt +0 -97
@@ -18,35 +18,35 @@ EntryFaultError::EntryFaultError(EntryFault fault, const std::string &internal_m
18
18
  , _fault(std::move(fault)) {}
19
19
 
20
20
  EntryFaultError make_entry_fault_error(const std::string &message, PathHierarchy hierarchy, int errno_value) {
21
- EntryFault fault;
22
- fault.hierarchy = std::move(hierarchy);
23
- fault.message = message;
24
- fault.errno_value = errno_value;
25
- return EntryFaultError(std::move(fault));
21
+ EntryFault fault;
22
+ fault.hierarchy = std::move(hierarchy);
23
+ fault.message = message;
24
+ fault.errno_value = errno_value;
25
+ return EntryFaultError(std::move(fault));
26
26
  }
27
27
 
28
28
  std::string format_errno_error(const std::string &prefix, int err) {
29
- if (err == 0) {
30
- return prefix;
31
- }
32
-
33
- std::string message = prefix;
34
- message.append(": ");
35
- message.append(std::strerror(err));
36
- message.append(" (posix errno=");
37
- message.append(std::to_string(err));
38
- message.push_back(')');
39
- return message;
29
+ if (err == 0) {
30
+ return prefix;
31
+ }
32
+
33
+ std::string message = prefix;
34
+ message.append(": ");
35
+ message.append(std::strerror(err));
36
+ message.append(" (posix errno=");
37
+ message.append(std::to_string(err));
38
+ message.push_back(')');
39
+ return message;
40
40
  }
41
41
 
42
42
  std::string format_path_errno_error(const std::string &action, const std::string &path, int err) {
43
- std::string prefix = action;
44
- if (!path.empty()) {
45
- prefix.append(" '");
46
- prefix.append(path);
47
- prefix.push_back('\'');
48
- }
49
- return format_errno_error(prefix, err);
43
+ std::string prefix = action;
44
+ if (!path.empty()) {
45
+ prefix.append(" '");
46
+ prefix.append(path);
47
+ prefix.push_back('\'');
48
+ }
49
+ return format_errno_error(prefix, err);
50
50
  }
51
51
 
52
52
  std::string prefer_error_detail(const std::string &detail, const std::string &fallback) { return detail.empty() ? fallback : detail; }
@@ -19,7 +19,6 @@ public:
19
19
  // No libarchive dependency
20
20
  // Copy constructor and assignment (orchestrator is not copied)
21
21
  Impl(const Impl &other);
22
- Impl &operator=(const Impl &other);
23
22
 
24
23
  Impl(const PathHierarchy &hierarchy, std::shared_ptr<ArchiveStackOrchestrator> data_source_orchestrator, bool default_descent);
25
24
 
@@ -45,7 +44,7 @@ private:
45
44
  EntryMetadataMap _metadata;
46
45
  bool _descend_enabled = true; // Flag to control automatic descent
47
46
 
48
- std::shared_ptr<ArchiveStackOrchestrator> _orchestrator; ///< Active orchestrator (shared with traverser or detached copy)
47
+ std::shared_ptr<ArchiveStackOrchestrator> _orchestrator; ///< Active orchestrator (shared with traverser or detached copy)
49
48
  bool _shares_traverser_orchestrator = false;
50
49
 
51
50
  mutable std::optional<ArchiveOption> _archive_options;
@@ -10,9 +10,7 @@
10
10
 
11
11
  namespace archive_r {
12
12
 
13
- MultiVolumeManager::MultiVolumeGroup &MultiVolumeManager::get_or_create_multi_volume_group(const PathHierarchy &parent_hierarchy,
14
- const std::string &base_name,
15
- PathEntry::Parts::Ordering ordering) {
13
+ MultiVolumeManager::MultiVolumeGroup &MultiVolumeManager::get_or_create_multi_volume_group(const PathHierarchy &parent_hierarchy, const std::string &base_name, PathEntry::Parts::Ordering ordering) {
16
14
  GroupList &groups = _multi_volume_groups[parent_hierarchy];
17
15
  auto it = std::find_if(groups.begin(), groups.end(), [&](const MultiVolumeGroup &group) { return group.base_name == base_name; });
18
16
 
@@ -47,17 +45,14 @@ bool MultiVolumeManager::pop_group_for_parent(const PathHierarchy &parent_hierar
47
45
  return true;
48
46
  }
49
47
 
50
- void MultiVolumeManager::mark_entry_as_multi_volume(const PathHierarchy &entry_path, const std::string &base_name,
51
- PathEntry::Parts::Ordering ordering) {
48
+ void MultiVolumeManager::mark_entry_as_multi_volume(const PathHierarchy &entry_path, const std::string &base_name, PathEntry::Parts::Ordering ordering) {
52
49
  if (entry_path.empty() || pathhierarchy_is_multivolume(entry_path)) {
53
50
  return;
54
51
  }
55
52
 
56
53
  MultiVolumeGroup &target = get_or_create_multi_volume_group(parent_hierarchy(entry_path), base_name, ordering);
57
54
  auto &parts = target.parts;
58
- const bool exists = std::any_of(parts.begin(), parts.end(), [&](const PathHierarchy &existing) {
59
- return hierarchies_equal(existing, entry_path);
60
- });
55
+ const bool exists = std::any_of(parts.begin(), parts.end(), [&](const PathHierarchy &existing) { return hierarchies_equal(existing, entry_path); });
61
56
  if (!exists) {
62
57
  parts.push_back(entry_path);
63
58
  }
@@ -24,14 +24,12 @@ public:
24
24
  using GroupList = std::vector<MultiVolumeGroup>;
25
25
  using ParentGroupMap = std::map<PathHierarchy, GroupList, PathHierarchyLess>;
26
26
 
27
- void mark_entry_as_multi_volume(const PathHierarchy &entry_path, const std::string &base_name,
28
- PathEntry::Parts::Ordering ordering = PathEntry::Parts::Ordering::Natural);
27
+ void mark_entry_as_multi_volume(const PathHierarchy &entry_path, const std::string &base_name, PathEntry::Parts::Ordering ordering = PathEntry::Parts::Ordering::Natural);
29
28
 
30
29
  bool pop_multi_volume_group(const PathHierarchy &current_hierarchy, PathHierarchy &multi_volume_hierarchy);
31
30
 
32
31
  private:
33
- MultiVolumeGroup &get_or_create_multi_volume_group(const PathHierarchy &parent_hierarchy, const std::string &base_name,
34
- PathEntry::Parts::Ordering ordering);
32
+ MultiVolumeGroup &get_or_create_multi_volume_group(const PathHierarchy &parent_hierarchy, const std::string &base_name, PathEntry::Parts::Ordering ordering);
35
33
 
36
34
  bool pop_group_for_parent(const PathHierarchy &parent_hierarchy, MultiVolumeGroup &out_group);
37
35
 
@@ -33,9 +33,9 @@ struct MultiVolumeStreamBase::Impl {
33
33
  };
34
34
 
35
35
  MultiVolumeStreamBase::MultiVolumeStreamBase(PathHierarchy logical_path, bool supports_seek)
36
- : _logical_path(std::move(logical_path))
37
- , _supports_seek(supports_seek)
38
- , _impl(std::make_unique<Impl>(*this)) {
36
+ : _logical_path(std::move(logical_path))
37
+ , _supports_seek(supports_seek)
38
+ , _impl(std::make_unique<Impl>(*this)) {
39
39
  _impl->total_parts = pathhierarchy_volume_size(_logical_path);
40
40
  if (_impl->total_parts == 0) {
41
41
  throw std::invalid_argument("MultiVolumeStreamBase requires at least one volume component");
@@ -84,9 +84,7 @@ void MultiVolumeStreamBase::rewind() {
84
84
  _impl->logical_offset = 0;
85
85
  }
86
86
 
87
- bool MultiVolumeStreamBase::at_end() const {
88
- return _impl->active_part_index >= _impl->total_parts;
89
- }
87
+ bool MultiVolumeStreamBase::at_end() const { return _impl->active_part_index >= _impl->total_parts; }
90
88
 
91
89
  int64_t MultiVolumeStreamBase::seek(int64_t offset, int whence) {
92
90
  if (!_supports_seek) {
@@ -122,6 +120,17 @@ int64_t MultiVolumeStreamBase::seek(int64_t offset, int whence) {
122
120
 
123
121
  int64_t MultiVolumeStreamBase::tell() const { return _impl->logical_offset; }
124
122
 
123
+ int64_t MultiVolumeStreamBase::seek_within_single_part(int64_t offset, int whence) {
124
+ (void)offset;
125
+ (void)whence;
126
+ return -1;
127
+ }
128
+
129
+ int64_t MultiVolumeStreamBase::size_of_single_part(const PathHierarchy &single_part) {
130
+ (void)single_part;
131
+ return -1;
132
+ }
133
+
125
134
  void MultiVolumeStreamBase::Impl::ensure_part_active(std::size_t part_index) {
126
135
  if (part_open && open_part_index == part_index) {
127
136
  return;
@@ -173,14 +182,14 @@ bool MultiVolumeStreamBase::Impl::ensure_size_metadata() {
173
182
 
174
183
  int64_t MultiVolumeStreamBase::Impl::compute_target_offset(int64_t offset, int whence) const {
175
184
  switch (whence) {
176
- case SEEK_SET:
177
- return offset;
178
- case SEEK_CUR:
179
- return logical_offset + offset;
180
- case SEEK_END:
181
- return total_size + offset;
182
- default:
183
- return -1;
185
+ case SEEK_SET:
186
+ return offset;
187
+ case SEEK_CUR:
188
+ return logical_offset + offset;
189
+ case SEEK_END:
190
+ return total_size + offset;
191
+ default:
192
+ return -1;
184
193
  }
185
194
  }
186
195
 
@@ -107,9 +107,7 @@ PathHierarchy make_single_path(const std::string &root) {
107
107
 
108
108
  void append_single(PathHierarchy &hierarchy, std::string value) { hierarchy.emplace_back(PathEntry::single(std::move(value))); }
109
109
 
110
- void append_multi_volume(PathHierarchy &hierarchy, std::vector<std::string> parts, PathEntry::Parts::Ordering ordering) {
111
- hierarchy.emplace_back(PathEntry::multi_volume(std::move(parts), ordering));
112
- }
110
+ void append_multi_volume(PathHierarchy &hierarchy, std::vector<std::string> parts, PathEntry::Parts::Ordering ordering) { hierarchy.emplace_back(PathEntry::multi_volume(std::move(parts), ordering)); }
113
111
 
114
112
  PathHierarchy pathhierarchy_prefix_until(const PathHierarchy &hierarchy, size_t inclusive_index) {
115
113
  if (hierarchy.empty() || inclusive_index >= hierarchy.size()) {
@@ -126,5 +124,4 @@ PathHierarchy parent_hierarchy(const PathHierarchy &hierarchy) {
126
124
  return pathhierarchy_prefix_until(hierarchy, hierarchy.size() - 2);
127
125
  }
128
126
 
129
-
130
127
  } // namespace archive_r
@@ -73,10 +73,6 @@ bool determine_multi_volume_segments(const std::vector<PathHierarchy> &sources,
73
73
  }
74
74
 
75
75
  bool collect_multi_volume_parts(const std::vector<PathHierarchy> &sources, const CollapseSegments &segments, std::vector<std::string> &parts) {
76
- if (sources.empty()) {
77
- return false;
78
- }
79
-
80
76
  const std::size_t suffix_size = segments.suffix.size();
81
77
  const std::size_t required_size = segments.multi_volume_depth + 1 + suffix_size;
82
78
 
@@ -111,14 +107,6 @@ bool collect_multi_volume_parts(const std::vector<PathHierarchy> &sources, const
111
107
  return true;
112
108
  }
113
109
 
114
- bool flatten_single_entry(const PathEntry &entry, std::string &output) {
115
- if (entry.is_single()) {
116
- output = entry.single_value();
117
- return true;
118
- }
119
- return flatten_entry_to_string(entry, output);
120
- }
121
-
122
110
  } // namespace
123
111
 
124
112
  const std::string *path_entry_component_at(const PathEntry &entry, std::size_t index) {
@@ -129,14 +117,10 @@ const std::string *path_entry_component_at(const PathEntry &entry, std::size_t i
129
117
  return nullptr;
130
118
  }
131
119
 
132
- if (entry.is_multi_volume()) {
133
- const auto &parts = entry.multi_volume_parts().values;
134
- if (index < parts.size()) {
135
- return &parts[index];
136
- }
137
- return nullptr;
120
+ const auto &parts = entry.multi_volume_parts().values;
121
+ if (index < parts.size()) {
122
+ return &parts[index];
138
123
  }
139
-
140
124
  return nullptr;
141
125
  }
142
126
 
@@ -162,11 +146,7 @@ std::string pathhierarchy_volume_entry_name(const PathHierarchy &logical, std::s
162
146
  return {};
163
147
  }
164
148
 
165
- std::string entry_name;
166
- if (!flatten_single_entry(tail, entry_name)) {
167
- return {};
168
- }
169
- return entry_name;
149
+ return tail.single_value();
170
150
  }
171
151
 
172
152
  const auto &parts = tail.multi_volume_parts().values;
@@ -241,20 +221,18 @@ std::string path_entry_display(const PathEntry &entry) {
241
221
  if (entry.is_single()) {
242
222
  return entry.single_value();
243
223
  }
244
- if (entry.is_multi_volume()) {
245
- std::string value = "[";
246
- bool first = true;
247
- for (const auto &part : entry.multi_volume_parts().values) {
248
- if (!first) {
249
- value.push_back('|');
250
- }
251
- value += part;
252
- first = false;
224
+
225
+ std::string value = "[";
226
+ bool first = true;
227
+ for (const auto &part : entry.multi_volume_parts().values) {
228
+ if (!first) {
229
+ value.push_back('|');
253
230
  }
254
- value.push_back(']');
255
- return value;
231
+ value += part;
232
+ first = false;
256
233
  }
257
- return {};
234
+ value.push_back(']');
235
+ return value;
258
236
  }
259
237
 
260
238
  std::string hierarchy_display(const PathHierarchy &hierarchy) {
@@ -4,106 +4,95 @@
4
4
  #pragma once
5
5
  #include <string>
6
6
 
7
+ #include <algorithm>
7
8
  #include <chrono>
9
+ #include <iomanip>
8
10
  #include <iostream>
9
11
  #include <map>
10
- #include <vector>
11
- #include <algorithm>
12
- #include <iomanip>
13
12
  #include <mutex>
14
-
13
+ #include <vector>
15
14
 
16
15
  namespace archive_r {
17
16
  namespace internal {
18
17
 
19
-
20
-
21
18
  class SimpleProfiler {
22
19
  public:
23
- static SimpleProfiler& instance() {
24
- static SimpleProfiler inst;
25
- return inst;
20
+ static SimpleProfiler &instance() {
21
+ static SimpleProfiler inst;
22
+ return inst;
23
+ }
24
+
25
+ ~SimpleProfiler() { report(); }
26
+
27
+ void start(const std::string &name) {
28
+ std::lock_guard<std::mutex> lock(mutex_);
29
+ auto now = std::chrono::high_resolution_clock::now();
30
+ start_times_[name] = now;
31
+ }
32
+
33
+ void stop(const std::string &name) {
34
+ std::lock_guard<std::mutex> lock(mutex_);
35
+ auto now = std::chrono::high_resolution_clock::now();
36
+ auto it = start_times_.find(name);
37
+ if (it != start_times_.end()) {
38
+ auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(now - it->second).count();
39
+ durations_[name] += duration;
40
+ counts_[name]++;
26
41
  }
42
+ }
27
43
 
28
- ~SimpleProfiler() {
29
- report();
30
- }
44
+ void report() {
45
+ std::lock_guard<std::mutex> lock(mutex_);
46
+ if (durations_.empty())
47
+ return;
31
48
 
32
- void start(const std::string& name) {
33
- std::lock_guard<std::mutex> lock(mutex_);
34
- auto now = std::chrono::high_resolution_clock::now();
35
- start_times_[name] = now;
49
+ std::cout << "\n=== Profiling Report (archive_r::internal) ===" << std::endl;
50
+ std::vector<std::pair<std::string, long long>> sorted_durations;
51
+ for (const auto &pair : durations_) {
52
+ sorted_durations.push_back(pair);
36
53
  }
37
54
 
38
- void stop(const std::string& name) {
39
- std::lock_guard<std::mutex> lock(mutex_);
40
- auto now = std::chrono::high_resolution_clock::now();
41
- auto it = start_times_.find(name);
42
- if (it != start_times_.end()) {
43
- auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>(now - it->second).count();
44
- durations_[name] += duration;
45
- counts_[name]++;
46
- }
47
- }
55
+ std::sort(sorted_durations.begin(), sorted_durations.end(), [](const auto &a, const auto &b) { return a.second > b.second; });
48
56
 
49
- void report() {
50
- std::lock_guard<std::mutex> lock(mutex_);
51
- if (durations_.empty()) return;
52
-
53
- std::cout << "\n=== Profiling Report (archive_r::internal) ===" << std::endl;
54
- std::vector<std::pair<std::string, long long>> sorted_durations;
55
- for (const auto& pair : durations_) {
56
- sorted_durations.push_back(pair);
57
- }
58
-
59
- std::sort(sorted_durations.begin(), sorted_durations.end(),
60
- [](const auto& a, const auto& b) { return a.second > b.second; });
61
-
62
- std::cout << std::left << std::setw(40) << "Name"
63
- << std::right << std::setw(15) << "Total (ms)"
64
- << std::setw(10) << "Count"
65
- << std::setw(15) << "Avg (us)" << std::endl;
66
- std::cout << std::string(80, '-') << std::endl;
67
-
68
- for (const auto& pair : sorted_durations) {
69
- const auto& name = pair.first;
70
- long long total_ns = pair.second;
71
- long long count = counts_[name];
72
- double avg_ns = count > 0 ? (double)total_ns / count : 0;
73
-
74
- std::cout << std::left << std::setw(40) << name
75
- << std::right << std::setw(15) << std::fixed << std::setprecision(3) << total_ns / 1000000.0
76
- << std::setw(10) << count
77
- << std::setw(15) << std::fixed << std::setprecision(3) << avg_ns / 1000.0
78
- << std::endl;
79
- }
80
- std::cout << "==============================================\n" << std::endl;
81
- }
57
+ std::cout << std::left << std::setw(40) << "Name" << std::right << std::setw(15) << "Total (ms)" << std::setw(10) << "Count" << std::setw(15) << "Avg (us)" << std::endl;
58
+ std::cout << std::string(80, '-') << std::endl;
59
+
60
+ for (const auto &pair : sorted_durations) {
61
+ const auto &name = pair.first;
62
+ long long total_ns = pair.second;
63
+ long long count = counts_[name];
64
+ double avg_ns = count > 0 ? (double)total_ns / count : 0;
82
65
 
83
- void reset() {
84
- std::lock_guard<std::mutex> lock(mutex_);
85
- start_times_.clear();
86
- durations_.clear();
87
- counts_.clear();
66
+ std::cout << std::left << std::setw(40) << name << std::right << std::setw(15) << std::fixed << std::setprecision(3) << total_ns / 1000000.0 << std::setw(10) << count << std::setw(15)
67
+ << std::fixed << std::setprecision(3) << avg_ns / 1000.0 << std::endl;
88
68
  }
69
+ std::cout << "==============================================\n" << std::endl;
70
+ }
71
+
72
+ void reset() {
73
+ std::lock_guard<std::mutex> lock(mutex_);
74
+ start_times_.clear();
75
+ durations_.clear();
76
+ counts_.clear();
77
+ }
89
78
 
90
79
  private:
91
- std::map<std::string, std::chrono::high_resolution_clock::time_point> start_times_;
92
- std::map<std::string, long long> durations_;
93
- std::map<std::string, long long> counts_;
94
- std::mutex mutex_;
80
+ std::map<std::string, std::chrono::high_resolution_clock::time_point> start_times_;
81
+ std::map<std::string, long long> durations_;
82
+ std::map<std::string, long long> counts_;
83
+ std::mutex mutex_;
95
84
  };
96
85
 
97
86
  class ScopedTimer {
98
87
  public:
99
- ScopedTimer(const std::string& name) : name_(name) {
100
- SimpleProfiler::instance().start(name_);
101
- }
102
- ~ScopedTimer() {
103
- SimpleProfiler::instance().stop(name_);
104
- }
88
+ ScopedTimer(const std::string &name)
89
+ : name_(name) {
90
+ SimpleProfiler::instance().start(name_);
91
+ }
92
+ ~ScopedTimer() { SimpleProfiler::instance().stop(name_); }
93
+
105
94
  private:
106
- std::string name_;
95
+ std::string name_;
107
96
  };
108
97
 
109
98
  } // namespace internal
@@ -11,16 +11,16 @@
11
11
  #include <cstdio>
12
12
  #include <filesystem>
13
13
  #include <stdexcept>
14
+ #include <string_view>
14
15
  #include <sys/stat.h>
15
16
  #include <system_error>
16
- #include <string_view>
17
17
  #include <utility>
18
18
  #include <vector>
19
19
 
20
20
  #if !defined(_WIN32)
21
- # include <grp.h>
22
- # include <pwd.h>
23
- # include <unistd.h>
21
+ #include <grp.h>
22
+ #include <pwd.h>
23
+ #include <unistd.h>
24
24
  #endif
25
25
 
26
26
  namespace archive_r {
@@ -66,21 +66,10 @@ static bool lookup_groupname(gid_t gid, std::string &name_out) {
66
66
  } // namespace
67
67
 
68
68
  SystemFileStream::SystemFileStream(PathHierarchy logical_path)
69
- : MultiVolumeStreamBase(std::move(logical_path), true)
70
- , _handle(nullptr) {
71
- if (_logical_path.empty()) {
72
- throw std::invalid_argument("Root file hierarchy cannot be empty");
73
- }
69
+ : MultiVolumeStreamBase(std::move(logical_path), true)
70
+ , _handle(nullptr) {}
74
71
 
75
- const PathEntry &root_entry = _logical_path.front();
76
- if (!root_entry.is_single() && !root_entry.is_multi_volume()) {
77
- throw std::invalid_argument("Root file hierarchy must be a single file or multi-volume source");
78
- }
79
- }
80
-
81
- SystemFileStream::~SystemFileStream() {
82
- deactivate_active_part();
83
- }
72
+ SystemFileStream::~SystemFileStream() { deactivate_active_part(); }
84
73
 
85
74
  void SystemFileStream::open_single_part(const PathHierarchy &single_part) {
86
75
  const PathEntry &entry = single_part.back();
@@ -100,7 +89,7 @@ void SystemFileStream::open_single_part(const PathHierarchy &single_part) {
100
89
  // Enable larger buffering on Windows to improve performance
101
90
  // Use 64KB buffer to match StreamArchive's buffer size
102
91
  if (_handle) {
103
- std::setvbuf(_handle, nullptr, _IOFBF, 65536);
92
+ std::setvbuf(_handle, nullptr, _IOFBF, 65536);
104
93
  }
105
94
  #endif
106
95
  }
@@ -168,6 +157,8 @@ void SystemFileStream::report_read_failure(int err) {
168
157
  throw make_entry_fault_error(detailed, _logical_path, err);
169
158
  }
170
159
 
160
+ // Collect filesystem metadata for the root path in the hierarchy.
161
+ // Returns an empty info struct when metadata is unavailable or disallowed.
171
162
  FilesystemMetadataInfo collect_root_path_metadata(const PathHierarchy &hierarchy, const std::unordered_set<std::string> &allowed_keys) {
172
163
  FilesystemMetadataInfo info;
173
164
 
@@ -187,6 +178,12 @@ FilesystemMetadataInfo collect_root_path_metadata(const PathHierarchy &hierarchy
187
178
  return info;
188
179
  }
189
180
 
181
+ ec.clear();
182
+ const bool exists = entry.exists(ec);
183
+ if (ec || !exists) {
184
+ return info;
185
+ }
186
+
190
187
  mode_t filetype = 0;
191
188
  uint64_t size = 0;
192
189
 
@@ -219,9 +216,7 @@ FilesystemMetadataInfo collect_root_path_metadata(const PathHierarchy &hierarchy
219
216
  info.filetype = filetype;
220
217
  EntryMetadataMap metadata;
221
218
  if (!allowed_keys.empty()) {
222
- const auto wants = [&allowed_keys](std::string_view key) {
223
- return allowed_keys.find(std::string(key)) != allowed_keys.end();
224
- };
219
+ const auto wants = [&allowed_keys](std::string_view key) { return allowed_keys.find(std::string(key)) != allowed_keys.end(); };
225
220
 
226
221
  // Path hierarchy / directory entry derived metadata
227
222
  if (wants("pathname")) {
@@ -246,10 +241,10 @@ FilesystemMetadataInfo collect_root_path_metadata(const PathHierarchy &hierarchy
246
241
  }
247
242
 
248
243
  const bool needs_stat = (wants("size") && size == 0)
249
- #if !defined(_WIN32)
250
- || wants("uid") || wants("gid") || wants("uname") || wants("gname")
251
- #endif
252
- ;
244
+ #if !defined(_WIN32)
245
+ || wants("uid") || wants("gid") || wants("uname") || wants("gname")
246
+ #endif
247
+ ;
253
248
 
254
249
  struct stat stat_buffer;
255
250
  bool have_stat = false;
@@ -10,10 +10,10 @@
10
10
  #include <unordered_set>
11
11
  #include <vector>
12
12
 
13
- #include "archive_r/multi_volume_stream_base.h"
14
- #include "archive_r/platform_compat.h"
15
13
  #include "archive_r/entry_metadata.h"
14
+ #include "archive_r/multi_volume_stream_base.h"
16
15
  #include "archive_r/path_hierarchy.h"
16
+ #include "archive_r/platform_compat.h"
17
17
 
18
18
  namespace archive_r {
19
19