blob: 4c49bc9c24ecb4dfc5671c0d99a34bc1eddc43b1 [file] [log] [blame]
/*****************************************************************************\
* xstring.c - heap-oriented string manipulation functions with "safe"
* string expansion as needed.
******************************************************************************
* Copyright (C) 2002 The Regents of the University of California.
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
* Written by Jim Garlick <garlick@llnl.gov>
* Mark Grondona <grondona@llnl.gov>, et al.
*
* CODE-OCEC-09-009. All rights reserved.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include "config.h"
#include <ctype.h>
#include <errno.h>
#include <libgen.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include "slurm/slurm_errno.h"
#include "src/common/macros.h"
#include "src/common/slurm_time.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#define XFGETS_CHUNKSIZE 64
/*
* Define slurm-specific aliases for use by plugins, see slurm_xlator.h
* for details.
*/
strong_alias(_xstrcat, slurm_xstrcat);
strong_alias(_xstrncat, slurm_xstrncat);
strong_alias(_xstrncatat, slurm_xstrncatat);
strong_alias(_xstrcatchar, slurm_xstrcatchar);
strong_alias(_xstrftimecat, slurm_xstrftimecat);
strong_alias(_xiso8601timecat, slurm_xiso8601timecat);
strong_alias(_xrfc5424timecat, slurm_xrfc5424timecat);
strong_alias(_xstrfmtcat, slurm_xstrfmtcat);
strong_alias(_xstrfmtcatat, slurm_xstrfmtcatat);
strong_alias(_xmemcat, slurm_xmemcat);
strong_alias(xstrdup, slurm_xstrdup);
strong_alias(xstrdup_printf, slurm_xstrdup_printf);
strong_alias(_xstrdup_vprintf, slurm_xstrdup_vprintf);
strong_alias(xstrndup, slurm_xstrndup);
strong_alias(xbasename, slurm_xbasename);
strong_alias(xdirname, slurm_xdirname);
strong_alias(_xstrsubstitute, slurm_xstrsubstitute);
strong_alias(xshort_hostname, slurm_xshort_hostname);
strong_alias(xstring_is_whitespace, slurm_xstring_is_whitespace);
strong_alias(xstrtolower, slurm_xstrtolower);
strong_alias(xstrchr, slurm_xstrchr);
strong_alias(xstrrchr, slurm_xstrrchr);
strong_alias(xstrcmp, slurm_xstrcmp);
strong_alias(xstrncmp, slurm_xstrncmp);
strong_alias(xstrcasecmp, slurm_xstrcasecmp);
strong_alias(xstrncasecmp, slurm_xstrncasecmp);
strong_alias(xstrstr, slurm_xstrstr);
strong_alias(xstrcasestr, slurm_xstrcasestr);
strong_alias(xbase64_from_base64url, slurm_xbase64_from_base64url);
/*
* Ensure that a string has enough space to add 'needed' characters.
* If the string is uninitialized, it should be NULL.
* str (IN/OUT) str
* str_len(IN) current string length, if known. -1 otherwise
* needed (IN) additional space needed
*/
static void _makespace(char **str, int str_len, int needed)
{
if (*str == NULL)
*str = xmalloc(needed + 1);
else {
int actual_size;
int used = (str_len < 0) ? strlen(*str) + 1 : str_len + 1;
int min_new_size = used + needed;
int cur_size = xsize(*str);
if (min_new_size > cur_size) {
int new_size = min_new_size;
if (new_size < (cur_size + XFGETS_CHUNKSIZE))
new_size = cur_size + XFGETS_CHUNKSIZE;
if (new_size < (cur_size * 2))
new_size = cur_size * 2;
xrealloc(*str, new_size);
actual_size = xsize(*str);
if (actual_size)
xassert(actual_size == new_size);
}
}
}
/*
* Concatenate str2 onto str1, expanding str1 as needed.
* str1 (IN/OUT) target string (pointer to in case of expansion)
* str2 (IN) source string
*/
void _xstrcat(char **str1, const char *str2)
{
if (str2 == NULL)
str2 = "(null)";
_makespace(str1, -1, strlen(str2));
strcat(*str1, str2);
}
/*
* Append str2 onto str at pos, * expanding buf as needed. pos is updated to the
* end of the appended string.
*
* Meant to be used in loops constructing longer strings that are performance
* sensitive, as xstrcat() needs to re-seek to the end of str making the string
* construction worse by another O(log(strlen)) factor.
*/
void _xstrncatat(char **str, char **pos, const char *str2, ssize_t append_len)
{
size_t orig_len;
if (!str2)
return;
if (append_len < 0)
append_len = strlen(str2);
/* No string yet to append to, so return a copy of str2. */
if (!*str) {
*str = xstrndup(str2, append_len);
*pos = *str + append_len;
return;
}
if (!*pos) {
orig_len = strlen(*str);
} else {
xassert(*pos >= *str);
orig_len = *pos - *str;
}
_makespace(str, orig_len, append_len);
memcpy(*str + orig_len, str2, append_len);
/*
* Update *pos. Cannot happen earlier as _makespace() may have
* changed *str to a different address.
*/
*pos = *str + orig_len + append_len;
}
/*
* Concatenate len of str2 onto str1, expanding str1 as needed.
* str1 (IN/OUT) target string (pointer to in case of expansion)
* str2 (IN) source string
* len (IN) len of str2 to concat
*/
void _xstrncat(char **str1, const char *str2, size_t len)
{
if (str2 == NULL)
str2 = "(null)";
_makespace(str1, -1, len);
strncat(*str1, str2, len);
}
/*
* Add a character to str, expanding str1 as needed.
* str1 (IN/OUT) target string (pointer to in case of expansion)
* c (IN) character to add
*/
void _xstrcatchar(char **str, char c)
{
int len = (*str) ? strlen(*str) : 0;
_makespace(str, len, 1);
(*str)[len++] = c;
(*str)[len] = '\0';
}
/*
* append strftime of fmt to buffer buf, expand buf as needed
*
*/
void _xstrftimecat(char **buf, const char *fmt)
{
char p[256]; /* output truncated to 256 chars */
time_t t;
struct tm tm;
const char default_fmt[] = "%m/%d/%Y %H:%M:%S %Z";
if (fmt == NULL)
fmt = default_fmt;
if (time(&t) == (time_t) -1)
fprintf(stderr, "time() failed\n");
if (!localtime_r(&t, &tm))
fprintf(stderr, "localtime_r() failed\n");
strftime(p, sizeof(p), fmt, &tm);
_xstrcat(buf, p);
}
/*
* Append a ISO 8601 formatted timestamp to buffer buf, expand as needed
*/
void _xiso8601timecat(char **buf, bool msec)
{
char p[64] = "";
struct timeval tv;
struct tm tm;
if (gettimeofday(&tv, NULL) == -1)
fprintf(stderr, "gettimeofday() failed\n");
if (!localtime_r(&tv.tv_sec, &tm))
fprintf(stderr, "localtime_r() failed\n");
if (strftime(p, sizeof(p), "%Y-%m-%dT%T", &tm) == 0)
fprintf(stderr, "strftime() returned 0\n");
if (msec)
_xstrfmtcat(buf, "%s.%3.3d", p, (int)(tv.tv_usec / 1000));
else
_xstrfmtcat(buf, "%s", p);
}
/*
* Append a RFC 5424 formatted timestamp to buffer buf, expand as needed
*
*/
void _xrfc5424timecat(char **buf, bool msec)
{
char p[64] = "";
char z[12] = "";
struct timeval tv;
struct tm tm;
if (gettimeofday(&tv, NULL) == -1)
fprintf(stderr, "gettimeofday() failed\n");
if (!localtime_r(&tv.tv_sec, &tm))
fprintf(stderr, "localtime_r() failed\n");
if (strftime(p, sizeof(p), "%Y-%m-%dT%T", &tm) == 0)
fprintf(stderr, "strftime() returned 0\n");
/* The strftime %z format creates timezone offsets of the form
* (+/-)hhmm, whereas the RFC 5424 format is (+/-)hh:mm. So
* shift the minutes one step back and insert the semicolon.
*/
if (strftime(z, sizeof(z), "%z", &tm) == 0)
fprintf(stderr, "strftime() returned 0\n");
z[5] = z[4];
z[4] = z[3];
z[3] = ':';
if (msec)
_xstrfmtcat(buf, "%s.%3.3d%s", p, (int)(tv.tv_usec / 1000), z);
else
_xstrfmtcat(buf, "%s%s", p, z);
}
extern void _xrfc3339timecat(char **buf)
{
char p[64] = "";
char z[12] = "";
struct timeval tv;
struct tm tm;
if (gettimeofday(&tv, NULL) == -1)
fprintf(stderr, "gettimeofday() failed\n");
if (!localtime_r(&tv.tv_sec, &tm))
fprintf(stderr, "localtime_r() failed\n");
if (strftime(p, sizeof(p), "%FT%T", &tm) == 0)
fprintf(stderr, "strftime() returned 0\n");
/* The strftime %z format creates timezone offsets of the form
* (+/-)hhmm, whereas the RFC 3339 format is (+/-)hh:mm. So
* shift the minutes one step back and insert the semicolon.
*/
if (strftime(z, sizeof(z), "%z", &tm) == 0)
fprintf(stderr, "strftime() returned 0\n");
z[5] = z[4];
z[4] = z[3];
z[3] = ':';
_xstrfmtcat(buf, "%s%s", p, z);
}
/*
* append formatted string with printf-style args to buf, expanding
* buf as needed
*/
void _xstrfmtcat(char **str, const char *fmt, ...)
{
char *p = NULL;
va_list ap;
va_start(ap, fmt);
_xstrdup_vprintf(&p, fmt, ap);
va_end(ap);
if (!p)
return;
/* If str does not exist yet, just give it back p directly */
if (!*str) {
*str = p;
return;
}
xstrcat(*str, p);
xfree(p);
}
/*
* Append formatted string with printf-style args to str at pos,
* expanding buf as needed. pos is updated to the end of the appended
* string.
*
* Meant to be used in loops constructing longer strings that are performance
* sensitive, as xstrfmtcat() needs to re-seek to the end of str making the
* string construction worse by another O(log(strlen)) factor.
*/
void _xstrfmtcatat(char **str, char **pos, const char *fmt, ...)
{
size_t orig_len, append_len;
char *p = NULL;
va_list ap;
va_start(ap, fmt);
append_len = _xstrdup_vprintf(&p, fmt, ap);
va_end(ap);
if (!p)
return;
/* No string yet to append to, so just return p. */
if (!*str) {
*str = p;
*pos = p + append_len;
return;
}
if (!*pos) {
orig_len = strlen(*str);
} else {
xassert(*pos >= *str);
orig_len = *pos - *str;
}
_makespace(str, orig_len, append_len);
memcpy(*str + orig_len, p, append_len);
xfree(p);
/*
* Update *pos. Cannot happen earlier as _makespace() may have
* changed *str to a different address.
*/
*pos = *str + orig_len + append_len;
}
/*
* append a range of memory from start to end to the string str,
* expanding str as needed
*/
void _xmemcat(char **str, char *start, char *end)
{
char buf[4096];
size_t len;
xassert(end >= start);
len = (size_t) end - (size_t) start;
if (len == 0)
return;
if (len > 4095)
len = 4095;
memcpy(buf, start, len);
buf[len] = '\0';
xstrcat(*str, buf);
}
/*
* Replacement for libc basename
* path (IN) path possibly containing '/' characters
* RETURN last component of path
*/
char * xbasename(char *path)
{
char *p;
p = strrchr(path , '/');
return (p ? (p + 1) : path);
}
/*
* Slurm's safe implementation of dirname.
*
* This function returns a malloc'ed string which lets the caller to safely
* manipulate it, unlike in dirname() which in some cases returns pointers
* to the passed string, and in other cases a new malloc'ed string. dirname()
* can also modify the original string so it is preferred to pas a copy of the
* input string.
*
* Besides that, the result is exactly like in dirname().
*
* IN path - path to get the dirname from.
* RETURN res - string containing the path without the basename.
*/
char *xdirname(const char *path)
{
char *res = NULL;
char *fname = xstrdup(path);
res = xstrdup(dirname(fname));
xfree(fname);
return res;
}
/*
* Duplicate a string.
* str (IN) string to duplicate
* RETURN copy of string
*/
char *xstrdup(const char *str)
{
size_t siz;
char *result;
if (!str)
return NULL;
siz = strlen(str) + 1;
result = xmalloc(siz);
/* includes terminating NUL from source string */
(void) memcpy(result, str, siz);
return result;
}
/*
* Give me a copy of the string as if it were printf.
* fmt (IN) format of string and args if any
* RETURN copy of formatted string
*/
char *xstrdup_printf(const char *fmt, ...)
{
char *result;
va_list ap;
va_start(ap, fmt);
_xstrdup_vprintf(&result, fmt, ap);
va_end(ap);
return result;
}
/*
* Duplicate at most "n" characters of a string.
* str (IN) string to duplicate
* n (IN)
* RETURN copy of string
*/
char * xstrndup(const char *str, size_t n)
{
size_t siz;
char *result;
if (str == NULL)
return NULL;
siz = strnlen(str, n);
result = xmalloc(siz + 1);
(void) memcpy(result, str, siz);
result[siz] = '\0';
return result;
}
/*
** strtol which only reads 'n' number of chars in the str to get the number
*/
long int xstrntol(const char *str, char **endptr, size_t n, int base)
{
long int number = 0;
char new_str[n+1], *new_endptr = NULL;
memcpy(new_str, str, n);
new_str[n] = '\0';
number = strtol(new_str, &new_endptr, base);
if (endptr)
*endptr = ((char *)str) + (new_endptr - new_str);
return number;
}
/*
* Find the first instance of a sub-string "pattern" in the string "str",
* and replace it with the string "replacement".
* str (IN/OUT) target string (pointer to in case of expansion)
* pattern (IN) substring to look for in str
* replacement (IN) string with which to replace the "pattern" string
* all (IN) replace all or not
*/
void _xstrsubstitute(char **str, const char *pattern, const char *replacement,
const bool all)
{
int pat_len, rep_len;
char *ptr, *end_copy, *pos = NULL;
int append_len, pos_offset;
if (*str == NULL || pattern == NULL || pattern[0] == '\0')
return;
pat_len = strlen(pattern);
if (replacement == NULL)
rep_len = 0;
else
rep_len = strlen(replacement);
append_len = rep_len - pat_len;
pos_offset = 0;
again:
pos = *str + pos_offset;
if ((ptr = strstr(pos, pattern)) == NULL)
return;
/* Get the end after the pattern */
end_copy = xstrdup(ptr + pat_len);
pos_offset += ptr - pos;
if (rep_len != 0) {
/*
* If we are making this bigger, make sure we get enough space.
*/
if (append_len > 0)
_makespace(str, -1, append_len);
memcpy((*str) + pos_offset, replacement, rep_len);
pos_offset += rep_len;
}
/* add the end of new string */
if (end_copy) {
int end_len = strlen(end_copy);
memcpy((*str) + pos_offset, end_copy, end_len);
/* terminate the string if we are shrinking it */
if (append_len < 0)
(*str)[pos_offset + end_len] = '\0';
xfree(end_copy);
}
if (all)
goto again;
return;
}
/* xshort_hostname
* Returns an xmalloc'd string containing the hostname
* of the local machine. The hostname contains only
* the short version of the hostname (e.g. "linux123.foo.bar"
* becomes "linux123")
*
* Returns NULL on error.
*/
char *xshort_hostname(void)
{
int error_code;
char *dot_ptr, path_name[1024];
error_code = gethostname (path_name, sizeof(path_name));
if (error_code)
return NULL;
dot_ptr = strchr (path_name, '.');
if (dot_ptr != NULL)
dot_ptr[0] = '\0';
return xstrdup(path_name);
}
/* Returns true if all characters in a string are whitespace characters,
* otherwise returns false;
*/
bool xstring_is_whitespace(const char *str)
{
int i = 0;
while (str[i] != '\0') {
if (!isspace((int)str[i]))
return false;
i++;
}
return true;
}
extern bool xstrtolower(char *str)
{
bool modified = false;
if (!str)
return modified;
for (int j = 0; str[j]; j++) {
int c = tolower((int) str[j]);
if (c != str[j])
modified = true;
str[j] = c;
}
return modified;
}
/* safe strchr */
char *xstrchr(const char *s1, int c)
{
return s1 ? strchr(s1, c) : NULL;
}
/* safe strrchr */
char *xstrrchr(const char *s1, int c)
{
return s1 ? strrchr(s1, c) : NULL;
}
/* safe strcmp */
int xstrcmp(const char *s1, const char *s2)
{
if (!s1 && !s2)
return 0;
else if (!s1)
return -1;
else if (!s2)
return 1;
else
return strcmp(s1, s2);
}
/* safe strncmp */
int xstrncmp(const char *s1, const char *s2, size_t n)
{
if (!s1 && !s2)
return 0;
else if (!s1)
return -1;
else if (!s2)
return 1;
else
return strncmp(s1, s2, n);
}
/* safe strcasecmp */
int xstrcasecmp(const char *s1, const char *s2)
{
if (!s1 && !s2)
return 0;
else if (!s1)
return -1;
else if (!s2)
return 1;
else
return strcasecmp(s1, s2);
}
/* safe strncasecmp */
int xstrncasecmp(const char *s1, const char *s2, size_t n)
{
if (!s1 && !s2)
return 0;
else if (!s1)
return -1;
else if (!s2)
return 1;
else
return strncasecmp(s1, s2, n);
}
/* safe xstrstr */
char *xstrstr(const char *haystack, const char *needle)
{
if (!haystack || !needle)
return NULL;
return strstr(haystack, needle);
}
char *xstrcasestr(const char *haystack, const char *needle)
{
int hay_inx, hay_size, need_inx, need_size;
char *hay_ptr = (char *) haystack;
if (haystack == NULL || needle == NULL)
return NULL;
hay_size = strlen(haystack);
need_size = strlen(needle);
for (hay_inx=0; hay_inx<hay_size; hay_inx++) {
for (need_inx=0; need_inx<need_size; need_inx++) {
if (tolower((int) hay_ptr[need_inx]) !=
tolower((int) needle [need_inx]))
break; /* mismatch */
}
if (need_inx == need_size) /* it matched */
return hay_ptr;
else /* keep looking */
hay_ptr++;
}
return NULL; /* no match anywhere in string */
}
/*
* Give me a copy of the string as if it were printf.
* This is stdarg-compatible routine, so vararg-compatible
* functions can do va_start() and invoke this function.
*
* fmt (IN) format of string and args if any
* RETURN copy of formatted string
*/
size_t _xstrdup_vprintf(char **str, const char *fmt, va_list ap)
{
/* Start out with a size of 100 bytes. */
int n, size = 100;
va_list our_ap;
char *p = xmalloc(size);
while (1) {
/* Try to print in the allocated space. */
va_copy(our_ap, ap);
n = vsnprintf(p, size, fmt, our_ap);
va_end(our_ap);
/* If that worked, return the string. */
if (n > -1 && n < size) {
*str = p;
return n;
}
/* Else try again with more space. */
if (n > -1) /* glibc 2.1 */
size = n + 1; /* precisely what is needed */
else /* glibc 2.0 */
size *= 2; /* twice the old size */
p = xrealloc(p, size);
}
/* NOTREACHED */
}
extern void xstrtrim(char *string)
{
char *start, *end, *ptr = string;
if (!string || !(*string))
return;
/* walk start to the right until we find NULL or a real character */
while (*ptr && isspace(*ptr))
++ptr;
if (!(*ptr)) {
/* string is all whitespace */
string[0] = '\0';
return;
}
/* save start of non-space string */
start = ptr;
/* find the end of the string */
while (*ptr)
++ptr;
/* save real end of string as it will be overwritten later */
end = ptr;
/* walk to the left to find start of ending whitespace */
do {
char *sptr = ptr - 1;
/* we should never walk past start */
xassert(*sptr);
if (!*sptr || sptr <= start || !isspace(*sptr))
break;
ptr = sptr;
*ptr = '\0';
} while (true);
xassert(*ptr == '\0');
xassert(end >= ptr);
xassert(start >= string);
xassert(start <= end);
/* shift it over if necessary */
if (end != start)
memmove(string, start, (ptr - start + 1));
}
extern char *xstring_bytes2hex(const unsigned char *string, int len,
const char *delimiter)
{
char *hex = NULL, *pos = NULL;
if (len <= 0)
return NULL;
for (int i = 0; i < len; i++) {
if (hex && delimiter)
xstrfmtcatat(hex, &pos, "%s", delimiter);
/* convert each char into equiv hex */
xstrfmtcatat(hex, &pos, "%02x", string[i]);
}
return hex;
}
extern char *xstring_bytes2printable(const unsigned char *string, int len,
const char replace)
{
char *str = NULL, *pos = NULL;
if (len <= 0)
return NULL;
for (int i = 0; i < len; i++) {
if (isalnum(string[i]) || ispunct(string[i]) ||
(string[i] == ' '))
xstrfmtcatat(str, &pos, "%c", string[i]);
else
xstrfmtcatat(str, &pos, "%c", replace);
}
return str;
}
extern char *xbase64_from_base64url(const char *in)
{
char *out;
int i;
size_t length = strlen(in);
/* extra padding in case the padding was stripped off */
out = xmalloc(length + 3);
for (i = 0; i < length; i++) {
switch (in[i]) {
case '-':
out[i] = '+';
break;
case '_':
out[i] = '/';
break;
default:
out[i] = in[i];
}
}
/*
* Fix padding in case it was trimmed.
* String length must be a multiple of 4.
*/
for (int padding = 4 - (i % 4); padding < 4 && padding; padding--)
out[i++] = '=';
return out;
}