source: mainline/uspace/lib/c/generic/str.c@ 8e893ae

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 8e893ae was 8e893ae, checked in by Martin Decky <martin@…>, 13 years ago

avoid comparison with 0 if the type is unsigned

  • Property mode set to 100644
File size: 37.7 KB
Line 
1/*
2 * Copyright (c) 2005 Martin Decky
3 * Copyright (c) 2008 Jiri Svoboda
4 * Copyright (c) 2011 Martin Sucha
5 * Copyright (c) 2011 Oleg Romanenko
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * - The name of the author may not be used to endorse or promote products
18 * derived from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/** @addtogroup libc
33 * @{
34 */
35/** @file
36 */
37
38#include <str.h>
39#include <stdlib.h>
40#include <assert.h>
41#include <stdint.h>
42#include <ctype.h>
43#include <malloc.h>
44#include <errno.h>
45#include <align.h>
46#include <mem.h>
47#include <str.h>
48
49/** Check the condition if wchar_t is signed */
50#ifdef WCHAR_IS_UNSIGNED
51 #define WCHAR_SIGNED_CHECK(cond) (true)
52#else
53 #define WCHAR_SIGNED_CHECK(cond) (cond)
54#endif
55
56/** Byte mask consisting of lowest @n bits (out of 8) */
57#define LO_MASK_8(n) ((uint8_t) ((1 << (n)) - 1))
58
59/** Byte mask consisting of lowest @n bits (out of 32) */
60#define LO_MASK_32(n) ((uint32_t) ((1 << (n)) - 1))
61
62/** Byte mask consisting of highest @n bits (out of 8) */
63#define HI_MASK_8(n) (~LO_MASK_8(8 - (n)))
64
65/** Number of data bits in a UTF-8 continuation byte */
66#define CONT_BITS 6
67
68/** Decode a single character from a string.
69 *
70 * Decode a single character from a string of size @a size. Decoding starts
71 * at @a offset and this offset is moved to the beginning of the next
72 * character. In case of decoding error, offset generally advances at least
73 * by one. However, offset is never moved beyond size.
74 *
75 * @param str String (not necessarily NULL-terminated).
76 * @param offset Byte offset in string where to start decoding.
77 * @param size Size of the string (in bytes).
78 *
79 * @return Value of decoded character, U_SPECIAL on decoding error or
80 * NULL if attempt to decode beyond @a size.
81 *
82 */
83wchar_t str_decode(const char *str, size_t *offset, size_t size)
84{
85 if (*offset + 1 > size)
86 return 0;
87
88 /* First byte read from string */
89 uint8_t b0 = (uint8_t) str[(*offset)++];
90
91 /* Determine code length */
92
93 unsigned int b0_bits; /* Data bits in first byte */
94 unsigned int cbytes; /* Number of continuation bytes */
95
96 if ((b0 & 0x80) == 0) {
97 /* 0xxxxxxx (Plain ASCII) */
98 b0_bits = 7;
99 cbytes = 0;
100 } else if ((b0 & 0xe0) == 0xc0) {
101 /* 110xxxxx 10xxxxxx */
102 b0_bits = 5;
103 cbytes = 1;
104 } else if ((b0 & 0xf0) == 0xe0) {
105 /* 1110xxxx 10xxxxxx 10xxxxxx */
106 b0_bits = 4;
107 cbytes = 2;
108 } else if ((b0 & 0xf8) == 0xf0) {
109 /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
110 b0_bits = 3;
111 cbytes = 3;
112 } else {
113 /* 10xxxxxx -- unexpected continuation byte */
114 return U_SPECIAL;
115 }
116
117 if (*offset + cbytes > size)
118 return U_SPECIAL;
119
120 wchar_t ch = b0 & LO_MASK_8(b0_bits);
121
122 /* Decode continuation bytes */
123 while (cbytes > 0) {
124 uint8_t b = (uint8_t) str[(*offset)++];
125
126 /* Must be 10xxxxxx */
127 if ((b & 0xc0) != 0x80)
128 return U_SPECIAL;
129
130 /* Shift data bits to ch */
131 ch = (ch << CONT_BITS) | (wchar_t) (b & LO_MASK_8(CONT_BITS));
132 cbytes--;
133 }
134
135 return ch;
136}
137
138/** Encode a single character to string representation.
139 *
140 * Encode a single character to string representation (i.e. UTF-8) and store
141 * it into a buffer at @a offset. Encoding starts at @a offset and this offset
142 * is moved to the position where the next character can be written to.
143 *
144 * @param ch Input character.
145 * @param str Output buffer.
146 * @param offset Byte offset where to start writing.
147 * @param size Size of the output buffer (in bytes).
148 *
149 * @return EOK if the character was encoded successfully, EOVERFLOW if there
150 * was not enough space in the output buffer or EINVAL if the character
151 * code was invalid.
152 */
153int chr_encode(const wchar_t ch, char *str, size_t *offset, size_t size)
154{
155 if (*offset >= size)
156 return EOVERFLOW;
157
158 if (!chr_check(ch))
159 return EINVAL;
160
161 /* Unsigned version of ch (bit operations should only be done
162 on unsigned types). */
163 uint32_t cc = (uint32_t) ch;
164
165 /* Determine how many continuation bytes are needed */
166
167 unsigned int b0_bits; /* Data bits in first byte */
168 unsigned int cbytes; /* Number of continuation bytes */
169
170 if ((cc & ~LO_MASK_32(7)) == 0) {
171 b0_bits = 7;
172 cbytes = 0;
173 } else if ((cc & ~LO_MASK_32(11)) == 0) {
174 b0_bits = 5;
175 cbytes = 1;
176 } else if ((cc & ~LO_MASK_32(16)) == 0) {
177 b0_bits = 4;
178 cbytes = 2;
179 } else if ((cc & ~LO_MASK_32(21)) == 0) {
180 b0_bits = 3;
181 cbytes = 3;
182 } else {
183 /* Codes longer than 21 bits are not supported */
184 return EINVAL;
185 }
186
187 /* Check for available space in buffer */
188 if (*offset + cbytes >= size)
189 return EOVERFLOW;
190
191 /* Encode continuation bytes */
192 unsigned int i;
193 for (i = cbytes; i > 0; i--) {
194 str[*offset + i] = 0x80 | (cc & LO_MASK_32(CONT_BITS));
195 cc = cc >> CONT_BITS;
196 }
197
198 /* Encode first byte */
199 str[*offset] = (cc & LO_MASK_32(b0_bits)) | HI_MASK_8(8 - b0_bits - 1);
200
201 /* Advance offset */
202 *offset += cbytes + 1;
203
204 return EOK;
205}
206
207/** Get size of string.
208 *
209 * Get the number of bytes which are used by the string @a str (excluding the
210 * NULL-terminator).
211 *
212 * @param str String to consider.
213 *
214 * @return Number of bytes used by the string
215 *
216 */
217size_t str_size(const char *str)
218{
219 size_t size = 0;
220
221 while (*str++ != 0)
222 size++;
223
224 return size;
225}
226
227/** Get size of wide string.
228 *
229 * Get the number of bytes which are used by the wide string @a str (excluding the
230 * NULL-terminator).
231 *
232 * @param str Wide string to consider.
233 *
234 * @return Number of bytes used by the wide string
235 *
236 */
237size_t wstr_size(const wchar_t *str)
238{
239 return (wstr_length(str) * sizeof(wchar_t));
240}
241
242/** Get size of string with length limit.
243 *
244 * Get the number of bytes which are used by up to @a max_len first
245 * characters in the string @a str. If @a max_len is greater than
246 * the length of @a str, the entire string is measured (excluding the
247 * NULL-terminator).
248 *
249 * @param str String to consider.
250 * @param max_len Maximum number of characters to measure.
251 *
252 * @return Number of bytes used by the characters.
253 *
254 */
255size_t str_lsize(const char *str, size_t max_len)
256{
257 size_t len = 0;
258 size_t offset = 0;
259
260 while (len < max_len) {
261 if (str_decode(str, &offset, STR_NO_LIMIT) == 0)
262 break;
263
264 len++;
265 }
266
267 return offset;
268}
269
270/** Get size of string with size limit.
271 *
272 * Get the number of bytes which are used by the string @a str
273 * (excluding the NULL-terminator), but no more than @max_size bytes.
274 *
275 * @param str String to consider.
276 * @param max_size Maximum number of bytes to measure.
277 *
278 * @return Number of bytes used by the string
279 *
280 */
281size_t str_nsize(const char *str, size_t max_size)
282{
283 size_t size = 0;
284
285 while ((*str++ != 0) && (size < max_size))
286 size++;
287
288 return size;
289}
290
291/** Get size of wide string with size limit.
292 *
293 * Get the number of bytes which are used by the wide string @a str
294 * (excluding the NULL-terminator), but no more than @max_size bytes.
295 *
296 * @param str Wide string to consider.
297 * @param max_size Maximum number of bytes to measure.
298 *
299 * @return Number of bytes used by the wide string
300 *
301 */
302size_t wstr_nsize(const wchar_t *str, size_t max_size)
303{
304 return (wstr_nlength(str, max_size) * sizeof(wchar_t));
305}
306
307/** Get size of wide string with length limit.
308 *
309 * Get the number of bytes which are used by up to @a max_len first
310 * wide characters in the wide string @a str. If @a max_len is greater than
311 * the length of @a str, the entire wide string is measured (excluding the
312 * NULL-terminator).
313 *
314 * @param str Wide string to consider.
315 * @param max_len Maximum number of wide characters to measure.
316 *
317 * @return Number of bytes used by the wide characters.
318 *
319 */
320size_t wstr_lsize(const wchar_t *str, size_t max_len)
321{
322 return (wstr_nlength(str, max_len * sizeof(wchar_t)) * sizeof(wchar_t));
323}
324
325/** Get number of characters in a string.
326 *
327 * @param str NULL-terminated string.
328 *
329 * @return Number of characters in string.
330 *
331 */
332size_t str_length(const char *str)
333{
334 size_t len = 0;
335 size_t offset = 0;
336
337 while (str_decode(str, &offset, STR_NO_LIMIT) != 0)
338 len++;
339
340 return len;
341}
342
343/** Get number of characters in a wide string.
344 *
345 * @param str NULL-terminated wide string.
346 *
347 * @return Number of characters in @a str.
348 *
349 */
350size_t wstr_length(const wchar_t *wstr)
351{
352 size_t len = 0;
353
354 while (*wstr++ != 0)
355 len++;
356
357 return len;
358}
359
360/** Get number of characters in a string with size limit.
361 *
362 * @param str NULL-terminated string.
363 * @param size Maximum number of bytes to consider.
364 *
365 * @return Number of characters in string.
366 *
367 */
368size_t str_nlength(const char *str, size_t size)
369{
370 size_t len = 0;
371 size_t offset = 0;
372
373 while (str_decode(str, &offset, size) != 0)
374 len++;
375
376 return len;
377}
378
379/** Get number of characters in a string with size limit.
380 *
381 * @param str NULL-terminated string.
382 * @param size Maximum number of bytes to consider.
383 *
384 * @return Number of characters in string.
385 *
386 */
387size_t wstr_nlength(const wchar_t *str, size_t size)
388{
389 size_t len = 0;
390 size_t limit = ALIGN_DOWN(size, sizeof(wchar_t));
391 size_t offset = 0;
392
393 while ((offset < limit) && (*str++ != 0)) {
394 len++;
395 offset += sizeof(wchar_t);
396 }
397
398 return len;
399}
400
401/** Check whether character is plain ASCII.
402 *
403 * @return True if character is plain ASCII.
404 *
405 */
406bool ascii_check(wchar_t ch)
407{
408 if (WCHAR_SIGNED_CHECK(ch >= 0) && (ch <= 127))
409 return true;
410
411 return false;
412}
413
414/** Check whether character is valid
415 *
416 * @return True if character is a valid Unicode code point.
417 *
418 */
419bool chr_check(wchar_t ch)
420{
421 if (WCHAR_SIGNED_CHECK(ch >= 0) && (ch <= 1114111))
422 return true;
423
424 return false;
425}
426
427/** Compare two NULL terminated strings.
428 *
429 * Do a char-by-char comparison of two NULL-terminated strings.
430 * The strings are considered equal iff they consist of the same
431 * characters on the minimum of their lengths.
432 *
433 * @param s1 First string to compare.
434 * @param s2 Second string to compare.
435 *
436 * @return 0 if the strings are equal, -1 if first is smaller,
437 * 1 if second smaller.
438 *
439 */
440int str_cmp(const char *s1, const char *s2)
441{
442 wchar_t c1 = 0;
443 wchar_t c2 = 0;
444
445 size_t off1 = 0;
446 size_t off2 = 0;
447
448 while (true) {
449 c1 = str_decode(s1, &off1, STR_NO_LIMIT);
450 c2 = str_decode(s2, &off2, STR_NO_LIMIT);
451
452 if (c1 < c2)
453 return -1;
454
455 if (c1 > c2)
456 return 1;
457
458 if (c1 == 0 || c2 == 0)
459 break;
460 }
461
462 return 0;
463}
464
465/** Compare two NULL terminated strings with length limit.
466 *
467 * Do a char-by-char comparison of two NULL-terminated strings.
468 * The strings are considered equal iff they consist of the same
469 * characters on the minimum of their lengths and the length limit.
470 *
471 * @param s1 First string to compare.
472 * @param s2 Second string to compare.
473 * @param max_len Maximum number of characters to consider.
474 *
475 * @return 0 if the strings are equal, -1 if first is smaller,
476 * 1 if second smaller.
477 *
478 */
479int str_lcmp(const char *s1, const char *s2, size_t max_len)
480{
481 wchar_t c1 = 0;
482 wchar_t c2 = 0;
483
484 size_t off1 = 0;
485 size_t off2 = 0;
486
487 size_t len = 0;
488
489 while (true) {
490 if (len >= max_len)
491 break;
492
493 c1 = str_decode(s1, &off1, STR_NO_LIMIT);
494 c2 = str_decode(s2, &off2, STR_NO_LIMIT);
495
496 if (c1 < c2)
497 return -1;
498
499 if (c1 > c2)
500 return 1;
501
502 if (c1 == 0 || c2 == 0)
503 break;
504
505 ++len;
506 }
507
508 return 0;
509
510}
511
512/** Copy string.
513 *
514 * Copy source string @a src to destination buffer @a dest.
515 * No more than @a size bytes are written. If the size of the output buffer
516 * is at least one byte, the output string will always be well-formed, i.e.
517 * null-terminated and containing only complete characters.
518 *
519 * @param dest Destination buffer.
520 * @param count Size of the destination buffer (must be > 0).
521 * @param src Source string.
522 *
523 */
524void str_cpy(char *dest, size_t size, const char *src)
525{
526 /* There must be space for a null terminator in the buffer. */
527 assert(size > 0);
528
529 size_t src_off = 0;
530 size_t dest_off = 0;
531
532 wchar_t ch;
533 while ((ch = str_decode(src, &src_off, STR_NO_LIMIT)) != 0) {
534 if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
535 break;
536 }
537
538 dest[dest_off] = '\0';
539}
540
541/** Copy size-limited substring.
542 *
543 * Copy prefix of string @a src of max. size @a size to destination buffer
544 * @a dest. No more than @a size bytes are written. The output string will
545 * always be well-formed, i.e. null-terminated and containing only complete
546 * characters.
547 *
548 * No more than @a n bytes are read from the input string, so it does not
549 * have to be null-terminated.
550 *
551 * @param dest Destination buffer.
552 * @param count Size of the destination buffer (must be > 0).
553 * @param src Source string.
554 * @param n Maximum number of bytes to read from @a src.
555 *
556 */
557void str_ncpy(char *dest, size_t size, const char *src, size_t n)
558{
559 /* There must be space for a null terminator in the buffer. */
560 assert(size > 0);
561
562 size_t src_off = 0;
563 size_t dest_off = 0;
564
565 wchar_t ch;
566 while ((ch = str_decode(src, &src_off, n)) != 0) {
567 if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
568 break;
569 }
570
571 dest[dest_off] = '\0';
572}
573
574/** Append one string to another.
575 *
576 * Append source string @a src to string in destination buffer @a dest.
577 * Size of the destination buffer is @a dest. If the size of the output buffer
578 * is at least one byte, the output string will always be well-formed, i.e.
579 * null-terminated and containing only complete characters.
580 *
581 * @param dest Destination buffer.
582 * @param count Size of the destination buffer.
583 * @param src Source string.
584 */
585void str_append(char *dest, size_t size, const char *src)
586{
587 size_t dstr_size;
588
589 dstr_size = str_size(dest);
590 if (dstr_size >= size)
591 return;
592
593 str_cpy(dest + dstr_size, size - dstr_size, src);
594}
595
596/** Convert space-padded ASCII to string.
597 *
598 * Common legacy text encoding in hardware is 7-bit ASCII fitted into
599 * a fixed-width byte buffer (bit 7 always zero), right-padded with spaces
600 * (ASCII 0x20). Convert space-padded ascii to string representation.
601 *
602 * If the text does not fit into the destination buffer, the function converts
603 * as many characters as possible and returns EOVERFLOW.
604 *
605 * If the text contains non-ASCII bytes (with bit 7 set), the whole string is
606 * converted anyway and invalid characters are replaced with question marks
607 * (U_SPECIAL) and the function returns EIO.
608 *
609 * Regardless of return value upon return @a dest will always be well-formed.
610 *
611 * @param dest Destination buffer
612 * @param size Size of destination buffer
613 * @param src Space-padded ASCII.
614 * @param n Size of the source buffer in bytes.
615 *
616 * @return EOK on success, EOVERFLOW if the text does not fit
617 * destination buffer, EIO if the text contains
618 * non-ASCII bytes.
619 */
620int spascii_to_str(char *dest, size_t size, const uint8_t *src, size_t n)
621{
622 size_t sidx;
623 size_t didx;
624 size_t dlast;
625 uint8_t byte;
626 int rc;
627 int result;
628
629 /* There must be space for a null terminator in the buffer. */
630 assert(size > 0);
631 result = EOK;
632
633 didx = 0;
634 dlast = 0;
635 for (sidx = 0; sidx < n; ++sidx) {
636 byte = src[sidx];
637 if (!ascii_check(byte)) {
638 byte = U_SPECIAL;
639 result = EIO;
640 }
641
642 rc = chr_encode(byte, dest, &didx, size - 1);
643 if (rc != EOK) {
644 assert(rc == EOVERFLOW);
645 dest[didx] = '\0';
646 return rc;
647 }
648
649 /* Remember dest index after last non-empty character */
650 if (byte != 0x20)
651 dlast = didx;
652 }
653
654 /* Terminate string after last non-empty character */
655 dest[dlast] = '\0';
656 return result;
657}
658
659/** Convert wide string to string.
660 *
661 * Convert wide string @a src to string. The output is written to the buffer
662 * specified by @a dest and @a size. @a size must be non-zero and the string
663 * written will always be well-formed.
664 *
665 * @param dest Destination buffer.
666 * @param size Size of the destination buffer.
667 * @param src Source wide string.
668 */
669void wstr_to_str(char *dest, size_t size, const wchar_t *src)
670{
671 wchar_t ch;
672 size_t src_idx;
673 size_t dest_off;
674
675 /* There must be space for a null terminator in the buffer. */
676 assert(size > 0);
677
678 src_idx = 0;
679 dest_off = 0;
680
681 while ((ch = src[src_idx++]) != 0) {
682 if (chr_encode(ch, dest, &dest_off, size - 1) != EOK)
683 break;
684 }
685
686 dest[dest_off] = '\0';
687}
688
689/** Convert UTF16 string to string.
690 *
691 * Convert utf16 string @a src to string. The output is written to the buffer
692 * specified by @a dest and @a size. @a size must be non-zero and the string
693 * written will always be well-formed. Surrogate pairs also supported.
694 *
695 * @param dest Destination buffer.
696 * @param size Size of the destination buffer.
697 * @param src Source utf16 string.
698 *
699 * @return EOK, if success, negative otherwise.
700 */
701int utf16_to_str(char *dest, size_t size, const uint16_t *src)
702{
703 size_t idx = 0, dest_off = 0;
704 wchar_t ch;
705 int rc = EOK;
706
707 /* There must be space for a null terminator in the buffer. */
708 assert(size > 0);
709
710 while (src[idx]) {
711 if ((src[idx] & 0xfc00) == 0xd800) {
712 if (src[idx + 1] && (src[idx + 1] & 0xfc00) == 0xdc00) {
713 ch = 0x10000;
714 ch += (src[idx] & 0x03FF) << 10;
715 ch += (src[idx + 1] & 0x03FF);
716 idx += 2;
717 }
718 else
719 break;
720 } else {
721 ch = src[idx];
722 idx++;
723 }
724 rc = chr_encode(ch, dest, &dest_off, size - 1);
725 if (rc != EOK)
726 break;
727 }
728 dest[dest_off] = '\0';
729 return rc;
730}
731
732int str_to_utf16(uint16_t *dest, size_t size, const char *src)
733{
734 int rc = EOK;
735 size_t offset = 0;
736 size_t idx = 0;
737 wchar_t c;
738
739 assert(size > 0);
740
741 while ((c = str_decode(src, &offset, STR_NO_LIMIT)) != 0) {
742 if (c > 0x10000) {
743 if (idx + 2 >= size - 1) {
744 rc = EOVERFLOW;
745 break;
746 }
747 c = (c - 0x10000);
748 dest[idx] = 0xD800 | (c >> 10);
749 dest[idx + 1] = 0xDC00 | (c & 0x3FF);
750 idx++;
751 } else {
752 dest[idx] = c;
753 }
754
755 idx++;
756 if (idx >= size - 1) {
757 rc = EOVERFLOW;
758 break;
759 }
760 }
761
762 dest[idx] = '\0';
763 return rc;
764}
765
766
767/** Convert wide string to new string.
768 *
769 * Convert wide string @a src to string. Space for the new string is allocated
770 * on the heap.
771 *
772 * @param src Source wide string.
773 * @return New string.
774 */
775char *wstr_to_astr(const wchar_t *src)
776{
777 char dbuf[STR_BOUNDS(1)];
778 char *str;
779 wchar_t ch;
780
781 size_t src_idx;
782 size_t dest_off;
783 size_t dest_size;
784
785 /* Compute size of encoded string. */
786
787 src_idx = 0;
788 dest_size = 0;
789
790 while ((ch = src[src_idx++]) != 0) {
791 dest_off = 0;
792 if (chr_encode(ch, dbuf, &dest_off, STR_BOUNDS(1)) != EOK)
793 break;
794 dest_size += dest_off;
795 }
796
797 str = malloc(dest_size + 1);
798 if (str == NULL)
799 return NULL;
800
801 /* Encode string. */
802
803 src_idx = 0;
804 dest_off = 0;
805
806 while ((ch = src[src_idx++]) != 0) {
807 if (chr_encode(ch, str, &dest_off, dest_size) != EOK)
808 break;
809 }
810
811 str[dest_size] = '\0';
812 return str;
813}
814
815
816/** Convert string to wide string.
817 *
818 * Convert string @a src to wide string. The output is written to the
819 * buffer specified by @a dest and @a dlen. @a dlen must be non-zero
820 * and the wide string written will always be null-terminated.
821 *
822 * @param dest Destination buffer.
823 * @param dlen Length of destination buffer (number of wchars).
824 * @param src Source string.
825 */
826void str_to_wstr(wchar_t *dest, size_t dlen, const char *src)
827{
828 size_t offset;
829 size_t di;
830 wchar_t c;
831
832 assert(dlen > 0);
833
834 offset = 0;
835 di = 0;
836
837 do {
838 if (di >= dlen - 1)
839 break;
840
841 c = str_decode(src, &offset, STR_NO_LIMIT);
842 dest[di++] = c;
843 } while (c != '\0');
844
845 dest[dlen - 1] = '\0';
846}
847
848/** Convert string to wide string.
849 *
850 * Convert string @a src to wide string. A new wide NULL-terminated
851 * string will be allocated on the heap.
852 *
853 * @param src Source string.
854 */
855wchar_t *str_to_awstr(const char *str)
856{
857 size_t len = str_length(str);
858
859 wchar_t *wstr = calloc(len+1, sizeof(wchar_t));
860 if (wstr == NULL)
861 return NULL;
862
863 str_to_wstr(wstr, len + 1, str);
864 return wstr;
865}
866
867/** Find first occurence of character in string.
868 *
869 * @param str String to search.
870 * @param ch Character to look for.
871 *
872 * @return Pointer to character in @a str or NULL if not found.
873 */
874char *str_chr(const char *str, wchar_t ch)
875{
876 wchar_t acc;
877 size_t off = 0;
878 size_t last = 0;
879
880 while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
881 if (acc == ch)
882 return (char *) (str + last);
883 last = off;
884 }
885
886 return NULL;
887}
888
889/** Removes specified trailing characters from a string.
890 *
891 * @param str String to remove from.
892 * @param ch Character to remove.
893 */
894void str_rtrim(char *str, wchar_t ch)
895{
896 size_t off = 0;
897 size_t pos = 0;
898 wchar_t c;
899 bool update_last_chunk = true;
900 char *last_chunk = NULL;
901
902 while ((c = str_decode(str, &off, STR_NO_LIMIT))) {
903 if (c != ch) {
904 update_last_chunk = true;
905 last_chunk = NULL;
906 } else if (update_last_chunk) {
907 update_last_chunk = false;
908 last_chunk = (str + pos);
909 }
910 pos = off;
911 }
912
913 if (last_chunk)
914 *last_chunk = '\0';
915}
916
917/** Removes specified leading characters from a string.
918 *
919 * @param str String to remove from.
920 * @param ch Character to remove.
921 */
922void str_ltrim(char *str, wchar_t ch)
923{
924 wchar_t acc;
925 size_t off = 0;
926 size_t pos = 0;
927 size_t str_sz = str_size(str);
928
929 while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
930 if (acc != ch)
931 break;
932 else
933 pos = off;
934 }
935
936 if (pos > 0) {
937 memmove(str, &str[pos], str_sz - pos);
938 pos = str_sz - pos;
939 str[str_sz - pos] = '\0';
940 }
941}
942
943/** Find last occurence of character in string.
944 *
945 * @param str String to search.
946 * @param ch Character to look for.
947 *
948 * @return Pointer to character in @a str or NULL if not found.
949 */
950char *str_rchr(const char *str, wchar_t ch)
951{
952 wchar_t acc;
953 size_t off = 0;
954 size_t last = 0;
955 const char *res = NULL;
956
957 while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) {
958 if (acc == ch)
959 res = (str + last);
960 last = off;
961 }
962
963 return (char *) res;
964}
965
966/** Insert a wide character into a wide string.
967 *
968 * Insert a wide character into a wide string at position
969 * @a pos. The characters after the position are shifted.
970 *
971 * @param str String to insert to.
972 * @param ch Character to insert to.
973 * @param pos Character index where to insert.
974 @ @param max_pos Characters in the buffer.
975 *
976 * @return True if the insertion was sucessful, false if the position
977 * is out of bounds.
978 *
979 */
980bool wstr_linsert(wchar_t *str, wchar_t ch, size_t pos, size_t max_pos)
981{
982 size_t len = wstr_length(str);
983
984 if ((pos > len) || (pos + 1 > max_pos))
985 return false;
986
987 size_t i;
988 for (i = len; i + 1 > pos; i--)
989 str[i + 1] = str[i];
990
991 str[pos] = ch;
992
993 return true;
994}
995
996/** Remove a wide character from a wide string.
997 *
998 * Remove a wide character from a wide string at position
999 * @a pos. The characters after the position are shifted.
1000 *
1001 * @param str String to remove from.
1002 * @param pos Character index to remove.
1003 *
1004 * @return True if the removal was sucessful, false if the position
1005 * is out of bounds.
1006 *
1007 */
1008bool wstr_remove(wchar_t *str, size_t pos)
1009{
1010 size_t len = wstr_length(str);
1011
1012 if (pos >= len)
1013 return false;
1014
1015 size_t i;
1016 for (i = pos + 1; i <= len; i++)
1017 str[i - 1] = str[i];
1018
1019 return true;
1020}
1021
1022int stricmp(const char *a, const char *b)
1023{
1024 int c = 0;
1025
1026 while (a[c] && b[c] && (!(tolower(a[c]) - tolower(b[c]))))
1027 c++;
1028
1029 return (tolower(a[c]) - tolower(b[c]));
1030}
1031
1032/** Convert string to a number.
1033 * Core of strtol and strtoul functions.
1034 *
1035 * @param nptr Pointer to string.
1036 * @param endptr If not NULL, function stores here pointer to the first
1037 * invalid character.
1038 * @param base Zero or number between 2 and 36 inclusive.
1039 * @param sgn It's set to 1 if minus found.
1040 * @return Result of conversion.
1041 */
1042static unsigned long
1043_strtoul(const char *nptr, char **endptr, int base, char *sgn)
1044{
1045 unsigned char c;
1046 unsigned long result = 0;
1047 unsigned long a, b;
1048 const char *str = nptr;
1049 const char *tmpptr;
1050
1051 while (isspace(*str))
1052 str++;
1053
1054 if (*str == '-') {
1055 *sgn = 1;
1056 ++str;
1057 } else if (*str == '+')
1058 ++str;
1059
1060 if (base) {
1061 if ((base == 1) || (base > 36)) {
1062 /* FIXME: set errno to EINVAL */
1063 return 0;
1064 }
1065 if ((base == 16) && (*str == '0') && ((str[1] == 'x') ||
1066 (str[1] == 'X'))) {
1067 str += 2;
1068 }
1069 } else {
1070 base = 10;
1071
1072 if (*str == '0') {
1073 base = 8;
1074 if ((str[1] == 'X') || (str[1] == 'x')) {
1075 base = 16;
1076 str += 2;
1077 }
1078 }
1079 }
1080
1081 tmpptr = str;
1082
1083 while (*str) {
1084 c = *str;
1085 c = (c >= 'a' ? c - 'a' + 10 : (c >= 'A' ? c - 'A' + 10 :
1086 (c <= '9' ? c - '0' : 0xff)));
1087 if (c > base) {
1088 break;
1089 }
1090
1091 a = (result & 0xff) * base + c;
1092 b = (result >> 8) * base + (a >> 8);
1093
1094 if (b > (ULONG_MAX >> 8)) {
1095 /* overflow */
1096 /* FIXME: errno = ERANGE*/
1097 return ULONG_MAX;
1098 }
1099
1100 result = (b << 8) + (a & 0xff);
1101 ++str;
1102 }
1103
1104 if (str == tmpptr) {
1105 /*
1106 * No number was found => first invalid character is the first
1107 * character of the string.
1108 */
1109 /* FIXME: set errno to EINVAL */
1110 str = nptr;
1111 result = 0;
1112 }
1113
1114 if (endptr)
1115 *endptr = (char *) str;
1116
1117 if (nptr == str) {
1118 /*FIXME: errno = EINVAL*/
1119 return 0;
1120 }
1121
1122 return result;
1123}
1124
1125/** Convert initial part of string to long int according to given base.
1126 * The number may begin with an arbitrary number of whitespaces followed by
1127 * optional sign (`+' or `-'). If the base is 0 or 16, the prefix `0x' may be
1128 * inserted and the number will be taken as hexadecimal one. If the base is 0
1129 * and the number begin with a zero, number will be taken as octal one (as with
1130 * base 8). Otherwise the base 0 is taken as decimal.
1131 *
1132 * @param nptr Pointer to string.
1133 * @param endptr If not NULL, function stores here pointer to the first
1134 * invalid character.
1135 * @param base Zero or number between 2 and 36 inclusive.
1136 * @return Result of conversion.
1137 */
1138long int strtol(const char *nptr, char **endptr, int base)
1139{
1140 char sgn = 0;
1141 unsigned long number = 0;
1142
1143 number = _strtoul(nptr, endptr, base, &sgn);
1144
1145 if (number > LONG_MAX) {
1146 if ((sgn) && (number == (unsigned long) (LONG_MAX) + 1)) {
1147 /* FIXME: set 0 to errno */
1148 return number;
1149 }
1150 /* FIXME: set ERANGE to errno */
1151 return (sgn ? LONG_MIN : LONG_MAX);
1152 }
1153
1154 return (sgn ? -number : number);
1155}
1156
1157/** Duplicate string.
1158 *
1159 * Allocate a new string and copy characters from the source
1160 * string into it. The duplicate string is allocated via sleeping
1161 * malloc(), thus this function can sleep in no memory conditions.
1162 *
1163 * The allocation cannot fail and the return value is always
1164 * a valid pointer. The duplicate string is always a well-formed
1165 * null-terminated UTF-8 string, but it can differ from the source
1166 * string on the byte level.
1167 *
1168 * @param src Source string.
1169 *
1170 * @return Duplicate string.
1171 *
1172 */
1173char *str_dup(const char *src)
1174{
1175 size_t size = str_size(src) + 1;
1176 char *dest = (char *) malloc(size);
1177 if (dest == NULL)
1178 return (char *) NULL;
1179
1180 str_cpy(dest, size, src);
1181 return dest;
1182}
1183
1184/** Duplicate string with size limit.
1185 *
1186 * Allocate a new string and copy up to @max_size bytes from the source
1187 * string into it. The duplicate string is allocated via sleeping
1188 * malloc(), thus this function can sleep in no memory conditions.
1189 * No more than @max_size + 1 bytes is allocated, but if the size
1190 * occupied by the source string is smaller than @max_size + 1,
1191 * less is allocated.
1192 *
1193 * The allocation cannot fail and the return value is always
1194 * a valid pointer. The duplicate string is always a well-formed
1195 * null-terminated UTF-8 string, but it can differ from the source
1196 * string on the byte level.
1197 *
1198 * @param src Source string.
1199 * @param n Maximum number of bytes to duplicate.
1200 *
1201 * @return Duplicate string.
1202 *
1203 */
1204char *str_ndup(const char *src, size_t n)
1205{
1206 size_t size = str_size(src);
1207 if (size > n)
1208 size = n;
1209
1210 char *dest = (char *) malloc(size + 1);
1211 if (dest == NULL)
1212 return (char *) NULL;
1213
1214 str_ncpy(dest, size + 1, src, size);
1215 return dest;
1216}
1217
1218/** Convert initial part of string to unsigned long according to given base.
1219 * The number may begin with an arbitrary number of whitespaces followed by
1220 * optional sign (`+' or `-'). If the base is 0 or 16, the prefix `0x' may be
1221 * inserted and the number will be taken as hexadecimal one. If the base is 0
1222 * and the number begin with a zero, number will be taken as octal one (as with
1223 * base 8). Otherwise the base 0 is taken as decimal.
1224 *
1225 * @param nptr Pointer to string.
1226 * @param endptr If not NULL, function stores here pointer to the first
1227 * invalid character
1228 * @param base Zero or number between 2 and 36 inclusive.
1229 * @return Result of conversion.
1230 */
1231unsigned long strtoul(const char *nptr, char **endptr, int base)
1232{
1233 char sgn = 0;
1234 unsigned long number = 0;
1235
1236 number = _strtoul(nptr, endptr, base, &sgn);
1237
1238 return (sgn ? -number : number);
1239}
1240
1241char *strtok(char *s, const char *delim)
1242{
1243 static char *next;
1244
1245 return strtok_r(s, delim, &next);
1246}
1247
1248char *strtok_r(char *s, const char *delim, char **next)
1249{
1250 char *start, *end;
1251
1252 if (s == NULL)
1253 s = *next;
1254
1255 /* Skip over leading delimiters. */
1256 while (*s && (str_chr(delim, *s) != NULL)) ++s;
1257 start = s;
1258
1259 /* Skip over token characters. */
1260 while (*s && (str_chr(delim, *s) == NULL)) ++s;
1261 end = s;
1262 *next = (*s ? s + 1 : s);
1263
1264 if (start == end) {
1265 return NULL; /* No more tokens. */
1266 }
1267
1268 /* Overwrite delimiter with NULL terminator. */
1269 *end = '\0';
1270 return start;
1271}
1272
1273/** Convert string to uint64_t (internal variant).
1274 *
1275 * @param nptr Pointer to string.
1276 * @param endptr Pointer to the first invalid character is stored here.
1277 * @param base Zero or number between 2 and 36 inclusive.
1278 * @param neg Indication of unary minus is stored here.
1279 * @apram result Result of the conversion.
1280 *
1281 * @return EOK if conversion was successful.
1282 *
1283 */
1284static int str_uint(const char *nptr, char **endptr, unsigned int base,
1285 bool *neg, uint64_t *result)
1286{
1287 assert(endptr != NULL);
1288 assert(neg != NULL);
1289 assert(result != NULL);
1290
1291 *neg = false;
1292 const char *str = nptr;
1293
1294 /* Ignore leading whitespace */
1295 while (isspace(*str))
1296 str++;
1297
1298 if (*str == '-') {
1299 *neg = true;
1300 str++;
1301 } else if (*str == '+')
1302 str++;
1303
1304 if (base == 0) {
1305 /* Decode base if not specified */
1306 base = 10;
1307
1308 if (*str == '0') {
1309 base = 8;
1310 str++;
1311
1312 switch (*str) {
1313 case 'b':
1314 case 'B':
1315 base = 2;
1316 str++;
1317 break;
1318 case 'o':
1319 case 'O':
1320 base = 8;
1321 str++;
1322 break;
1323 case 'd':
1324 case 'D':
1325 case 't':
1326 case 'T':
1327 base = 10;
1328 str++;
1329 break;
1330 case 'x':
1331 case 'X':
1332 base = 16;
1333 str++;
1334 break;
1335 default:
1336 str--;
1337 }
1338 }
1339 } else {
1340 /* Check base range */
1341 if ((base < 2) || (base > 36)) {
1342 *endptr = (char *) str;
1343 return EINVAL;
1344 }
1345 }
1346
1347 *result = 0;
1348 const char *startstr = str;
1349
1350 while (*str != 0) {
1351 unsigned int digit;
1352
1353 if ((*str >= 'a') && (*str <= 'z'))
1354 digit = *str - 'a' + 10;
1355 else if ((*str >= 'A') && (*str <= 'Z'))
1356 digit = *str - 'A' + 10;
1357 else if ((*str >= '0') && (*str <= '9'))
1358 digit = *str - '0';
1359 else
1360 break;
1361
1362 if (digit >= base)
1363 break;
1364
1365 uint64_t prev = *result;
1366 *result = (*result) * base + digit;
1367
1368 if (*result < prev) {
1369 /* Overflow */
1370 *endptr = (char *) str;
1371 return EOVERFLOW;
1372 }
1373
1374 str++;
1375 }
1376
1377 if (str == startstr) {
1378 /*
1379 * No digits were decoded => first invalid character is
1380 * the first character of the string.
1381 */
1382 str = nptr;
1383 }
1384
1385 *endptr = (char *) str;
1386
1387 if (str == nptr)
1388 return EINVAL;
1389
1390 return EOK;
1391}
1392
1393/** Convert string to uint8_t.
1394 *
1395 * @param nptr Pointer to string.
1396 * @param endptr If not NULL, pointer to the first invalid character
1397 * is stored here.
1398 * @param base Zero or number between 2 and 36 inclusive.
1399 * @param strict Do not allow any trailing characters.
1400 * @param result Result of the conversion.
1401 *
1402 * @return EOK if conversion was successful.
1403 *
1404 */
1405int str_uint8_t(const char *nptr, char **endptr, unsigned int base,
1406 bool strict, uint8_t *result)
1407{
1408 assert(result != NULL);
1409
1410 bool neg;
1411 char *lendptr;
1412 uint64_t res;
1413 int ret = str_uint(nptr, &lendptr, base, &neg, &res);
1414
1415 if (endptr != NULL)
1416 *endptr = (char *) lendptr;
1417
1418 if (ret != EOK)
1419 return ret;
1420
1421 /* Do not allow negative values */
1422 if (neg)
1423 return EINVAL;
1424
1425 /* Check whether we are at the end of
1426 the string in strict mode */
1427 if ((strict) && (*lendptr != 0))
1428 return EINVAL;
1429
1430 /* Check for overflow */
1431 uint8_t _res = (uint8_t) res;
1432 if (_res != res)
1433 return EOVERFLOW;
1434
1435 *result = _res;
1436
1437 return EOK;
1438}
1439
1440/** Convert string to uint16_t.
1441 *
1442 * @param nptr Pointer to string.
1443 * @param endptr If not NULL, pointer to the first invalid character
1444 * is stored here.
1445 * @param base Zero or number between 2 and 36 inclusive.
1446 * @param strict Do not allow any trailing characters.
1447 * @param result Result of the conversion.
1448 *
1449 * @return EOK if conversion was successful.
1450 *
1451 */
1452int str_uint16_t(const char *nptr, char **endptr, unsigned int base,
1453 bool strict, uint16_t *result)
1454{
1455 assert(result != NULL);
1456
1457 bool neg;
1458 char *lendptr;
1459 uint64_t res;
1460 int ret = str_uint(nptr, &lendptr, base, &neg, &res);
1461
1462 if (endptr != NULL)
1463 *endptr = (char *) lendptr;
1464
1465 if (ret != EOK)
1466 return ret;
1467
1468 /* Do not allow negative values */
1469 if (neg)
1470 return EINVAL;
1471
1472 /* Check whether we are at the end of
1473 the string in strict mode */
1474 if ((strict) && (*lendptr != 0))
1475 return EINVAL;
1476
1477 /* Check for overflow */
1478 uint16_t _res = (uint16_t) res;
1479 if (_res != res)
1480 return EOVERFLOW;
1481
1482 *result = _res;
1483
1484 return EOK;
1485}
1486
1487/** Convert string to uint32_t.
1488 *
1489 * @param nptr Pointer to string.
1490 * @param endptr If not NULL, pointer to the first invalid character
1491 * is stored here.
1492 * @param base Zero or number between 2 and 36 inclusive.
1493 * @param strict Do not allow any trailing characters.
1494 * @param result Result of the conversion.
1495 *
1496 * @return EOK if conversion was successful.
1497 *
1498 */
1499int str_uint32_t(const char *nptr, char **endptr, unsigned int base,
1500 bool strict, uint32_t *result)
1501{
1502 assert(result != NULL);
1503
1504 bool neg;
1505 char *lendptr;
1506 uint64_t res;
1507 int ret = str_uint(nptr, &lendptr, base, &neg, &res);
1508
1509 if (endptr != NULL)
1510 *endptr = (char *) lendptr;
1511
1512 if (ret != EOK)
1513 return ret;
1514
1515 /* Do not allow negative values */
1516 if (neg)
1517 return EINVAL;
1518
1519 /* Check whether we are at the end of
1520 the string in strict mode */
1521 if ((strict) && (*lendptr != 0))
1522 return EINVAL;
1523
1524 /* Check for overflow */
1525 uint32_t _res = (uint32_t) res;
1526 if (_res != res)
1527 return EOVERFLOW;
1528
1529 *result = _res;
1530
1531 return EOK;
1532}
1533
1534/** Convert string to uint64_t.
1535 *
1536 * @param nptr Pointer to string.
1537 * @param endptr If not NULL, pointer to the first invalid character
1538 * is stored here.
1539 * @param base Zero or number between 2 and 36 inclusive.
1540 * @param strict Do not allow any trailing characters.
1541 * @param result Result of the conversion.
1542 *
1543 * @return EOK if conversion was successful.
1544 *
1545 */
1546int str_uint64_t(const char *nptr, char **endptr, unsigned int base,
1547 bool strict, uint64_t *result)
1548{
1549 assert(result != NULL);
1550
1551 bool neg;
1552 char *lendptr;
1553 int ret = str_uint(nptr, &lendptr, base, &neg, result);
1554
1555 if (endptr != NULL)
1556 *endptr = (char *) lendptr;
1557
1558 if (ret != EOK)
1559 return ret;
1560
1561 /* Do not allow negative values */
1562 if (neg)
1563 return EINVAL;
1564
1565 /* Check whether we are at the end of
1566 the string in strict mode */
1567 if ((strict) && (*lendptr != 0))
1568 return EINVAL;
1569
1570 return EOK;
1571}
1572
1573/** Convert string to size_t.
1574 *
1575 * @param nptr Pointer to string.
1576 * @param endptr If not NULL, pointer to the first invalid character
1577 * is stored here.
1578 * @param base Zero or number between 2 and 36 inclusive.
1579 * @param strict Do not allow any trailing characters.
1580 * @param result Result of the conversion.
1581 *
1582 * @return EOK if conversion was successful.
1583 *
1584 */
1585int str_size_t(const char *nptr, char **endptr, unsigned int base,
1586 bool strict, size_t *result)
1587{
1588 assert(result != NULL);
1589
1590 bool neg;
1591 char *lendptr;
1592 uint64_t res;
1593 int ret = str_uint(nptr, &lendptr, base, &neg, &res);
1594
1595 if (endptr != NULL)
1596 *endptr = (char *) lendptr;
1597
1598 if (ret != EOK)
1599 return ret;
1600
1601 /* Do not allow negative values */
1602 if (neg)
1603 return EINVAL;
1604
1605 /* Check whether we are at the end of
1606 the string in strict mode */
1607 if ((strict) && (*lendptr != 0))
1608 return EINVAL;
1609
1610 /* Check for overflow */
1611 size_t _res = (size_t) res;
1612 if (_res != res)
1613 return EOVERFLOW;
1614
1615 *result = _res;
1616
1617 return EOK;
1618}
1619
1620void order_suffix(const uint64_t val, uint64_t *rv, char *suffix)
1621{
1622 if (val > UINT64_C(10000000000000000000)) {
1623 *rv = val / UINT64_C(1000000000000000000);
1624 *suffix = 'Z';
1625 } else if (val > UINT64_C(1000000000000000000)) {
1626 *rv = val / UINT64_C(1000000000000000);
1627 *suffix = 'E';
1628 } else if (val > UINT64_C(1000000000000000)) {
1629 *rv = val / UINT64_C(1000000000000);
1630 *suffix = 'T';
1631 } else if (val > UINT64_C(1000000000000)) {
1632 *rv = val / UINT64_C(1000000000);
1633 *suffix = 'G';
1634 } else if (val > UINT64_C(1000000000)) {
1635 *rv = val / UINT64_C(1000000);
1636 *suffix = 'M';
1637 } else if (val > UINT64_C(1000000)) {
1638 *rv = val / UINT64_C(1000);
1639 *suffix = 'k';
1640 } else {
1641 *rv = val;
1642 *suffix = ' ';
1643 }
1644}
1645
1646void bin_order_suffix(const uint64_t val, uint64_t *rv, const char **suffix,
1647 bool fixed)
1648{
1649 if (val > UINT64_C(1152921504606846976)) {
1650 *rv = val / UINT64_C(1125899906842624);
1651 *suffix = "EiB";
1652 } else if (val > UINT64_C(1125899906842624)) {
1653 *rv = val / UINT64_C(1099511627776);
1654 *suffix = "TiB";
1655 } else if (val > UINT64_C(1099511627776)) {
1656 *rv = val / UINT64_C(1073741824);
1657 *suffix = "GiB";
1658 } else if (val > UINT64_C(1073741824)) {
1659 *rv = val / UINT64_C(1048576);
1660 *suffix = "MiB";
1661 } else if (val > UINT64_C(1048576)) {
1662 *rv = val / UINT64_C(1024);
1663 *suffix = "KiB";
1664 } else {
1665 *rv = val;
1666 if (fixed)
1667 *suffix = "B ";
1668 else
1669 *suffix = "B";
1670 }
1671}
1672
1673/** @}
1674 */
Note: See TracBrowser for help on using the repository browser.