Rewrite WUtils string.c in Rust #3

Merged
trurl merged 7 commits from trurl/wmaker:refactor/wutil-rs-string into refactor/wutil-rs 2025-12-08 12:40:57 -05:00
21 changed files with 485 additions and 508 deletions

View File

@@ -15,7 +15,7 @@ wraster = $(top_builddir)/wrlib/libwraster.la
LDADD= libWUtil.la libWINGs.la $(wraster) $(wutilrs) @INTLIBS@
libWINGs_la_LIBADD = libWUtil.la $(wraster) $(wutilrs) @XLIBS@ @XFT_LIBS@ @FCLIBS@ @LIBM@ @PANGO_LIBS@
libWUtil_la_LIBADD = @LIBBSD@ $(wutilrs)
libWUtil_la_LIBADD = $(wutilrs)
EXTRA_DIST = BUGS make-rgb Examples Extras Tests
@@ -76,7 +76,6 @@ libWUtil_la_SOURCES = \
misc.c \
notification.c \
proplist.c \
string.c \
tree.c \
userdefaults.c \
userdefaults.h \

View File

@@ -271,14 +271,9 @@ char* wstrconcat(const char *str1, const char *str2);
* so always assign the returned address to avoid dangling pointers. */
char* wstrappend(char *dst, const char *src);
size_t wstrlcpy(char *, const char *, size_t);
size_t wstrlcat(char *, const char *, size_t);
void wtokensplit(char *command, char ***argv, int *argc);
char* wtokennext(char *word, char **next);
char* wtokenjoin(char **list, int count);
void wtokenfree(char **tokens, int count);

View File

@@ -498,7 +498,7 @@ static void registerDescriptionList(WMScreen * scr, WMView * view, WMArray * ope
for (i = 0; i < count; i++) {
text = WMGetDragOperationItemText(WMGetFromArray(operationArray, i));
wstrlcpy(textListItem, text, size);
strlcpy(textListItem, text, size);
/* to next text offset */
textListItem = &(textListItem[strlen(textListItem) + 1]);

View File

@@ -76,8 +76,8 @@ char *wfindfileinarray(WMPropList *array, const char *file)
path = wmalloc(len + flen + 2);
path = memcpy(path, p, len);
path[len] = 0;
if (wstrlcat(path, "/", len + flen + 2) >= len + flen + 2 ||
wstrlcat(path, file, len + flen + 2) >= len + flen + 2) {
if (strlcat(path, "/", len + flen + 2) >= len + flen + 2 ||
strlcat(path, file, len + flen + 2) >= len + flen + 2) {
wfree(path);
return NULL;
}

View File

@@ -652,7 +652,7 @@ static void mpm_get_hostname(WParserMacro *this, WMenuParser parser)
return;
}
}
wstrlcpy((char *) this->value, h, sizeof(this->value) );
strlcpy((char *) this->value, h, sizeof(this->value) );
}
/* Name of the current user */
@@ -677,7 +677,7 @@ static void mpm_get_user_name(WParserMacro *this, WMenuParser parser)
user = pw_user->pw_name;
if (user == NULL) goto error_no_username;
}
wstrlcpy((char *) this->value, user, sizeof(this->value) );
strlcpy((char *) this->value, user, sizeof(this->value) );
}
/* Number id of the user under which we are running */

View File

