Flow-IPC 1.0.1
Flow-IPC project: Full implementation reference.
shared_name.cpp
Go to the documentation of this file.
1/* Flow-IPC: Core
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
20#include <boost/functional/hash/hash.hpp>
21#include <cctype>
22
23namespace ipc::util
24{
25
26// Initializers.
27
28/* Underscores are allowed for all applicable shared resource types. Ideally we'd use them to separate words, but 2
29 * factors are responsible for making _ the folder separator and using camelCase to separate words between pairs of
30 * nearby underscores: 1, characters are at a premium against S_MAX_LENGTH, so we shouldn't waste them on cosmetic
31 * concerns if possible; and 2, it's unclear what other special characters (not an alphanumeric) would be suitable. */
32const char Shared_name::S_SEPARATOR = '_';
33
34// See our doc header for discussion of chosen value.
35const size_t Shared_name::S_MAX_LENGTH = 75;
36
38
42
45
46// Implementations.
47
48Shared_name::Shared_name() = default;
51
52Shared_name Shared_name::ct(const char* src) // Static.
53{
54 Shared_name result;
55 result.m_raw_name.assign(src); // Copy it.
56 return result;
57}
58
59Shared_name Shared_name::ct(std::string&& src_moved) // Static.
60{
61 Shared_name result;
62 result.m_raw_name.assign(std::move(src_moved)); // Move it.
63 return result;
64}
65
68
69Shared_name& Shared_name::operator+=(const char* raw_name_to_append)
70{
71 // Existence/impl rationale: This is faster than if they had to: `+= Shared_name::ct(raw_name_to_append)`.
72
73 m_raw_name += raw_name_to_append;
74 return *this;
75}
76
78{
80 return operator+=(String_view(to_append.str()));
81}
82
83Shared_name& Shared_name::operator/=(const char* raw_name_to_append)
84{
85 // Existence/impl rationale: This is faster than if they had to: `/= Shared_name::ct(raw_name_to_append)`.
86
88 return operator/=(String_view(raw_name_to_append)); // Requires a strlen() internally but gets .reserve() in return.
89}
90
92{
94 return operator/=(String_view(to_append.str()));
95}
96
97Shared_name operator+(const Shared_name& src1, const char* raw_src2)
98{
99 // Existence/impl rationale: This is faster than if they had to: `src1 + Shared_name::ct(raw_src2)`.
100
101 using util::String_view;
102 return operator+(src1, String_view(raw_src2)); // Requires a strlen() internally but gets .reserve() in return.
103}
104
105Shared_name operator+(const char* raw_src1, const Shared_name& src2)
106{
107 // Existence rationale: For symmetry with overload: (src1, raw_src2).
108
109 using util::String_view;
110 return operator+(String_view(raw_src1), src2);
111}
112
114{
115 using util::String_view;
116 return src1 + String_view(src2.str());
117}
118
119Shared_name operator/(const Shared_name& src1, const char* raw_src2)
120{
121 // Existence/impl rationale: This is faster than if they had to: `src1 / Shared_name::ct(raw_src2)`.
122
123 using util::String_view;
124 return operator/(src1, String_view(raw_src2));
125}
126
127Shared_name operator/(const char* raw_src1, const Shared_name& src2)
128{
129 // Existence rationale: For symmetry with overload: (src1, raw_src2).
130
131 using util::String_view;
132 return operator/(String_view(raw_src1), src2);
133}
134
136{
137 return Shared_name(src1) /= src2;
138}
139
140const std::string& Shared_name::str() const
141{
142 return m_raw_name;
143}
144
145const char* Shared_name::native_str() const
146{
147 return m_raw_name.c_str();
148}
149
150size_t Shared_name::size() const
151{
152 return m_raw_name.size();
153}
154
156{
157 return m_raw_name.empty();
158}
159
161{
162 return (!empty()) && (m_raw_name.back() == S_SEPARATOR);
163}
164
166{
167 return (!m_raw_name.empty()) && (m_raw_name.front() == S_SEPARATOR);
168}
169
171{
172 m_raw_name.clear();
173}
174
176{
177 using flow::util::in_closed_range;
178 using std::isalnum;
179
180 // Keep in sync with sanitize()!
181
182 if (size() > S_MAX_LENGTH)
183 {
184 return false;
185 }
186 // else
187
188 /* Following is self-explanatory given the contract. It could be made much briefer, probably, by using built-in
189 * string functions and/or string_algo. However, for max perf, we'd rather do only one linear-time scan.
190 * Finally, could other characters be potentially allowed by the various sys calls? Maybe, but we'd rather be safe
191 * and sorry in terms of portability, and also these chars should be aesthetically good as a convention. */
192
193 bool prev_is_sep = false;
194 for (const auto ch : m_raw_name)
195 {
196 bool is_sep = ch == S_SEPARATOR;
197 if ((!is_sep) && (!isalnum(ch))) // Note: isalnum() explicitly checks for [A-Za-z0-9]; no internationalized stuff.
198 {
199 return false;
200 }
201 // else
202
203 if (is_sep)
204 {
205 if (prev_is_sep)
206 {
207 return false;
208 }
209 // else
210 prev_is_sep = true;
211 }
212 else
213 {
214 prev_is_sep = false; // Might be no-op.
215 }
216 } // for (ch : m_raw_name)
217
218 return true;
219} // Shared_name::sanitized()
220
222{
223 using std::string;
224
225 // SEPS are the 2 allowed input separator characters. They must be different. Try to keep this compile-time.
226 constexpr char SEPARATOR_ALT = '/';
227 static_assert(S_SEPARATOR != SEPARATOR_ALT,
228 "The real separator and the alt must be different characters.");
229 constexpr const char SEPS[] = { SEPARATOR_ALT, S_SEPARATOR, '\0' };
230
231 /* The following implementation, while self-explanatory, foregoes briefer approaches in favor of ~max perf.
232 * This includes (but isn't limited to) re-implementing sanitized() functionality instead of just returning
233 * actual sanitized() at the end, all so we can limit to just one linear scan of m_raw_name. */
234
235 // Keep in sync with sanitized()!
236
237 if (empty()) // Get rid of degenerate case for simplicity of below.
238 {
239 return true;
240 }
241 // else at least one character to read and (unless it's illegal) write.
242
243 string orig_backup_or_empty_if_unchanged; // Unused until !empty().
244
245 // Convenience function (should get inlined). At worst, it's constant time + a dealloc of m_raw_name.
246 const auto undo_changes_pre_return_false_func = [&]()
247 {
248 if (!orig_backup_or_empty_if_unchanged.empty())
249 {
250 // Sanitizations had been done to m_raw_name. So undo all of it (constant-time move; dealloc).
251 m_raw_name = std::move(orig_backup_or_empty_if_unchanged);
252 }
253 // else { No sanitizations had been done so far to m_raw_name, so nothing to undo. }
254 };
255
256 const size_t n = size();
257 size_t out_pos = 0;
258 for (size_t in_pos = 0; in_pos != n;
259 ++in_pos, ++out_pos)
260 {
261 /* We cannot predict, at the start, whether we'll ultimately result in a too-long string, because repeating
262 * separators can actually lower the length. However, if we get to this iteration, then we *will* be writing a
263 * character (unless ch is illegal, but then we'd return false anyway); hence we can detect ASAP whether the
264 * output is too long for sure. */
265 if (out_pos >= S_MAX_LENGTH)
266 {
267 undo_changes_pre_return_false_func();
268 return false;
269 }
270 // else not too long yet.
271
272 auto ch = m_raw_name[in_pos];
273 bool is_last = in_pos == n - 1; // Attn: intentionally not `const` (in_pos may change below).
274 const bool is_sep = (ch == S_SEPARATOR) || (ch == SEPARATOR_ALT);
275
276 // Perform nearly identical check to sanitized() (reminder: the 2 functions must remain in sync).
277 if ((!is_sep) && (!isalnum(ch))) // Note: isalnum() explicitly checks for [A-Za-z0-9]; no i18n stuff going on.
278 {
279 undo_changes_pre_return_false_func();
280 return false;
281 }
282 // else { Legal character ch, though it may be sanitized by replacing with a different value. }
283
284 if (is_sep)
285 {
286 ch = S_SEPARATOR; // Namely, all separator possibilities are normalized to this one.
287
288 /* Since we found a separator, immediately skip past any more adjacent separators by moving in_pos
289 * inside this { body }. Set it to the position just before the first non-separator. That ranges between
290 * not touching in_pos at all (i.e., only one separator after all); and setting it to (n - 1) (meaning either
291 * we're at end of input string, or the rest of it is all separators). Note for() will ++in_pos right after this.
292 *
293 * out_pos doesn't change; we are scanning string and writing it out at the same time. */
294 if (!is_last)
295 {
296 in_pos = m_raw_name.find_first_not_of(SEPS, in_pos + 1);
297 if (in_pos == string::npos)
298 {
299 in_pos = n - 1;
300 is_last = true; // We updated in_pos; so update this accordingly.
301 }
302 else
303 {
304 --in_pos;
305 assert(in_pos != (n - 1));
306 assert(!is_last);
307 }
308 }
309 } // if (is_sep)
310 // in_pos and is_last are finalized.
311
312 // Actually write out the character (though no need if it would result in no change).
313 if (m_raw_name[out_pos] != ch)
314 {
315 /* Make backup if this is the very first modification we're going to make.
316 * As a little optimization, skip that, if there are no characters left to scan (and thus write); as then
317 * there is no chance left of `return false`, hence no undo would occur. */
318 if (orig_backup_or_empty_if_unchanged.empty() && (!is_last))
319 {
320 // Alloc + linear-time copy; as advertised in contract this is a possible perf cost.
321 orig_backup_or_empty_if_unchanged = m_raw_name;
322 }
323 m_raw_name[out_pos] = ch;
324 }
325 } // for ({in|out}_pos in [0, size())) - But note in_pos can skip more, inside { body }.
326
327 // Passed gauntlet in terms of whether we can `return true` (we can and must). Finish up.
328 m_raw_name.erase(out_pos); // Constant-time. Possible no-op.
329 return true;
330 /* If !orig_backup_or_empty_if_unchanged.empty(), it is now deallocated.
331 * If we'd returned false with !orig_backup_or_empty_if_unchanged.empty(), then orig_backup_or_empty_if_unchanged
332 * would've been swapped with m_raw_name, and the latter's buffer would be deallocated instead. Either way,
333 * as we'd promised in contract, overall it's 1 alloc, 1 copy of m_raw_name, 1 dealloc, and some constant-time
334 * stuff. (And if orig_backup_or_empty_if_unchanged.empty(), then even those are skipped.) */
335} // Shared_name::sanitize()
336
338{
339 assert(!resource_type.empty());
340
341 const auto& SENTINEL = Shared_name::S_SENTINEL;
342 const auto& ROOT_MAGIC = Shared_name::S_ROOT_MAGIC;
343
344 Shared_name name;
345 name /= ROOT_MAGIC;
346 name /= resource_type;
347 name /= SENTINEL;
348 name /= SENTINEL;
349 name /= SENTINEL;
350 return name;
351} // Shared_name::build_conventional_non_session_based_shared_name()
352
353std::ostream& operator<<(std::ostream& os, const Shared_name& val)
354{
355 // Output char count for convenience: these can be at a premium.
356 if (val.str().empty())
357 {
358 return os << "null";
359 }
360 // else
361 return os << val.str().size() << '|' << val.str();
362}
363
364std::istream& operator>>(std::istream& is, Shared_name& val)
365{
366 // @todo Can be made a tiny bit faster by writing directly into val.m_raw_name. Requires `friend` or something.
367 std::string str;
368 is >> str;
369 val = Shared_name::ct(std::move(str));
370 return is;
371}
372
373bool operator==(const Shared_name& val1, const Shared_name& val2)
374{
375 return val1.str() == val2.str();
376}
377
378bool operator!=(const Shared_name& val1, const Shared_name& val2)
379{
380 return !(operator==(val1, val2));
381}
382
384{
385 /* Existence/impl rationale: This is faster than if they had to: `val1 == Shared_name::ct(val2)`.
386 * Existence rationale: It's also nice to be able to write: `val1 == "something"` (String_view implicitly cted). */
387
388 using util::String_view;
389 return std::operator==(String_view(val1.str()), val2);
390}
391
393{
394 return !(operator==(val1, val2));
395}
396
398{
399 return operator==(val2, val1);
400}
401
403{
404 return !(operator==(val1, val2));
405}
406
407bool operator<(const Shared_name& val1, const Shared_name& val2)
408{
409 return val1.str() < val2.str();
410}
411
412size_t hash_value(const Shared_name& val)
413{
414 using boost::hash;
415 using std::string;
416
417 return hash<string>()(val.str());
418}
419
420void swap(Shared_name& val1, Shared_name& val2)
421{
422 using std::swap;
423 swap(val1.m_raw_name, val2.m_raw_name);
424}
425
426} // namespace ipc::util
String-wrapping abstraction representing a name uniquely distinguishing a kernel-persistent entity fr...
bool sanitized() const
Returns true if and only if the contained name/fragment is sanitized according to length,...
const char * native_str() const
Returns (sans copying) pointer to NUL-terminated wrapped name string, suitable to pass into sys calls...
Shared_name & operator+=(const Shared_name &src_to_append)
Appends the given other Shared_name.
Definition: shared_name.cpp:77
Shared_name()
Constructs empty() name.
static const Shared_name S_SENTINEL
A Shared_name fragment, with no S_SEPARATOR characters inside, that represents a path component that ...
static Shared_name ct(const Source &src)
Copy-constructs from a char-sequence container (including string, util::String_view,...
static const Shared_name S_RESOURCE_TYPE_ID_SHM
Relative-folder fragment (no separators) identifying the resource type for: SHM pools.
std::string m_raw_name
The name or name fragment; see str().
static const Shared_name S_RESOURCE_TYPE_ID_MUTEX
Relative-folder fragment (no separators) identifying the resource type for: boost....
static const Shared_name S_EMPTY
A (default-cted) Shared_name. May be useful for functions returning const Shared_name&.
static const char S_SEPARATOR
Character we use, by convention, to separate conceptual folders within str().
bool has_trailing_separator() const
Returns true if and only if !this->empty(), and str() ends with the S_SEPARATOR character.
void clear()
Makes it so empty() == true.
bool empty() const
Returns true if and only if str().empty() == true.
Shared_name & operator/=(const Shared_name &src_to_append)
Appends a folder separator followed by the given other Shared_name.
Definition: shared_name.cpp:91
static const Shared_name S_1ST_OR_ONLY
A Shared_name fragment, with no S_SEPARATOR characters inside, that represents a path component that ...
bool absolute() const
Returns true if and only if the first character is S_SEPARATOR.
Shared_name & operator=(const Shared_name &src)
Copy-assigns from an existing Shared_name.
bool sanitize()
Best-effort attempt to turn sanitized() from false to true, unless it is already true; returns the fi...
const std::string & str() const
Returns (sans copying) ref to immutable entire wrapped name string, suitable to pass into sys calls w...
static const Shared_name S_ROOT_MAGIC
A Shared_name fragment, with no S_SEPARATOR characters inside, to be used in any Shared_name maintain...
size_t size() const
Returns str().size().
static const size_t S_MAX_LENGTH
Max value of size() such that, if str() used to name a supported shared resource, sys call safely won...
util::Shared_name Shared_name
Convenience alias for the commonly used type util::Shared_name.
Flow-IPC module containing miscellaneous general-use facilities that ubiquitously used by ~all Flow-I...
std::ostream & operator<<(std::ostream &os, const Native_handle &val)
Prints string representation of the given Native_handle to the given ostream.
bool operator<(Native_handle val1, Native_handle val2)
Returns a less-than comparison of two Native_handle objects, with the usual total ordering guarantees...
Shared_name build_conventional_non_session_based_shared_name(const Shared_name &resource_type)
Builds an absolute name according to the path convention explained in Shared_name class doc header; t...
bool operator==(util::String_view val1, const Shared_name &val2)
Returns true if and only if string(val1) == val2.str().
void swap(Shared_name &val1, Shared_name &val2)
Swaps two objects.
Shared_name operator/(const Shared_name &src1, const char *raw_src2)
Returns new object equal to Shared_name(src1) /= raw_src2.
bool operator!=(Native_handle val1, Native_handle val2)
Negation of similar ==.
size_t hash_value(Native_handle val)
Hasher of Native_handle for boost.unordered et al.
std::istream & operator>>(std::istream &is, Shared_name &val)
Reads Shared_name from the given istream; equivalent to reading string into Shared_name::str().
bool operator==(Native_handle val1, Native_handle val2)
Returns true if and only if the two Native_handle objects are the same underlying handle.
flow::util::String_view String_view
Short-hand for Flow's String_view.
Definition: util_fwd.hpp:109
Shared_name operator+(const Shared_name &src1, const char *raw_src2)
Returns new object equal to Shared_name(src1) += raw_src2.
Definition: shared_name.cpp:97