1,7 → 1,6 |
/* |
* Copyright (c) 2005 Martin Decky |
* Copyright (C) 1998 by Wes Peters <wes@softweyr.com> |
* Copyright (c) 1988, 1993 The Regents of the University of California. |
* Copyright (c) 2008 Jiri Svoboda |
* All rights reserved. |
* |
* Redistribution and use in source and binary forms, with or without |
35,180 → 34,665 |
*/ |
|
#include <string.h> |
#include <unistd.h> |
#include <stdlib.h> |
#include <assert.h> |
#include <limits.h> |
#include <ctype.h> |
#include <limits.h> |
#include <malloc.h> |
#include <errno.h> |
#include <align.h> |
#include <sys/types.h> |
#include <malloc.h> |
#include <mem.h> |
#include <string.h> |
|
/* Dummy implementation of mem/ functions */ |
/** Byte mask consisting of lowest @n bits (out of 8) */ |
#define LO_MASK_8(n) ((uint8_t) ((1 << (n)) - 1)) |
|
void *memset(void *s, int c, size_t n) |
/** Byte mask consisting of lowest @n bits (out of 32) */ |
#define LO_MASK_32(n) ((uint32_t) ((1 << (n)) - 1)) |
|
/** Byte mask consisting of highest @n bits (out of 8) */ |
#define HI_MASK_8(n) (~LO_MASK_8(8 - (n))) |
|
/** Number of data bits in a UTF-8 continuation byte */ |
#define CONT_BITS 6 |
|
/** Decode a single character from a string. |
* |
* Decode a single character from a string of size @a size. Decoding starts |
* at @a offset and this offset is moved to the beginning of the next |
* character. In case of decoding error, offset generally advances at least |
* by one. However, offset is never moved beyond size. |
* |
* @param str String (not necessarily NULL-terminated). |
* @param offset Byte offset in string where to start decoding. |
* @param size Size of the string (in bytes). |
* |
* @return Value of decoded character, U_SPECIAL on decoding error or |
* NULL if attempt to decode beyond @a size. |
* |
*/ |
wchar_t str_decode(const char *str, size_t *offset, size_t size) |
{ |
char *os = s; |
if (*offset + 1 > size) |
return 0; |
|
while (n--) |
*(os++) = c; |
/* First byte read from string */ |
uint8_t b0 = (uint8_t) str[(*offset)++]; |
|
return s; |
/* Determine code length */ |
|
unsigned int b0_bits; /* Data bits in first byte */ |
unsigned int cbytes; /* Number of continuation bytes */ |
|
if ((b0 & 0x80) == 0) { |
/* 0xxxxxxx (Plain ASCII) */ |
b0_bits = 7; |
cbytes = 0; |
} else if ((b0 & 0xe0) == 0xc0) { |
/* 110xxxxx 10xxxxxx */ |
b0_bits = 5; |
cbytes = 1; |
} else if ((b0 & 0xf0) == 0xe0) { |
/* 1110xxxx 10xxxxxx 10xxxxxx */ |
b0_bits = 4; |
cbytes = 2; |
} else if ((b0 & 0xf8) == 0xf0) { |
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ |
b0_bits = 3; |
cbytes = 3; |
} else { |
/* 10xxxxxx -- unexpected continuation byte */ |
return U_SPECIAL; |
} |
|
struct along { |
unsigned long n; |
} __attribute__ ((packed)); |
if (*offset + cbytes > size) |
return U_SPECIAL; |
|
static void *unaligned_memcpy(void *dst, const void *src, size_t n) |
wchar_t ch = b0 & LO_MASK_8(b0_bits); |
|
/* Decode continuation bytes */ |
while (cbytes > 0) { |
uint8_t b = (uint8_t) str[(*offset)++]; |
|
/* Must be 10xxxxxx */ |
if ((b & 0xc0) != 0x80) |
return U_SPECIAL; |
|
/* Shift data bits to ch */ |
ch = (ch << CONT_BITS) | (wchar_t) (b & LO_MASK_8(CONT_BITS)); |
cbytes--; |
} |
|
return ch; |
} |
|
/** Encode a single character to string representation. |
* |
* Encode a single character to string representation (i.e. UTF-8) and store |
* it into a buffer at @a offset. Encoding starts at @a offset and this offset |
* is moved to the position where the next character can be written to. |
* |
* @param ch Input character. |
* @param str Output buffer. |
* @param offset Byte offset where to start writing. |
* @param size Size of the output buffer (in bytes). |
* |
* @return EOK if the character was encoded successfully, EOVERFLOW if there |
* was not enough space in the output buffer or EINVAL if the character |
* code was invalid. |
*/ |
int chr_encode(const wchar_t ch, char *str, size_t *offset, size_t size) |
{ |
int i, j; |
struct along *adst = dst; |
const struct along *asrc = src; |
if (*offset >= size) |
return EOVERFLOW; |
|
for (i = 0; i < n / sizeof(unsigned long); i++) |
adst[i].n = asrc[i].n; |
if (!chr_check(ch)) |
return EINVAL; |
|
for (j = 0; j < n % sizeof(unsigned long); j++) |
((unsigned char *) (((unsigned long *) dst) + i))[j] = |
((unsigned char *) (((unsigned long *) src) + i))[j]; |
/* Unsigned version of ch (bit operations should only be done |
on unsigned types). */ |
uint32_t cc = (uint32_t) ch; |
|
return (char *) dst; |
/* Determine how many continuation bytes are needed */ |
|
unsigned int b0_bits; /* Data bits in first byte */ |
unsigned int cbytes; /* Number of continuation bytes */ |
|
if ((cc & ~LO_MASK_32(7)) == 0) { |
b0_bits = 7; |
cbytes = 0; |
} else if ((cc & ~LO_MASK_32(11)) == 0) { |
b0_bits = 5; |
cbytes = 1; |
} else if ((cc & ~LO_MASK_32(16)) == 0) { |
b0_bits = 4; |
cbytes = 2; |
} else if ((cc & ~LO_MASK_32(21)) == 0) { |
b0_bits = 3; |
cbytes = 3; |
} else { |
/* Codes longer than 21 bits are not supported */ |
return EINVAL; |
} |
|
void *memcpy(void *dst, const void *src, size_t n) |
/* Check for available space in buffer */ |
if (*offset + cbytes >= size) |
return EOVERFLOW; |
|
/* Encode continuation bytes */ |
unsigned int i; |
for (i = cbytes; i > 0; i--) { |
str[*offset + i] = 0x80 | (cc & LO_MASK_32(CONT_BITS)); |
cc = cc >> CONT_BITS; |
} |
|
/* Encode first byte */ |
str[*offset] = (cc & LO_MASK_32(b0_bits)) | HI_MASK_8(8 - b0_bits - 1); |
|
/* Advance offset */ |
*offset += cbytes + 1; |
|
return EOK; |
} |
|
/** Get size of string. |
* |
* Get the number of bytes which are used by the string @a str (excluding the |
* NULL-terminator). |
* |
* @param str String to consider. |
* |
* @return Number of bytes used by the string |
* |
*/ |
size_t str_size(const char *str) |
{ |
int i, j; |
size_t size = 0; |
|
if (((long) dst & (sizeof(long) - 1)) || |
((long) src & (sizeof(long) - 1))) |
return unaligned_memcpy(dst, src, n); |
while (*str++ != 0) |
size++; |
|
for (i = 0; i < n / sizeof(unsigned long); i++) |
((unsigned long *) dst)[i] = ((unsigned long *) src)[i]; |
return size; |
} |
|
for (j = 0; j < n % sizeof(unsigned long); j++) |
((unsigned char *) (((unsigned long *) dst) + i))[j] = |
((unsigned char *) (((unsigned long *) src) + i))[j]; |
/** Get size of wide string. |
* |
* Get the number of bytes which are used by the wide string @a str (excluding the |
* NULL-terminator). |
* |
* @param str Wide string to consider. |
* |
* @return Number of bytes used by the wide string |
* |
*/ |
size_t wstr_size(const wchar_t *str) |
{ |
return (wstr_length(str) * sizeof(wchar_t)); |
} |
|
return (char *) dst; |
/** Get size of string with length limit. |
* |
* Get the number of bytes which are used by up to @a max_len first |
* characters in the string @a str. If @a max_len is greater than |
* the length of @a str, the entire string is measured (excluding the |
* NULL-terminator). |
* |
* @param str String to consider. |
* @param max_len Maximum number of characters to measure. |
* |
* @return Number of bytes used by the characters. |
* |
*/ |
size_t str_lsize(const char *str, count_t max_len) |
{ |
count_t len = 0; |
size_t offset = 0; |
|
while (len < max_len) { |
if (str_decode(str, &offset, STR_NO_LIMIT) == 0) |
break; |
|
len++; |
} |
|
void *memmove(void *dst, const void *src, size_t n) |
return offset; |
} |
|
/** Get size of wide string with length limit. |
* |
* Get the number of bytes which are used by up to @a max_len first |
* wide characters in the wide string @a str. If @a max_len is greater than |
* the length of @a str, the entire wide string is measured (excluding the |
* NULL-terminator). |
* |
* @param str Wide string to consider. |
* @param max_len Maximum number of wide characters to measure. |
* |
* @return Number of bytes used by the wide characters. |
* |
*/ |
size_t wstr_lsize(const wchar_t *str, count_t max_len) |
{ |
int i, j; |
return (wstr_nlength(str, max_len * sizeof(wchar_t)) * sizeof(wchar_t)); |
} |
|
if (src > dst) |
return memcpy(dst, src, n); |
/** Get number of characters in a string. |
* |
* @param str NULL-terminated string. |
* |
* @return Number of characters in string. |
* |
*/ |
count_t str_length(const char *str) |
{ |
count_t len = 0; |
size_t offset = 0; |
|
for (j = (n % sizeof(unsigned long)) - 1; j >= 0; j--) |
((unsigned char *) ((unsigned long *) dst))[j] = |
((unsigned char *) ((unsigned long *) src))[j]; |
while (str_decode(str, &offset, STR_NO_LIMIT) != 0) |
len++; |
|
for (i = n / sizeof(unsigned long) - 1; i >=0 ; i--) |
((unsigned long *) dst)[i] = ((unsigned long *) src)[i]; |
return len; |
} |
|
return (char *) dst; |
/** Get number of characters in a wide string. |
* |
* @param str NULL-terminated wide string. |
* |
* @return Number of characters in @a str. |
* |
*/ |
count_t wstr_length(const wchar_t *wstr) |
{ |
count_t len = 0; |
|
while (*wstr++ != 0) |
len++; |
|
return len; |
} |
|
/** Compare two memory areas. |
/** Get number of characters in a string with size limit. |
* |
* @param s1 Pointer to the first area to compare. |
* @param s2 Pointer to the second area to compare. |
* @param len Size of the first area in bytes. Both areas must have |
* the same length. |
* @return If len is 0, return zero. If the areas match, return |
* zero. Otherwise return non-zero. |
* @param str NULL-terminated string. |
* @param size Maximum number of bytes to consider. |
* |
* @return Number of characters in string. |
* |
*/ |
int bcmp(const char *s1, const char *s2, size_t len) |
count_t str_nlength(const char *str, size_t size) |
{ |
for (; len && *s1++ == *s2++; len--) |
; |
count_t len = 0; |
size_t offset = 0; |
|
while (str_decode(str, &offset, size) != 0) |
len++; |
|
return len; |
} |
|
/** Count the number of characters in the string, not including terminating 0. |
/** Get number of characters in a string with size limit. |
* |
* @param str String. |
* @param str NULL-terminated string. |
* @param size Maximum number of bytes to consider. |
* |
* @return Number of characters in string. |
* |
*/ |
size_t strlen(const char *str) |
count_t wstr_nlength(const wchar_t *str, size_t size) |
{ |
size_t counter = 0; |
count_t len = 0; |
count_t limit = ALIGN_DOWN(size, sizeof(wchar_t)); |
count_t offset = 0; |
|
while (str[counter] != 0) |
counter++; |
while ((offset < limit) && (*str++ != 0)) { |
len++; |
offset += sizeof(wchar_t); |
} |
|
return counter; |
return len; |
} |
|
int strcmp(const char *a, const char *b) |
/** Check whether character is plain ASCII. |
* |
* @return True if character is plain ASCII. |
* |
*/ |
bool ascii_check(wchar_t ch) |
{ |
int c = 0; |
if ((ch >= 0) && (ch <= 127)) |
return true; |
|
while (a[c] && b[c] && (!(a[c] - b[c]))) |
c++; |
return false; |
} |
|
return (a[c] - b[c]); |
/** Check whether character is valid |
* |
* @return True if character is a valid Unicode code point. |
* |
*/ |
bool chr_check(wchar_t ch) |
{ |
if ((ch >= 0) && (ch <= 1114111)) |
return true; |
|
return false; |
} |
|
int strncmp(const char *a, const char *b, size_t n) |
/** Compare two NULL terminated strings. |
* |
* Do a char-by-char comparison of two NULL-terminated strings. |
* The strings are considered equal iff they consist of the same |
* characters on the minimum of their lengths. |
* |
* @param s1 First string to compare. |
* @param s2 Second string to compare. |
* |
* @return 0 if the strings are equal, -1 if first is smaller, |
* 1 if second smaller. |
* |
*/ |
int str_cmp(const char *s1, const char *s2) |
{ |
size_t c = 0; |
wchar_t c1 = 0; |
wchar_t c2 = 0; |
|
while (c < n && a[c] && b[c] && (!(a[c] - b[c]))) |
c++; |
size_t off1 = 0; |
size_t off2 = 0; |
|
return ( c < n ? a[c] - b[c] : 0); |
while (true) { |
c1 = str_decode(s1, &off1, STR_NO_LIMIT); |
c2 = str_decode(s2, &off2, STR_NO_LIMIT); |
|
if (c1 < c2) |
return -1; |
|
if (c1 > c2) |
return 1; |
|
if (c1 == 0 || c2 == 0) |
break; |
} |
|
int stricmp(const char *a, const char *b) |
return 0; |
} |
|
/** Compare two NULL terminated strings with length limit. |
* |
* Do a char-by-char comparison of two NULL-terminated strings. |
* The strings are considered equal iff they consist of the same |
* characters on the minimum of their lengths and the length limit. |
* |
* @param s1 First string to compare. |
* @param s2 Second string to compare. |
* @param max_len Maximum number of characters to consider. |
* |
* @return 0 if the strings are equal, -1 if first is smaller, |
* 1 if second smaller. |
* |
*/ |
int str_lcmp(const char *s1, const char *s2, count_t max_len) |
{ |
int c = 0; |
wchar_t c1 = 0; |
wchar_t c2 = 0; |
|
while (a[c] && b[c] && (!(tolower(a[c]) - tolower(b[c])))) |
c++; |
size_t off1 = 0; |
size_t off2 = 0; |
|
return (tolower(a[c]) - tolower(b[c])); |
count_t len = 0; |
|
while (true) { |
if (len >= max_len) |
break; |
|
c1 = str_decode(s1, &off1, STR_NO_LIMIT); |
c2 = str_decode(s2, &off2, STR_NO_LIMIT); |
|
if (c1 < c2) |
return -1; |
|
if (c1 > c2) |
return 1; |
|
if (c1 == 0 || c2 == 0) |
break; |
|
++len; |
} |
|
/** Return pointer to the first occurence of character c in string. |
return 0; |
|
} |
|
/** Copy string. |
* |
* @param str Scanned string. |
* @param c Searched character (taken as one byte). |
* @return Pointer to the matched character or NULL if it is not |
* found in given string. |
* Copy source string @a src to destination buffer @a dest. |
* No more than @a size bytes are written. If the size of the output buffer |
* is at least one byte, the output string will always be well-formed, i.e. |
* null-terminated and containing only complete characters. |
* |
* @param dst Destination buffer. |
* @param count Size of the destination buffer (must be > 0). |
* @param src Source string. |
*/ |
char *strchr(const char *str, int c) |
void str_cpy(char *dest, size_t size, const char *src) |
{ |
while (*str != '\0') { |
if (*str == (char) c) |
return (char *) str; |
str++; |
wchar_t ch; |
size_t src_off; |
size_t dest_off; |
|
/* There must be space for a null terminator in the buffer. */ |
assert(size > 0); |
|
src_off = 0; |
dest_off = 0; |
|
while ((ch = str_decode(src, &src_off, STR_NO_LIMIT)) != 0) { |
if (chr_encode(ch, dest, &dest_off, size - 1) != EOK) |
break; |
} |
|
dest[dest_off] = '\0'; |
} |
|
/** Copy size-limited substring. |
* |
* Copy prefix of string @a src of max. size @a size to destination buffer |
* @a dest. No more than @a size bytes are written. The output string will |
* always be well-formed, i.e. null-terminated and containing only complete |
* characters. |
* |
* No more than @a n bytes are read from the input string, so it does not |
* have to be null-terminated. |
* |
* @param dst Destination buffer. |
* @param count Size of the destination buffer (must be > 0). |
* @param src Source string. |
* @param n Maximum number of bytes to read from @a src. |
*/ |
void str_ncpy(char *dest, size_t size, const char *src, size_t n) |
{ |
wchar_t ch; |
size_t src_off; |
size_t dest_off; |
|
/* There must be space for a null terminator in the buffer. */ |
assert(size > 0); |
|
src_off = 0; |
dest_off = 0; |
|
while ((ch = str_decode(src, &src_off, n)) != 0) { |
if (chr_encode(ch, dest, &dest_off, size - 1) != EOK) |
break; |
} |
|
dest[dest_off] = '\0'; |
} |
|
/** Append one string to another. |
* |
* Append source string @a src to string in destination buffer @a dest. |
* Size of the destination buffer is @a dest. If the size of the output buffer |
* is at least one byte, the output string will always be well-formed, i.e. |
* null-terminated and containing only complete characters. |
* |
* @param dst Destination buffer. |
* @param count Size of the destination buffer. |
* @param src Source string. |
*/ |
void str_append(char *dest, size_t size, const char *src) |
{ |
size_t dstr_size; |
|
dstr_size = str_size(dest); |
str_cpy(dest + dstr_size, size - dstr_size, src); |
} |
|
/** Copy NULL-terminated wide string to string |
* |
* Copy source wide string @a src to destination buffer @a dst. |
* No more than @a size bytes are written. NULL-terminator is always |
* written after the last succesfully copied character (i.e. if the |
* destination buffer is has at least 1 byte, it will be always |
* NULL-terminated). |
* |
* @param src Source wide string. |
* @param dst Destination buffer. |
* @param count Size of the destination buffer. |
* |
*/ |
void wstr_nstr(char *dst, const wchar_t *src, size_t size) |
{ |
/* No space for the NULL-terminator in the buffer */ |
if (size == 0) |
return; |
|
wchar_t ch; |
count_t src_idx = 0; |
size_t dst_off = 0; |
|
while ((ch = src[src_idx++]) != 0) { |
if (chr_encode(ch, dst, &dst_off, size) != EOK) |
break; |
} |
|
if (dst_off >= size) |
dst[size - 1] = 0; |
else |
dst[dst_off] = 0; |
} |
|
/** Find first occurence of character in string. |
* |
* @param str String to search. |
* @param ch Character to look for. |
* |
* @return Pointer to character in @a str or NULL if not found. |
*/ |
const char *str_chr(const char *str, wchar_t ch) |
{ |
wchar_t acc; |
size_t off = 0; |
size_t last = 0; |
|
while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) { |
if (acc == ch) |
return (str + last); |
last = off; |
} |
|
return NULL; |
} |
|
/** Return pointer to the last occurence of character c in string. |
/** Find last occurence of character in string. |
* |
* @param str Scanned string. |
* @param c Searched character (taken as one byte). |
* @return Pointer to the matched character or NULL if it is not |
* found in given string. |
* @param str String to search. |
* @param ch Character to look for. |
* |
* @return Pointer to character in @a str or NULL if not found. |
*/ |
char *strrchr(const char *str, int c) |
const char *str_rchr(const char *str, wchar_t ch) |
{ |
char *retval = NULL; |
wchar_t acc; |
size_t off = 0; |
size_t last = 0; |
char *res = NULL; |
|
while (*str != '\0') { |
if (*str == (char) c) |
retval = (char *) str; |
str++; |
while ((acc = str_decode(str, &off, STR_NO_LIMIT)) != 0) { |
if (acc == ch) |
res = (str + last); |
last = off; |
} |
|
return (char *) retval; |
return res; |
} |
|
/** Insert a wide character into a wide string. |
* |
* Insert a wide character into a wide string at position |
* @a pos. The characters after the position are shifted. |
* |
* @param str String to insert to. |
* @param ch Character to insert to. |
* @param pos Character index where to insert. |
@ @param max_pos Characters in the buffer. |
* |
* @return True if the insertion was sucessful, false if the position |
* is out of bounds. |
* |
*/ |
bool wstr_linsert(wchar_t *str, wchar_t ch, count_t pos, count_t max_pos) |
{ |
count_t len = wstr_length(str); |
|
if ((pos > len) || (pos + 1 > max_pos)) |
return false; |
|
count_t i; |
for (i = len; i + 1 > pos; i--) |
str[i + 1] = str[i]; |
|
str[pos] = ch; |
|
return true; |
} |
|
/** Remove a wide character from a wide string. |
* |
* Remove a wide character from a wide string at position |
* @a pos. The characters after the position are shifted. |
* |
* @param str String to remove from. |
* @param pos Character index to remove. |
* |
* @return True if the removal was sucessful, false if the position |
* is out of bounds. |
* |
*/ |
bool wstr_remove(wchar_t *str, count_t pos) |
{ |
count_t len = wstr_length(str); |
|
if (pos >= len) |
return false; |
|
count_t i; |
for (i = pos + 1; i <= len; i++) |
str[i - 1] = str[i]; |
|
return true; |
} |
|
int stricmp(const char *a, const char *b) |
{ |
int c = 0; |
|
while (a[c] && b[c] && (!(tolower(a[c]) - tolower(b[c])))) |
c++; |
|
return (tolower(a[c]) - tolower(b[c])); |
} |
|
/** Convert string to a number. |
* Core of strtol and strtoul functions. |
* |
358,91 → 842,47 |
return (sgn ? -number : number); |
} |
|
char *strcpy(char *dest, const char *src) |
char *str_dup(const char *src) |
{ |
char *orig = dest; |
size_t size = str_size(src); |
void *dest = malloc(size + 1); |
|
while ((*(dest++) = *(src++))) |
; |
return orig; |
} |
if (dest == NULL) |
return (char *) NULL; |
|
char *strncpy(char *dest, const char *src, size_t n) |
{ |
char *orig = dest; |
|
while ((*(dest++) = *(src++)) && --n) |
; |
return orig; |
return (char *) memcpy(dest, src, size + 1); |
} |
|
char *strcat(char *dest, const char *src) |
char *strtok(char *s, const char *delim) |
{ |
char *orig = dest; |
while (*dest++) |
; |
--dest; |
while ((*dest++ = *src++)) |
; |
return orig; |
} |
static char *next; |
|
char * strdup(const char *s1) |
{ |
size_t len = strlen(s1) + 1; |
void *ret = malloc(len); |
|
if (ret == NULL) |
return (char *) NULL; |
|
return (char *) memcpy(ret, s1, len); |
return strtok_r(s, delim, &next); |
} |
|
/* Ported from FBSD strtok.c 8.1 (Berkeley) 6/4/93 */ |
char * strtok_r(char *s, const char *delim, char **last) |
char *strtok_r(char *s, const char *delim, char **next) |
{ |
char *spanp, *tok; |
int c, sc; |
char *start, *end; |
|
if (s == NULL && (s = *last) == NULL) |
return (NULL); |
if (s == NULL) |
s = *next; |
|
cont: |
c = *s++; |
for (spanp = (char *)delim; (sc = *spanp++) != 0;) { |
if (c == sc) |
goto cont; |
} |
/* Skip over leading delimiters. */ |
while (*s && (str_chr(delim, *s) != NULL)) ++s; |
start = s; |
|
if (c == 0) { /* no non-delimiter characters */ |
*last = NULL; |
return (NULL); |
} |
/* Skip over token characters. */ |
while (*s && (str_chr(delim, *s) == NULL)) ++s; |
end = s; |
*next = (*s ? s + 1 : s); |
|
tok = s - 1; |
|
for (;;) { |
c = *s++; |
spanp = (char *)delim; |
do { |
if ((sc = *spanp++) == c) { |
if (c == 0) |
s = NULL; |
else |
s[-1] = '\0'; |
*last = s; |
return (tok); |
if (start == end) { |
return NULL; /* No more tokens. */ |
} |
} while (sc != 0); |
} |
} |
|
/* Ported from FBSD strtok.c 8.1 (Berkeley) 6/4/93 */ |
char * strtok(char *s, const char *delim) |
{ |
static char *last; |
|
return (strtok_r(s, delim, &last)); |
/* Overwrite delimiter with NULL terminator. */ |
*end = '\0'; |
return start; |
} |
|
/** @} |