@@ -1,425 +0,0 @@
/*
* Until FreeBSD gets their act together;
* http://www.mail-archive.com/freebsd-hackers@freebsd.org/msg69469.html
*/
#if defined( FREEBSD )
# undef _XOPEN_SOURCE
#endif
#include "wconfig.h"
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <ctype.h>
#ifdef HAVE_BSD_STRING_H
#include <bsd/string.h>
#endif
#include "WUtil.h"
#define PRC_ALPHA 0
#define PRC_BLANK 1
#define PRC_ESCAPE 2
#define PRC_DQUOTE 3
#define PRC_EOS 4
#define PRC_SQUOTE 5
typedef struct {
short nstate;
short output;
} DFA;
static DFA mtable[9][6] = {
{{3, 1}, {0, 0}, {4, 0}, {1, 0}, {8, 0}, {6, 0}},
{{1, 1}, {1, 1}, {2, 0}, {3, 0}, {5, 0}, {1, 1}},
{{1, 1}, {1, 1}, {1, 1}, {1, 1}, {5, 0}, {1, 1}},
{{3, 1}, {5, 0}, {4, 0}, {1, 0}, {5, 0}, {6, 0}},
{{3, 1}, {3, 1}, {3, 1}, {3, 1}, {5, 0}, {3, 1}},
{{-1, -1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, /* final state */
{{6, 1}, {6, 1}, {7, 0}, {6, 1}, {5, 0}, {3, 0}},
{{6, 1}, {6, 1}, {6, 1}, {6, 1}, {5, 0}, {6, 1}},
{{-1, -1}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, /* final state */
};
char *wtokennext(char *word, char **next)
{
char *ptr;
char *ret, *t;
int state, ctype;
t = ret = wmalloc(strlen(word) + 1);
ptr = word;
state = 0;
while (1) {
if (*ptr == 0)
ctype = PRC_EOS;
else if (*ptr == '\\')
ctype = PRC_ESCAPE;
else if (*ptr == '"')
ctype = PRC_DQUOTE;
else if (*ptr == '\'')
ctype = PRC_SQUOTE;
else if (*ptr == ' ' || *ptr == '\t')
ctype = PRC_BLANK;
else
ctype = PRC_ALPHA;
if (mtable[state][ctype].output) {
*t = *ptr;
t++;
*t = 0;
}
state = mtable[state][ctype].nstate;
ptr++;
if (mtable[state][0].output < 0) {
break;
}
}
if (*ret == 0) {
wfree(ret);
ret = NULL;
}
if (ctype == PRC_EOS)
*next = NULL;
else
*next = ptr;
return ret;
}
/* separate a string in tokens, taking " and ' into account */
void wtokensplit(char *command, char ***argv, int *argc)
{
char *token, *line;
int count;
count = 0;
line = command;
do {
token = wtokennext(line, &line);
if (token) {
if (count == 0)
*argv = wmalloc(sizeof(**argv));
else
*argv = wrealloc(*argv, (count + 1) * sizeof(**argv));
(*argv)[count++] = token;
}
} while (token != NULL && line != NULL);
*argc = count;
}
char *wtokenjoin(char **list, int count)
{
int i, j;
char *flat_string, *wspace;
j = 0;
for (i = 0; i < count; i++) {
if (list[i] != NULL && list[i][0] != 0) {
j += strlen(list[i]);
if (strpbrk(list[i], " \t"))
j += 2;
}
}
flat_string = wmalloc(j + count + 1);
for (i = 0; i < count; i++) {
if (list[i] != NULL && list[i][0] != 0) {
if (i > 0 &&
wstrlcat(flat_string, " ", j + count + 1) >= j + count + 1)
goto error;
wspace = strpbrk(list[i], " \t");
if (wspace &&
wstrlcat(flat_string, "\"", j + count + 1) >= j + count + 1)
goto error;
if (wstrlcat(flat_string, list[i], j + count + 1) >= j + count + 1)
goto error;
if (wspace &&
wstrlcat(flat_string, "\"", j + count + 1) >= j + count + 1)
goto error;
}
}
return flat_string;
error:
wfree(flat_string);
return NULL;
}
void wtokenfree(char **tokens, int count)
{
while (count--)
wfree(tokens[count]);
wfree(tokens);
}
char *wtrimspace(const char *s)
{
const char *t;
if (s == NULL)
return NULL;
while (isspace(*s) && *s)
s++;
t = s + strlen(s) - 1;
while (t > s && isspace(*t))
t--;
return wstrndup(s, t - s + 1);
}
char *wstrdup(const char *str)
{
assert(str != NULL);
return strcpy(wmalloc(strlen(str) + 1), str);
}
char *wstrndup(const char *str, size_t len)
{
char *copy;
assert(str != NULL);
len = WMIN(len, strlen(str));
copy = strncpy(wmalloc(len + 1), str, len);
copy[len] = 0;
return copy;
}
char *wstrconcat(const char *str1, const char *str2)
{
char *str;
size_t slen, slen1;
if (!str1 && str2)
return wstrdup(str2);
else if (str1 && !str2)
return wstrdup(str1);
else if (!str1 && !str2)
return NULL;
slen1 = strlen(str1);
slen = slen1 + strlen(str2) + 1;
str = wmalloc(slen);
strcpy(str, str1);
strcpy(str + slen1, str2);
return str;
}
char *wstrappend(char *dst, const char *src)
{
size_t slen;
if (!src || *src == 0)
return dst;
else if (!dst)
return wstrdup(src);
slen = strlen(dst) + strlen(src) + 1;
dst = wrealloc(dst, slen);
strcat(dst, src);
return dst;
}
#ifdef HAVE_STRLCAT
size_t
wstrlcat(char *dst, const char *src, size_t siz)
{
return strlcat(dst, src, siz);
}
#else
/* $OpenBSD: strlcat.c,v 1.13 2005/08/08 08:05:37 espie Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Appends src to string dst of size siz (unlike strncat, siz is the
* full size of dst, not space left). At most siz-1 characters
* will be copied. Always NUL terminates (unless siz <= strlen(dst)).
* Returns strlen(src) + MIN(siz, strlen(initial dst)).
* If retval >= siz, truncation occurred.
*/
size_t
wstrlcat(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
size_t dlen;
/* Find the end of dst and adjust bytes left but don't go past end */
while (n-- != 0 && *d != '\0')
d++;
dlen = d - dst;
n = siz - dlen;
if (n == 0)
return(dlen + strlen(s));
while (*s != '\0') {
if (n != 1) {
*d++ = *s;
n--;
}
s++;
}
*d = '\0';
return(dlen + (s - src)); /* count does not include NUL */
}
#endif /* HAVE_STRLCAT */
#ifdef HAVE_STRLCPY
size_t
wstrlcpy(char *dst, const char *src, size_t siz)
{
return strlcpy(dst, src, siz);
}
#else
/* $OpenBSD: strlcpy.c,v 1.11 2006/05/05 15:27:38 millert Exp $ */
/*
* Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
* Copy src to string dst of size siz. At most siz-1 characters
* will be copied. Always NUL terminates (unless siz == 0).
* Returns strlen(src); if retval >= siz, truncation occurred.
*/
size_t
wstrlcpy(char *dst, const char *src, size_t siz)
{
char *d = dst;
const char *s = src;
size_t n = siz;
/* Copy as many bytes as will fit */
if (n != 0) {
while (--n != 0) {
if ((*d++ = *s++) == '\0')
break;
}
}
/* Not enough room in dst, add NUL and traverse rest of src */
if (n == 0) {
if (siz != 0)
*d = '\0'; /* NUL-terminate dst */
while (*s++)
;
}
return(s - src - 1); /* count does not include NUL */
}
#endif /* HAVE_STRLCPY */
/* transform `s' so that the result is safe to pass to the shell as an argument.
* returns a newly allocated string.
* with very heavy inspirations from NetBSD's shquote(3).
*/
char *wshellquote(const char *s)
{
char *p, *r, *last, *ret;
size_t slen;
int needs_quoting;
if (!s)
return NULL;
needs_quoting = !*s; /* the empty string does need quoting */
/* do not quote if consists only of the following characters */
for (p = (char *)s; *p && !needs_quoting; p++) {
needs_quoting = !(isalnum(*p) || (*p == '+') || (*p == '/') ||
(*p == '.') || (*p == ',') || (*p == '-'));
}
if (!needs_quoting)
return wstrdup(s);
for (slen = 0, p = (char *)s; *p; p++) /* count space needed (worst case) */
slen += *p == '\'' ? 4 : 1; /* every single ' becomes ''\' */
slen += 2 /* leading + trailing "'" */ + 1 /* NULL */;
ret = r = wmalloc(slen);
p = (char *)s;
last = p;
if (*p != '\'') /* if string doesn't already begin with "'" */
*r++ ='\''; /* start putting it in quotes */
while (*p) {
last = p;
if (*p == '\'') { /* turn each ' into ''\' */
if (p != s) /* except if it's the first ', in which case */
*r++ = '\''; /* only escape it */
*r++ = '\\';
*r++ = '\'';
while (*++p && *p == '\'') { /* keep turning each consecutive 's into \' */
*r++ = '\\';
*r++ = '\'';
}
if (*p) /* if more input follows, terminate */
*r++ = '\''; /* what we have so far */
} else {
*r++ = *p++;
}
}
if (*last != '\'') /* if the last one isn't already a ' */
*r++ = '\''; /* terminate the whole shebang */
*r = '\0';
return ret; /* technically, we lose (but not leak) a couple of */
/* bytes (twice the number of consecutive 's in the */
/* input or so), but since these are relatively rare */
/* and short-lived strings, not sure if a trip to */
/* wstrdup+wfree worths the gain. */
}

View File

@@ -101,21 +101,21 @@ static char *checkFile(const char *path, const char *folder, const char *ext, co
slen = strlen(path) + strlen(resource) + 1 + extralen;
ret = wmalloc(slen);
if (wstrlcpy(ret, path, slen) >= slen)
if (strlcpy(ret, path, slen) >= slen)
goto error;
if (folder &&
(wstrlcat(ret, "/", slen) >= slen ||
wstrlcat(ret, folder, slen) >= slen))
(strlcat(ret, "/", slen) >= slen ||
strlcat(ret, folder, slen) >= slen))
goto error;
if (ext &&
(wstrlcat(ret, "/", slen) >= slen ||
wstrlcat(ret, ext, slen) >= slen))
(strlcat(ret, "/", slen) >= slen ||
strlcat(ret, ext, slen) >= slen))
goto error;
if (wstrlcat(ret, "/", slen) >= slen ||
wstrlcat(ret, resource, slen) >= slen)
if (strlcat(ret, "/", slen) >= slen ||
strlcat(ret, resource, slen) >= slen)
goto error;
if (access(ret, F_OK) != 0)

View File

@@ -720,14 +720,14 @@ char *WMGetBrowserPathToColumn(WMBrowser * bPtr, int column)
path = wmalloc(slen);
/* ignore first `/' */
for (i = 0; i <= column; i++) {
if (wstrlcat(path, bPtr->pathSeparator, slen) >= slen)
if (strlcat(path, bPtr->pathSeparator, slen) >= slen)
goto error;
item = WMGetListSelectedItem(bPtr->columns[i]);
if (!item)
break;
if (wstrlcat(path, item->text, slen) >= slen)
if (strlcat(path, item->text, slen) >= slen)
goto error;
}
@@ -782,7 +782,7 @@ WMArray *WMGetBrowserPaths(WMBrowser * bPtr)
path = wmalloc(slen);
/* ignore first `/' */
for (i = 0; i <= column; i++) {
wstrlcat(path, bPtr->pathSeparator, slen);
strlcat(path, bPtr->pathSeparator, slen);
if (i == column) {
item = lastItem;
} else {
@@ -790,7 +790,7 @@ WMArray *WMGetBrowserPaths(WMBrowser * bPtr)
}
if (!item)
break;
wstrlcat(path, item->text, slen);
strlcat(path, item->text, slen);
}
WMAddToArray(paths, path);
}
@@ -1130,25 +1130,25 @@ static char *createTruncatedString(WMFont * font, const char *text, int *textLen
if (width >= 3 * dLen) {
int tmpTextLen = *textLen;
if (wstrlcpy(textBuf, text, slen) >= slen)
if (strlcpy(textBuf, text, slen) >= slen)
goto error;
while (tmpTextLen && (WMWidthOfString(font, textBuf, tmpTextLen) + 3 * dLen > width))
tmpTextLen--;
if (wstrlcpy(textBuf + tmpTextLen, "...", slen) >= slen)
if (strlcpy(textBuf + tmpTextLen, "...", slen) >= slen)
goto error;
*textLen = tmpTextLen + 3;
} else if (width >= 2 * dLen) {
if (wstrlcpy(textBuf, "..", slen) >= slen)
if (strlcpy(textBuf, "..", slen) >= slen)
goto error;
*textLen = 2;
} else if (width >= dLen) {
if (wstrlcpy(textBuf, ".", slen) >= slen)
if (strlcpy(textBuf, ".", slen) >= slen)
goto error;
*textLen = 1;

View File

@@ -513,12 +513,12 @@ static void listDirectoryOnColumn(WMFilePanel * panel, int column, const char *p
if (strcmp(dentry->d_name, ".") == 0 || strcmp(dentry->d_name, "..") == 0)
continue;
if (wstrlcpy(pbuf, path, sizeof(pbuf)) >= sizeof(pbuf))
if (strlcpy(pbuf, path, sizeof(pbuf)) >= sizeof(pbuf))
goto out;
if (strcmp(path, "/") != 0 &&
wstrlcat(pbuf, "/", sizeof(pbuf)) >= sizeof(pbuf))
strlcat(pbuf, "/", sizeof(pbuf)) >= sizeof(pbuf))
goto out;
if (wstrlcat(pbuf, dentry->d_name, sizeof(pbuf)) >= sizeof(pbuf))
if (strlcat(pbuf, dentry->d_name, sizeof(pbuf)) >= sizeof(pbuf))
goto out;
if (stat(pbuf, &stat_buf) != 0) {
@@ -626,11 +626,11 @@ static void createDir(WMWidget *widget, void *p_panel)
file = wmalloc(slen);
if (directory &&
(wstrlcat(file, directory, slen) >= slen ||
wstrlcat(file, "/", slen) >= slen))
(strlcat(file, directory, slen) >= slen ||
strlcat(file, "/", slen) >= slen))
goto out;
if (wstrlcat(file, dirName, slen) >= slen)
if (strlcat(file, dirName, slen) >= slen)
goto out;
if (mkdir(file, 00777) != 0) {

View File

@@ -558,7 +558,7 @@ static void listFamilies(WMScreen * scr, WMFontPanel * panel)
WMListItem *item;
WM_ITERATE_ARRAY(array, fam, i) {
wstrlcpy(buffer, fam->name, sizeof(buffer));
strlcpy(buffer, fam->name, sizeof(buffer));
item = WMAddListItem(panel->famLs, buffer);
item->clientData = fam;
@@ -640,7 +640,7 @@ static void familyClick(WMWidget * w, void *data)
int top = 0;
WMListItem *fitem;
wstrlcpy(buffer, face->typeface, sizeof(buffer));
strlcpy(buffer, face->typeface, sizeof(buffer));
if (strcasecmp(face->typeface, "Roman") == 0)
top = 1;
if (strcasecmp(face->typeface, "Regular") == 0)
@@ -773,7 +773,7 @@ static void setFontPanelFontName(FontPanel * panel, const char *family, const ch
int top = 0;
WMListItem *fitem;
wstrlcpy(buffer, face->typeface, sizeof(buffer));
strlcpy(buffer, face->typeface, sizeof(buffer));
if (strcasecmp(face->typeface, "Roman") == 0)
top = 1;
if (top)

View File

@@ -404,7 +404,7 @@ void WMInsertTextFieldText(WMTextField * tPtr, const char *text, int position)
if (position < 0 || position >= tPtr->textLen) {
/* append the text at the end */
wstrlcat(tPtr->text, text, tPtr->bufferSize);
strlcat(tPtr->text, text, tPtr->bufferSize);
tPtr->textLen += len;
tPtr->cursorPosition += len;
incrToFit(tPtr);
@@ -473,7 +473,7 @@ void WMSetTextFieldText(WMTextField * tPtr, const char *text)
tPtr->bufferSize = tPtr->textLen + TEXT_BUFFER_INCR;
tPtr->text = wrealloc(tPtr->text, tPtr->bufferSize);
}
wstrlcpy(tPtr->text, text, tPtr->bufferSize);
strlcpy(tPtr->text, text, tPtr->bufferSize);
}
tPtr->cursorPosition = tPtr->selection.position = tPtr->textLen;

View File

@@ -365,7 +365,7 @@ char *capture_shortcut(Display *dpy, Bool *capturing, Bool convert_case)
if ((numlock_mask != Mod5Mask) && (ev.xkey.state & Mod5Mask))
strcat(buffer, "Mod5+");
wstrlcat(buffer, key, sizeof(buffer));
strlcat(buffer, key, sizeof(buffer));
return wstrdup(buffer);
}

View File

@@ -391,39 +391,6 @@ dnl the flag 'O_NOFOLLOW' for 'open' is used in WINGs
WM_FUNC_OPEN_NOFOLLOW
dnl Check for strlcat/strlcpy
dnl =========================
m4_divert_push([INIT_PREPARE])dnl
AC_ARG_WITH([libbsd],
[AS_HELP_STRING([--without-libbsd], [do not use libbsd for strlcat and strlcpy [default=check]])],
[AS_IF([test "x$with_libbsd" != "xno"],
[with_libbsd=bsd],
[with_libbsd=]
)],
[with_libbsd=bsd])
m4_divert_pop([INIT_PREPARE])dnl
tmp_libs=$LIBS
AC_SEARCH_LIBS([strlcat],[$with_libbsd],
[AC_DEFINE(HAVE_STRLCAT, 1, [Define if strlcat is available])],
[],
[]
)
AC_SEARCH_LIBS([strlcpy],[$with_libbsd],
[AC_DEFINE(HAVE_STRLCAT, 1, [Define if strlcpy is available])],
[],
[]
)
LIBS=$tmp_libs
LIBBSD=
AS_IF([test "x$ac_cv_search_strlcat" = "x-lbsd" -o "x$ac_cv_search_strlcpy" = "x-lbsd"],
[LIBBSD=-lbsd
AC_CHECK_HEADERS([bsd/string.h])]
)
AC_SUBST(LIBBSD)
dnl Check for OpenBSD kernel memory interface - kvm(3)
dnl ==================================================
AS_IF([test "x$WM_OSDEP" = "xbsd"],

View File

@@ -94,7 +94,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun
wwarning(_("appmenu: bad menu entry \"%s\" in window %lx"), slist[*index], win);
return NULL;
}
if (wstrlcpy(title, &slist[*index][pos], sizeof(title)) >= sizeof(title)) {
if (strlcpy(title, &slist[*index][pos], sizeof(title)) >= sizeof(title)) {
wwarning(_("appmenu: menu command size exceeded in window %lx"), win);
return NULL;
}
@@ -127,7 +127,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun
slist[*index], win);
return NULL;
}
wstrlcpy(title, &slist[*index][pos], sizeof(title));
strlcpy(title, &slist[*index][pos], sizeof(title));
rtext[0] = 0;
} else {
if (sscanf(slist[*index], "%i %i %i %i %s %n",
@@ -137,7 +137,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun
slist[*index], win);
return NULL;
}
wstrlcpy(title, &slist[*index][pos], sizeof(title));
strlcpy(title, &slist[*index][pos], sizeof(title));
}
data = wmalloc(sizeof(WAppMenuData));
if (data == NULL) {
@@ -174,7 +174,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun
return NULL;
}
wstrlcpy(title, &slist[*index][pos], sizeof(title));
strlcpy(title, &slist[*index][pos], sizeof(title));
*index += 1;
submenu = parseMenuCommand(scr, win, slist, count, index);

View File

@@ -2205,7 +2205,7 @@ static int getKeybind(WScreen * scr, WDefaultEntry * entry, WMPropList * value,
return True;
}
wstrlcpy(buf, val, MAX_SHORTCUT_LENGTH);
strlcpy(buf, val, MAX_SHORTCUT_LENGTH);
b = (char *)buf;

View File

@@ -605,9 +605,9 @@ static void listPixmaps(WScreen *scr, WMList *lPtr, const char *path)
if (strcmp(dentry->d_name, ".") == 0 || strcmp(dentry->d_name, "..") == 0)
continue;
if (wstrlcpy(pbuf, apath, sizeof(pbuf)) >= sizeof(pbuf) ||
wstrlcat(pbuf, "/", sizeof(pbuf)) >= sizeof(pbuf) ||
wstrlcat(pbuf, dentry->d_name, sizeof(pbuf)) >= sizeof(pbuf)) {
if (strlcpy(pbuf, apath, sizeof(pbuf)) >= sizeof(pbuf) ||
strlcat(pbuf, "/", sizeof(pbuf)) >= sizeof(pbuf) ||
strlcat(pbuf, dentry->d_name, sizeof(pbuf)) >= sizeof(pbuf)) {
wwarning(_("full path for file \"%s\" in \"%s\" is longer than %d bytes, skipped"),
dentry->d_name, path, (int) (sizeof(pbuf) - 1) );
continue;

View File

@@ -406,7 +406,7 @@ static Bool addShortcut(const char *file, const char *shortcutDefinition, WMenu
ptr = wmalloc(sizeof(Shortcut));
wstrlcpy(buf, shortcutDefinition, MAX_SHORTCUT_LENGTH);
strlcpy(buf, shortcutDefinition, MAX_SHORTCUT_LENGTH);
b = (char *)buf;
/* get modifiers */

View File

@@ -797,7 +797,7 @@ void wWorkspaceMenuUpdate(WScreen * scr, WMenu * menu)
i = scr->workspace_count - (menu->entry_no - MC_WORKSPACE1);
ws = menu->entry_no - MC_WORKSPACE1;
while (i > 0) {
wstrlcpy(title, scr->workspaces[ws]->name, MAX_WORKSPACENAME_WIDTH);
strlcpy(title, scr->workspaces[ws]->name, MAX_WORKSPACENAME_WIDTH);
entry = wMenuAddCallback(menu, title, switchWSCommand, (void *)ws);
entry->flags.indicator = 1;

View File

@@ -9,7 +9,8 @@ RUST_SOURCES = \
src/hash_table.rs \
src/lib.rs \
src/memory.rs \
src/prop_list.rs
src/prop_list.rs \
src/string.rs
RUST_EXTRA = \
Cargo.lock \

View File

@@ -5,3 +5,4 @@ pub mod find_file;
pub mod hash_table;
pub mod memory;
pub mod prop_list;
pub mod string;

439
wutil-rs/src/string.rs Normal file
View File

@@ -0,0 +1,439 @@
//! String manipulation utilities.
//!
//! ## Rust rewrite notes
//!
//! These are more or less bug-for-bug reimplementations of the original WUtils
//! functions. Avoid using these functions in new code.
use std::{
ffi::{c_char, c_int, CStr},
iter, mem, ptr, slice,
};
use crate::memory::{alloc_bytes, alloc_string, ffi::wrealloc, free_bytes};
/// Returns a `wmalloc`-managed C-style string that duplicates `s`, which cannot
/// be null.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wstrdup(s: *const c_char) -> *mut c_char {
assert!(!s.is_null());
alloc_string(unsafe { CStr::from_ptr(s) })
}
/// Returns a `wmalloc`-managed C-style string of the first `len` bytes of `s`,
/// which cannot be null.
///
/// If `len` exceeds the length of `s`, uses the lesser value.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wstrndup(s: *const c_char, len: usize) -> *mut c_char {
assert!(!s.is_null());
let len = unsafe {
slice::from_raw_parts(s, len)
Outdated
Review

I don't think this is what you want, necessarily. You want to find a zero byte, sure, but looking beyond the first len bytes is not useful and may not even be correct, if the source string is not e.g. 0 terminated. I would argue that's a bug, but I could be wrong.

So I'd do something like this:

pub unsafe extern "C" fn strnlen(s: *const c_char, maxlen: usize) -> usize {
    assert!(!s.is_null());
    let s = s.cast<u8>();
    let bs = unsafe { std::slice::from_raw_parts(s, maxlen) };
    bs.into_iter().position(|p| p == 0).unwrap_or(maxlen)
}

pub unsafe extern "C" fn wstrndup(s: *const c_char, len: usize) -> *mut c_char {
    assert!(!s.is_null());
    let len = strnlen(s, len);
    let copy = allow_bytes(len + 1).cast();
    unsafe {
        ptr::copy(s.cast(), copy, len);
    }
    copy.cast()
}
I don't think this is what you want, necessarily. You want to find a zero byte, sure, but looking beyond the first `len` bytes is not useful and may not even be correct, if the source string is not e.g. 0 terminated. I would argue that's a bug, but I could be wrong. So I'd do something like this: ``` pub unsafe extern "C" fn strnlen(s: *const c_char, maxlen: usize) -> usize { assert!(!s.is_null()); let s = s.cast<u8>(); let bs = unsafe { std::slice::from_raw_parts(s, maxlen) }; bs.into_iter().position(|p| p == 0).unwrap_or(maxlen) } pub unsafe extern "C" fn wstrndup(s: *const c_char, len: usize) -> *mut c_char { assert!(!s.is_null()); let len = strnlen(s, len); let copy = allow_bytes(len + 1).cast(); unsafe { ptr::copy(s.cast(), copy, len); } copy.cast() } ```
Outdated
Review

Thanks, this is an improvement. (The extant wstrndup called strlen on s, so I didn't feel too badly about scanning ahead for a null byte ... but we can do better.)

I'd like to migrate away from supporting C-style strings, so I'm putting the length computation inline.

Thanks, this is an improvement. (The extant `wstrndup` called `strlen` on `s`, so I didn't feel too badly about scanning ahead for a null byte ... but we can do better.) I'd like to migrate away from supporting C-style strings, so I'm putting the length computation inline.
.into_iter()
.position(|p| *p == 0)
.unwrap_or(len)
};
let copy: *mut c_char = alloc_bytes(len + 1).cast(); // Implicitly zeroed.
unsafe {
ptr::copy_nonoverlapping(s, copy, len);
}
copy.cast::<c_char>()
}
/// Concatenates `s1` and `s2` into a `wmalloc`-managed C-style string.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wstrconcat(s1: *const c_char, s2: *const c_char) -> *mut c_char {
match (s1.is_null(), s2.is_null()) {
(true, true) => ptr::null_mut(),
(true, false) => unsafe { wstrdup(s1) },
(false, true) => unsafe { wstrdup(s2) },
(false, false) => unsafe {
let s1 = CStr::from_ptr(s1);
let l1 = s1.count_bytes();
let s2 = CStr::from_ptr(s2);
let l2 = s2.count_bytes();
let s: *mut c_char = alloc_bytes(l1 + l2 + 1).cast(); // Implicitly zeroed.
ptr::copy_nonoverlapping(s1.as_ptr(), s, l1);
ptr::copy_nonoverlapping(s2.as_ptr(), s.offset(l1 as isize), l2);
s
},
}
}
/// Appends `src` to `dest`, destructively reallocating `dest` and returning a
/// new `wmalloc`-managed pointer where the result is stored. `dst` must come
/// from `wmalloc` or `wrealloc`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wstrappend(dst: *mut c_char, src: *const c_char) -> *mut c_char {
if src.is_null() {
return dst;
}
let src = unsafe { CStr::from_ptr(src) };
let src_len = src.count_bytes();
if src_len == 0 {
return dst;
}
if dst.is_null() {
return unsafe { wstrdup(src.as_ptr()) };
}
let dst_len = unsafe { CStr::from_ptr(dst).count_bytes() + 1 };
let len = dst_len + src_len + 1;
let result: *mut c_char = unsafe { wrealloc(dst.cast(), len).cast() };
unsafe {
ptr::copy_nonoverlapping(
src.as_ptr(),
result.offset(dst_len.try_into().unwrap()),
src_len,
);
}
result
}
/// Strips leading and trailing whitespace from `s`, returning a
/// `wmalloc`-managed C-style string holding the result.
///
/// ## Rust rewite notes
///
/// This uses a slightly different notion of "space character" than the original
/// C implementation did. `s` must be valid UTF-8.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wtrimspace(s: *const c_char) -> *mut c_char {
if s.is_null() {
return ptr::null_mut();
}
let Ok(s) = (unsafe { CStr::from_ptr(s).to_str() }) else {
Outdated
Review

So this will allocate a Vec<u8> to hold the trimmed bytes, and then invoke alloc_string to do another allocation. I don't know if there's a good way around that; perhaps use strndup? Something like,

    let trimmmed = s.trim();
    let ptr = trimmed.as_ptr();
    let len = trimmed.len();
    unsafe { wstrndup(ptr, len) }
So this will allocate a `Vec<u8>` to hold the trimmed bytes, and then invoke `alloc_string` to do another allocation. I don't know if there's a good way around that; perhaps use `strndup`? Something like, ```rust let trimmmed = s.trim(); let ptr = trimmed.as_ptr(); let len = trimmed.len(); unsafe { wstrndup(ptr, len) } ```
Outdated
Review

This is an improvement. Thanks!

This is an improvement. Thanks!
// TODO: complain.
return ptr::null_mut();
};
let trimmed = s.trim();
let ptr = trimmed.as_ptr();
let len = trimmed.len();
unsafe { wstrndup(ptr, len) }
}
/// Splits `command` into tokens with approximately the same rules as used in
/// the shell for splitting up a command into program arguments.
fn tokensplit(command: &[u8]) -> Vec<Vec<u8>> {
enum Mode {
Start,
Unquoted(Vec<u8>),
Escape(Context),
Quoted { token: Vec<u8>, delimiter: u8, },
}
enum Context {
Unquoted(Vec<u8>),
Quoted { token: Vec<u8>, delimiter: u8, },
}
let mut out = Vec::new();
let mut mode = Mode::Start;
for &b in command {
mode = match (mode, b) {
(Mode::Start, b'\\') => Mode::Escape(Context::Unquoted(vec![])),
(Mode::Start, b'\'' | b'"') => Mode::Quoted { token: vec![], delimiter: b },
(Mode::Start, b' ' | b'\t') => Mode::Start,
(Mode::Start, _) => Mode::Unquoted(vec![b]),
(Mode::Unquoted(token), b'\\') => Mode::Escape(Context::Unquoted(token)),
(Mode::Unquoted(token), b'\'' | b'"') => Mode::Quoted { token, delimiter: b },
(Mode::Unquoted(token), b' ' | b'\t') => {
out.push(token);
Mode::Start
}
(Mode::Unquoted(mut token), _) => {
token.push(b);
Mode::Unquoted(token)
}
(Mode::Escape(Context::Unquoted(mut token)), _) => {
token.push(b);
Mode::Unquoted(token)
}
(Mode::Escape(Context::Quoted { mut token, delimiter }), _) => {
token.push(b);
Mode::Quoted { token, delimiter }
}
(Mode::Quoted { token, delimiter }, _) if b == delimiter => {
Mode::Unquoted(token)
}
(Mode::Quoted { token, delimiter }, _) if b == b'\\' => {
Mode::Escape(Context::Quoted { token, delimiter })
}
(Mode::Quoted { mut token, delimiter }, _) => {
token.push(b);
Mode::Quoted { token, delimiter }
}
}
}
match mode {
Mode::Start => (),
Mode::Unquoted(token) => out.push(token),
Mode::Quoted { token, .. } => out.push(token),
Mode::Escape(Context::Unquoted(token)) => out.push(token),
Mode::Escape(Context::Quoted { token, .. }) => out.push(token),
}
out
}
/// Splits `command` into tokens, storing the number of tokens in `argc` and
/// `wmalloc`-managed C-style strings for each token in `argv`. Call
/// [`wtokenfree`] to free `argv`.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wtokensplit(
command: *const c_char,
argv: *mut *mut *mut c_char,
argc: *mut c_int,
) {
if argv.is_null() || argc.is_null() {
return;
}
unsafe {
*argv = ptr::null_mut();
*argc = 0;
}
if command.is_null() {
return;
}
let command = unsafe { CStr::from_ptr(command) };
let Ok(command) = command.to_str() else {
return;
};
let tokens = tokensplit(command.as_bytes());
if tokens.is_empty() {
return;
}
let argv = unsafe {
*argv = alloc_bytes(mem::size_of::<*mut c_char>() * tokens.len()).cast::<*mut c_char>();
*argc = tokens.len() as c_int;
slice::from_raw_parts_mut(*argv, tokens.len())
};
for (dest, mut token) in argv.iter_mut().zip(tokens.into_iter()) {
token.push(b'\0');
*dest = alloc_string(unsafe { CStr::from_bytes_with_nul_unchecked(&token) });
}
}
/// Frees an `argv` populated by [`wtokensplit`].
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wtokenfree(tokens: *mut *mut c_char, count: c_int) {
if tokens.is_null() {
return;
}
if count > 0 {
let tokens = unsafe { slice::from_raw_parts_mut(tokens, count as usize) };
for token in tokens {
unsafe {
free_bytes(*token);
}
}
}
unsafe {
free_bytes(tokens.cast::<u8>());
}
}
/// Joins the tokens of the `count` elements of `list` into a single string,
/// enclosing them in double quotes (`"`) if necessary. Returns a
/// `wmalloc`-managed C-style string with the result, or null on failure.
///
/// ## Rust rewrite notes
///
/// `list` must consist of valid UTF-8 strings. This reimplementation does not
/// attempt to improve on the original, so it has bad failure modes (like not
/// escaping quotes in the input). Do not use this function in new code.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn wtokenjoin(list: *const *const char, count: c_int) -> *mut c_char {
if list.is_null() || count <= 0 {
return alloc_string(c"");
}
let list = unsafe {
slice::from_raw_parts(list.cast::<*const u8>(), count as usize)
};
let mut buffer = Vec::new();
for term in list {
if term.is_null() {
continue;
}
let term = unsafe { CStr::from_ptr(*term) };
if term.is_empty() {
continue;
}
if !buffer.is_empty() {
buffer.push(b' ');
}
let term = term.to_bytes();
if term.iter().find(|&&x| x == b' ' || x == b'\t').is_some() {
buffer.push(b'"');
buffer.extend_from_slice(term);
buffer.push(b'"');
} else {
buffer.extend_from_slice(term);
}
}
buffer.push(b'\0');
if let Ok(buffer) = CStr::from_bytes_until_nul(&buffer) {
alloc_string(buffer)
} else {
return ptr::null_mut();
}
}
#[cfg(test)]
mod test {
use super::{wtokenfree, wtokensplit, wtokenjoin};
use std::{ffi::{c_char, c_int, CStr}, ptr, slice};
#[test]
fn split_empty_whitespace() {
let mut argv = ptr::null_mut();
let mut argc = 0;
unsafe { wtokensplit(c"".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 0);
assert_eq!(argv, ptr::null_mut());
unsafe { wtokenfree(argv, argc); }
unsafe { wtokensplit(c" ".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 0);
assert_eq!(argv, ptr::null_mut());
unsafe { wtokenfree(argv, argc); }
unsafe { wtokensplit(c" \t ".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 0);
assert_eq!(argv, ptr::null_mut());
unsafe { wtokenfree(argv, argc); }
unsafe { wtokensplit(c" \t ".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 0);
assert_eq!(argv, ptr::null_mut());
unsafe { wtokenfree(argv, argc); }
unsafe { wtokensplit(c"\t\t".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 0);
assert_eq!(argv, ptr::null_mut());
unsafe { wtokenfree(argv, argc); }
}
fn args_of(argv: *mut *mut u8, argc: usize) -> Vec<String> {
let mut v = Vec::with_capacity(argc);
for s in unsafe { slice::from_raw_parts(argv, argc) } {
if s.is_null() {
return v;
}
v.push(String::from(unsafe { CStr::from_ptr(*s) }.to_str().unwrap()));
}
v
}
#[test]
fn split_empty_quoted() {
let mut argv = ptr::null_mut();
let mut argc = 0;
unsafe { wtokensplit(c"\"\"".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 1);
assert_eq!(args_of(argv, argc as usize), vec![String::from("")]);
unsafe { wtokenfree(argv, argc); }
argv = ptr::null_mut();
argc = 0;
unsafe { wtokensplit(c"''".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 1);
assert_eq!(args_of(argv, argc as usize), vec![String::from("")]);
unsafe { wtokenfree(argv, argc); }
argv = ptr::null_mut();
argc = 0;
unsafe { wtokensplit(c" ''\t".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 1);
assert_eq!(args_of(argv, argc as usize), vec![String::from("")]);
unsafe { wtokenfree(argv, argc); }
}
#[test]
fn split_one_unquoted() {
let mut argv = ptr::null_mut();
let mut argc = 0;
unsafe { wtokensplit(c"hello".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 1);
assert_eq!(args_of(argv, argc as usize), vec![String::from("hello")]);
unsafe { wtokensplit(c"hello\\\\".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 1);
assert_eq!(args_of(argv, argc as usize), vec![String::from("hello\\")]);
}
#[test]
fn split_one_quoted() {
let mut argv = ptr::null_mut();
let mut argc = 0;
unsafe { wtokensplit(c"\"hello\"".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 1);
assert_eq!(args_of(argv, argc as usize), vec![String::from("hello")]);
unsafe { wtokensplit(c"\"hello world\"".as_ptr(), &mut argv, &mut argc); }
assert_eq!(argc, 1);
assert_eq!(args_of(argv, argc as usize), vec![String::from("hello world")]);
}
#[test]
fn split_multi() {
let mut argv = ptr::null_mut();
let mut argc = 0;
unsafe {
wtokensplit(
c"\"hello world\" what\\'s happening' here it\\'s weird'".as_ptr(),
&mut argv,
&mut argc,
);
}
assert_eq!(argc, 3);
assert_eq!(args_of(argv, argc as usize), vec![String::from("hello world"),
String::from("what's"),
String::from("happening here it's weird")]);
}
/// Calls `f(wtokenjoin(list))` transparently.
fn with_list<R>(list: &[&CStr], f: impl FnOnce(Option<&CStr>) -> R) -> R {
let list: Vec<_> = list.iter().map(|s| s.as_ptr().cast::<c_char>()).collect();
let joined = unsafe { wtokenjoin(list.as_slice().as_ptr().cast(), list.len() as c_int) };
let result = f(if joined.is_null() { None } else { unsafe { Some(CStr::from_ptr(joined)) } });
unsafe { crate::memory::free_bytes(joined.cast()); }
result
}
#[test]
fn join_nothing() {
with_list(&[], |joined| {
assert_eq!(joined.unwrap(), c"");
});
with_list(&[c"", c""], |joined| {
assert_eq!(joined.unwrap(), c"");
});
}
#[test]
fn join_basic() {
with_list(&[c"hello", c"world"], |joined| {
assert_eq!(joined.unwrap(), c"hello world");
});
}
#[test]
fn join_quoted() {
with_list(&[c"hello", c"there world"], |joined| {
assert_eq!(joined.unwrap(), c"hello \"there world\"");
});
}
}