From d91275d959071c4f857a81bb9257a5c28975a0fa Mon Sep 17 00:00:00 2001 From: Dan Cross Date: Sun, 12 Oct 2025 01:14:35 +0000 Subject: [PATCH 01/39] memory: replace most of memory.c with Rust mem Replace `wmalloc` et al with wrappers around the Rust allocation library. --- WINGs/findfile.c | 6 +- WINGs/handlers.c | 4 +- WINGs/memory.c | 176 +------------------------------ WINGs/widgets.c | 3 +- WPrefs.app/Docks.c | 2 +- WPrefs.app/Focus.c | 2 +- WPrefs.app/HotCornerShortcuts.c | 2 +- WPrefs.app/Makefile.am | 1 + src/appmenu.c | 4 +- src/dialog.c | 31 +----- src/dock.c | 12 +-- src/event.c | 6 +- src/geomview.c | 2 +- src/main.c | 8 +- src/misc.c | 12 +-- src/properties.c | 12 +-- src/rootmenu.c | 8 +- src/session.c | 6 +- src/superfluous.c | 4 +- src/switchpanel.c | 3 +- src/window.c | 12 +-- src/xdnd.c | 4 +- util/Makefile.am | 33 ++++-- util/getstyle.c | 6 +- util/wmiv.c | 18 ++-- util/wmmenugen_misc.c | 2 +- util/wmsetbg.c | 4 +- util/wxcopy.c | 11 +- wmaker-rs/src/lib.rs | 3 +- wmaker-rs/src/memory.rs | 180 ++++++++++++++++++++++++++++++++ wmlib/Makefile.am | 3 +- wmlib/app.c | 12 ++- wmlib/menu.c | 30 +++--- wrlib/Makefile.am | 3 +- wrlib/context.c | 32 +++--- wrlib/convert.c | 44 ++++---- wrlib/convolve.c | 6 +- wrlib/load.c | 18 ++-- wrlib/load_gif.c | 6 +- wrlib/load_jpeg.c | 6 +- wrlib/load_png.c | 14 +-- wrlib/load_ppm.c | 8 +- wrlib/load_webp.c | 14 +-- wrlib/load_xpm.c | 8 +- wrlib/load_xpm_normalized.c | 42 ++++---- wrlib/raster.c | 12 ++- wrlib/save_jpeg.c | 6 +- wrlib/save_png.c | 6 +- wrlib/save_xpm.c | 6 +- wrlib/scale.c | 28 ++--- wrlib/tests/testgrad.c | 12 +-- wrlib/xutil.c | 24 +++-- 52 files changed, 475 insertions(+), 442 deletions(-) create mode 100644 wmaker-rs/src/memory.rs diff --git a/WINGs/findfile.c b/WINGs/findfile.c index bb2ce7f0..03026aee 100644 --- a/WINGs/findfile.c +++ b/WINGs/findfile.c @@ -456,7 +456,7 @@ int wcopy_file(const char *dest_dir, const char *src_file, const char *dest_file return -1; } - buffer = malloc(buffer_size); /* Don't use wmalloc to avoid the memset(0) we don't need */ + buffer = wmalloc(buffer_size); if (buffer == NULL) { werror(_("could not allocate memory for the copy buffer")); close(fd_dst); @@ -508,14 +508,14 @@ int wcopy_file(const char *dest_dir, const char *src_file, const char *dest_file if (close(fd_dst) != 0) { werror(_("could not close the file \"%s\": %s"), path_dst, strerror(errno)); cleanup_and_return_failure: - free(buffer); + wfree(buffer); close(fd_src); unlink(path_dst); wfree(path_dst); return -1; } - free(buffer); + wfree(buffer); wfree(path_dst); close(fd_src); diff --git a/WINGs/handlers.c b/WINGs/handlers.c index 88682867..264fb14c 100644 --- a/WINGs/handlers.c +++ b/WINGs/handlers.c @@ -126,7 +126,7 @@ WMHandlerID WMAddTimerHandler(int milliseconds, WMCallback * callback, void *cda { TimerHandler *handler; - handler = malloc(sizeof(TimerHandler)); + handler = wmalloc(sizeof(TimerHandler)); if (!handler) return NULL; @@ -214,7 +214,7 @@ WMHandlerID WMAddIdleHandler(WMCallback * callback, void *cdata) { IdleHandler *handler; - handler = malloc(sizeof(IdleHandler)); + handler = wmalloc(sizeof(IdleHandler)); if (!handler) return NULL; diff --git a/WINGs/memory.c b/WINGs/memory.c index e05e17a8..256f6516 100644 --- a/WINGs/memory.c +++ b/WINGs/memory.c @@ -19,34 +19,15 @@ * MA 02110-1301, USA. */ -#include "wconfig.h" +#include + #include "WUtil.h" -#include -#include -#include -#include -#include #include #include - -#ifdef HAVE_STDNORETURN +#include #include -#endif - -#ifdef USE_BOEHM_GC -#ifndef GC_DEBUG -#define GC_DEBUG -#endif /* !GC_DEBUG */ -#include -#endif /* USE_BOEHM_GC */ - -#ifndef False -# define False 0 -#endif -#ifndef True -# define True 1 -#endif +#include static void defaultHandler(int bla) { @@ -72,152 +53,3 @@ waborthandler *wsetabort(waborthandler * handler) return old; } - -static int Aborting = 0; /* if we're in the middle of an emergency exit */ - -static WMHashTable *table = NULL; - -void *wmalloc(size_t size) -{ - void *tmp; - - assert(size > 0); - -#ifdef USE_BOEHM_GC - tmp = GC_MALLOC(size); -#else - tmp = malloc(size); -#endif - if (tmp == NULL) { - wwarning("malloc() failed. Retrying after 2s."); - sleep(2); -#ifdef USE_BOEHM_GC - tmp = GC_MALLOC(size); -#else - tmp = malloc(size); -#endif - if (tmp == NULL) { - if (Aborting) { - fputs("Really Bad Error: recursive malloc() failure.", stderr); - exit(-1); - } else { - wfatal("virtual memory exhausted"); - Aborting = 1; - wAbort(False); - } - } - } - if (tmp != NULL) - memset(tmp, 0, size); - return tmp; -} - -void *wrealloc(void *ptr, size_t newsize) -{ - void *nptr; - - if (!ptr) { - nptr = wmalloc(newsize); - } else if (newsize == 0) { - wfree(ptr); - nptr = NULL; - } else { -#ifdef USE_BOEHM_GC - nptr = GC_REALLOC(ptr, newsize); -#else - nptr = realloc(ptr, newsize); -#endif - if (nptr == NULL) { - wwarning("realloc() failed. Retrying after 2s."); - sleep(2); -#ifdef USE_BOEHM_GC - nptr = GC_REALLOC(ptr, newsize); -#else - nptr = realloc(ptr, newsize); -#endif - if (nptr == NULL) { - if (Aborting) { - fputs("Really Bad Error: recursive realloc() failure.", stderr); - exit(-1); - } else { - wfatal("virtual memory exhausted"); - Aborting = 1; - wAbort(False); - } - } - } - } - return nptr; -} - -void *wretain(void *ptr) -{ - int *refcount; - - if (!table) { - table = WMCreateHashTable(WMIntHashCallbacks); - } - - refcount = WMHashGet(table, ptr); - if (!refcount) { - refcount = wmalloc(sizeof(int)); - *refcount = 1; - WMHashInsert(table, ptr, refcount); -#ifdef VERBOSE - printf("== %i (%p)\n", *refcount, ptr); -#endif - } else { - (*refcount)++; -#ifdef VERBOSE - printf("+ %i (%p)\n", *refcount, ptr); -#endif - } - - return ptr; -} - -void wfree(void *ptr) -{ - if (ptr) -#ifdef USE_BOEHM_GC - /* This should eventually be removed, once the criss-cross - * of wmalloc()d memory being free()d, malloc()d memory being - * wfree()d, various misuses of calling wfree() on objects - * allocated by libc malloc() and calling libc free() on - * objects allocated by Boehm GC (think external libraries) - * is cleaned up. - */ - if (GC_base(ptr) != 0) - GC_FREE(ptr); - else - free(ptr); -#else - free(ptr); -#endif - ptr = NULL; -} - -void wrelease(void *ptr) -{ - int *refcount; - - refcount = WMHashGet(table, ptr); - if (!refcount) { - wwarning("trying to release unexisting data %p", ptr); - } else { - (*refcount)--; - if (*refcount < 1) { -#ifdef VERBOSE - printf("RELEASING %p\n", ptr); -#endif - WMHashRemove(table, ptr); - wfree(refcount); - wfree(ptr); - } -#ifdef VERBOSE - else { - printf("- %i (%p)\n", *refcount, ptr); - } -#endif - } -} diff --git a/WINGs/widgets.c b/WINGs/widgets.c index 95e4fd77..ec501254 100644 --- a/WINGs/widgets.c +++ b/WINGs/widgets.c @@ -610,10 +610,9 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c assert(W_ApplicationInitialized()); } - scrPtr = malloc(sizeof(W_Screen)); + scrPtr = wmalloc(sizeof(W_Screen)); if (!scrPtr) return NULL; - memset(scrPtr, 0, sizeof(W_Screen)); scrPtr->aflags.hasAppIcon = 1; diff --git a/WPrefs.app/Docks.c b/WPrefs.app/Docks.c index c83332c8..3a796dca 100644 --- a/WPrefs.app/Docks.c +++ b/WPrefs.app/Docks.c @@ -129,7 +129,7 @@ static void autoDelayChanged(void *observerData, WMNotification *notification) } char *value = WMGetTextFieldText(anAutoDelayT); adjustButtonSelectionBasedOnValue(panel, row, value); - free(value); + wfree(value); return; } } diff --git a/WPrefs.app/Focus.c b/WPrefs.app/Focus.c index 04198751..476a0898 100644 --- a/WPrefs.app/Focus.c +++ b/WPrefs.app/Focus.c @@ -138,7 +138,7 @@ static void storeData(_Panel * panel) if (sscanf(str, "%i", &i) != 1) i = 0; SetIntegerForKey(i, "RaiseDelay"); - free(str); + wfree(str); SetBoolForKey(WMGetButtonSelected(panel->ignB), "IgnoreFocusClick"); SetBoolForKey(WMGetButtonSelected(panel->newB), "AutoFocus"); diff --git a/WPrefs.app/HotCornerShortcuts.c b/WPrefs.app/HotCornerShortcuts.c index d3259d80..e19487ad 100644 --- a/WPrefs.app/HotCornerShortcuts.c +++ b/WPrefs.app/HotCornerShortcuts.c @@ -147,7 +147,7 @@ static void storeData(_Panel * panel) if (sscanf(str, "%i", &i) != 1) i = 0; SetIntegerForKey(i, "HotCornerDelay"); - free(str); + wfree(str); SetIntegerForKey(WMGetSliderValue(panel->hceS), "HotCornerEdge"); diff --git a/WPrefs.app/Makefile.am b/WPrefs.app/Makefile.am index 4408ff96..fa41466e 100644 --- a/WPrefs.app/Makefile.am +++ b/WPrefs.app/Makefile.am @@ -68,6 +68,7 @@ WPrefs_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la WPrefs_LDADD = \ $(top_builddir)/WINGs/libWINGs.la\ $(top_builddir)/WINGs/libWUtil.la\ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ $(top_builddir)/wrlib/libwraster.la \ @XLFLAGS@ @XLIBS@ \ @LIBM@ \ diff --git a/src/appmenu.c b/src/appmenu.c index bc708964..11191641 100644 --- a/src/appmenu.c +++ b/src/appmenu.c @@ -139,7 +139,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun } wstrlcpy(title, &slist[*index][pos], sizeof(title)); } - data = malloc(sizeof(WAppMenuData)); + data = wmalloc(sizeof(WAppMenuData)); if (data == NULL) { wwarning(_("appmenu: out of memory creating menu for window %lx"), win); wMenuDestroy(menu, True); @@ -152,7 +152,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun if (!entry) { wMenuDestroy(menu, True); wwarning(_("appmenu: out of memory creating menu for window %lx"), win); - free(data); + wfree(data); return NULL; } if (rtext[0] != 0) diff --git a/src/dialog.c b/src/dialog.c index 391dda23..235d1118 100644 --- a/src/dialog.c +++ b/src/dialog.c @@ -39,10 +39,6 @@ #include #include -#ifdef HAVE_MALLOC_H -#include -#endif - #include #ifdef __FreeBSD__ #include @@ -1361,7 +1357,7 @@ void wShowInfoPanel(WScreen *scr) char *posn = getPrettyOSName(); if (posn) { snprintf(buffer, sizeof(buffer), _("Running on: %s (%s)\n"), posn, uts.machine); - free(posn); + wfree(posn); } else snprintf(buffer, sizeof(buffer), _("Running on: %s (%s)\n"), uts.sysname, uts.machine); @@ -1393,31 +1389,6 @@ void wShowInfoPanel(WScreen *scr) break; } -#if defined(HAVE_MALLOC_H) && defined(HAVE_MALLINFO2) - { - struct mallinfo2 ma = mallinfo2(); - snprintf(buffer, sizeof(buffer), -#ifdef DEBUG - _("Total memory allocated: %lu kB (in use: %lu kB, %lu free chunks)\n"), -#else - _("Total memory allocated: %lu kB (in use: %lu kB)\n"), -#endif - (ma.arena + ma.hblkhd) / 1024, - (ma.uordblks + ma.hblkhd) / 1024 -#ifdef DEBUG - /* - * This information is representative of the memory - * fragmentation. In ideal case it should be 1, but - * that is never possible - */ - , ma.ordblks -#endif - ); - - strbuf = wstrappend(strbuf, buffer); - } -#endif - strbuf = wstrappend(strbuf, _("Image formats: ")); strl = RSupportedFileFormats(); separator = NULL; diff --git a/src/dock.c b/src/dock.c index d59cf35c..39594aa2 100644 --- a/src/dock.c +++ b/src/dock.c @@ -3194,7 +3194,7 @@ static pid_t execCommand(WAppIcon *btn, const char *command, WSavedState *state) setsid(); #endif - args = malloc(sizeof(char *) * (argc + 1)); + args = wmalloc(sizeof(char *) * (argc + 1)); if (!args) exit(111); @@ -3338,8 +3338,8 @@ void wDockTrackWindowLaunch(WDock *dock, Window window) char *command = NULL; if (!PropGetWMClass(window, &wm_class, &wm_instance)) { - free(wm_class); - free(wm_instance); + wfree(wm_class); + wfree(wm_instance); return; } @@ -3419,10 +3419,8 @@ void wDockTrackWindowLaunch(WDock *dock, Window window) if (command) wfree(command); - if (wm_class) - free(wm_class); - if (wm_instance) - free(wm_instance); + wfree(wm_class); + wfree(wm_instance); } void wClipUpdateForWorkspaceChange(WScreen *scr, int workspace) diff --git a/src/event.c b/src/event.c index 7356a1a2..c9107f84 100644 --- a/src/event.c +++ b/src/event.c @@ -141,7 +141,7 @@ WMagicNumber wAddDeathHandler(pid_t pid, WDeathHandler * callback, void *cdata) { DeathHandler *handler; - handler = malloc(sizeof(DeathHandler)); + handler = wmalloc(sizeof(DeathHandler)); if (!handler) return 0; @@ -150,7 +150,7 @@ WMagicNumber wAddDeathHandler(pid_t pid, WDeathHandler * callback, void *cdata) handler->client_data = cdata; if (!deathHandlers) - deathHandlers = WMCreateArrayWithDestructor(8, free); + deathHandlers = WMCreateArrayWithDestructor(8, wfree); WMAddToArray(deathHandlers, handler); @@ -164,7 +164,7 @@ static void wdelete_death_handler(WMagicNumber id) if (!handler || !deathHandlers) return; - /* array destructor will call free(handler) */ + /* array destructor will call wfree(handler) */ WMRemoveFromArray(deathHandlers, handler); } diff --git a/src/geomview.c b/src/geomview.c index 3564ffac..64ec5835 100644 --- a/src/geomview.c +++ b/src/geomview.c @@ -40,7 +40,7 @@ WGeometryView *WCreateGeometryView(WMScreen * scr) widgetClass = W_RegisterUserWidget(); } - gview = malloc(sizeof(WGeometryView)); + gview = wmalloc(sizeof(WGeometryView)); if (!gview) { return NULL; } diff --git a/src/main.c b/src/main.c index 0f2f9e00..eaba9f94 100644 --- a/src/main.c +++ b/src/main.c @@ -131,7 +131,7 @@ static void setWVisualID(int screen, int val) /* no array at all, alloc space for screen + 1 entries * and init with default value */ wVisualID_len = screen + 1; - wVisualID = (int *)malloc(wVisualID_len * sizeof(int)); + wVisualID = (int *)wmalloc(wVisualID_len * sizeof(int)); for (i = 0; i < wVisualID_len; i++) { wVisualID[i] = -1; } @@ -156,7 +156,7 @@ static void setWVisualID(int screen, int val) */ static int initWVisualID(const char *user_str) { - char *mystr = strdup(user_str); + char *mystr = wstrdup(user_str); int cur_in_pos = 0; int cur_out_pos = 0; int cur_screen = 0; @@ -191,7 +191,7 @@ static int initWVisualID(const char *user_str) cur_in_pos++; } - free(mystr); + wfree(mystr); if (cur_screen == 0||error_found != 0) return 1; @@ -383,7 +383,7 @@ Bool RelaunchWindow(WWindow *wwin) setsid(); #endif /* argv is not null-terminated */ - char **a = (char **) malloc(argc + 1); + char **a = (char **) wmalloc(argc + 1); if (! a) { werror("out of memory trying to relaunch the application"); Exit(-1); diff --git a/src/misc.c b/src/misc.c index 4cab3584..123def2b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -520,7 +520,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) len = strlen(cmdline); olen = len + 1; - out = malloc(olen); + out = wmalloc(olen); if (!out) { wwarning(_("out of memory during expansion of \"%s\""), cmdline); return NULL; @@ -573,7 +573,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) (unsigned int)scr->focused_window->client_win); slen = strlen(tmpbuf); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%w", cmdline); goto error; @@ -590,7 +590,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) snprintf(tmpbuf, sizeof(tmpbuf), "0x%x", (unsigned int)scr->current_workspace + 1); slen = strlen(tmpbuf); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%W", cmdline); goto error; @@ -607,7 +607,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) if (user_input) { slen = strlen(user_input); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%a", cmdline); goto error; @@ -630,7 +630,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) } slen = strlen(scr->xdestring); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%d", cmdline); goto error; @@ -651,7 +651,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) } slen = strlen(selection); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%s", cmdline); goto error; diff --git a/src/properties.c b/src/properties.c index 6751306c..b42b1646 100644 --- a/src/properties.c +++ b/src/properties.c @@ -54,13 +54,13 @@ int PropGetWMClass(Window window, char **wm_class, char **wm_instance) class_hint = XAllocClassHint(); if (XGetClassHint(dpy, window, class_hint) == 0) { - *wm_class = strdup("default"); - *wm_instance = strdup("default"); + *wm_class = wstrdup("default"); + *wm_instance = wstrdup("default"); XFree(class_hint); return False; } - *wm_instance = strdup(class_hint->res_name); - *wm_class = strdup(class_hint->res_class); + *wm_instance = wstrdup(class_hint->res_name); + *wm_class = wstrdup(class_hint->res_class); XFree(class_hint->res_name); XFree(class_hint->res_class); @@ -133,7 +133,7 @@ int PropGetGNUstepWMAttr(Window window, GNUstepWMAttributes ** attr) if (!data) return False; - *attr = malloc(sizeof(GNUstepWMAttributes)); + *attr = wmalloc(sizeof(GNUstepWMAttributes)); if (!*attr) { XFree(data); return False; @@ -183,7 +183,7 @@ void PropSetIconTileHint(WScreen * scr, RImage * image) imageAtom = XInternAtom(dpy, "_RGBA_IMAGE", False); } - tmp = malloc(image->width * image->height * 4 + 4); + tmp = wmalloc(image->width * image->height * 4 + 4); if (!tmp) { wwarning("could not allocate memory to set _WINDOWMAKER_ICON_TILE hint"); return; diff --git a/src/rootmenu.c b/src/rootmenu.c index 213c30e8..c7931580 100644 --- a/src/rootmenu.c +++ b/src/rootmenu.c @@ -1243,7 +1243,7 @@ static WMenu *readMenuDirectory(WScreen *scr, const char *title, char **path, co if (dentry->d_name[0] == '.') continue; - buffer = malloc(strlen(path[i]) + strlen(dentry->d_name) + 4); + buffer = wmalloc(strlen(path[i]) + strlen(dentry->d_name) + 4); if (!buffer) { werror(_("out of memory while constructing directory menu %s"), path[i]); break; @@ -1288,7 +1288,7 @@ static WMenu *readMenuDirectory(WScreen *scr, const char *title, char **path, co } } } - free(buffer); + wfree(buffer); } closedir(dir); @@ -1315,7 +1315,7 @@ static WMenu *readMenuDirectory(WScreen *scr, const char *title, char **path, co length += 7; if (command) length += strlen(command) + 6; - buffer = malloc(length); + buffer = wmalloc(length); if (!buffer) { werror(_("out of memory while constructing directory menu %s"), path[data->index]); break; @@ -1353,7 +1353,7 @@ static WMenu *readMenuDirectory(WScreen *scr, const char *title, char **path, co if (command) length += strlen(command); - buffer = malloc(length); + buffer = wmalloc(length); if (!buffer) { werror(_("out of memory while constructing directory menu %s"), path[data->index]); break; diff --git a/src/session.c b/src/session.c index c01eba2f..5826e028 100644 --- a/src/session.c +++ b/src/session.c @@ -289,9 +289,9 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp) } if (instance) - free(instance); + wfree(instance); if (class) - free(class); + wfree(class); if (command) wfree(command); @@ -382,7 +382,7 @@ static pid_t execCommand(WScreen *scr, char *command) SetupEnvironment(scr); - args = malloc(sizeof(char *) * (argc + 1)); + args = wmalloc(sizeof(char *) * (argc + 1)); if (!args) exit(111); for (i = 0; i < argc; i++) { diff --git a/src/superfluous.c b/src/superfluous.c index c0e40be9..65d4d843 100644 --- a/src/superfluous.c +++ b/src/superfluous.c @@ -244,7 +244,7 @@ reinit: wApplicationSetBouncing(data->wapp, 0); WMDeleteTimerHandler(data->timer); wApplicationDestroy(data->wapp); - free(data); + wfree(data); } static int bounceDirection(WAppIcon *aicon) @@ -324,7 +324,7 @@ void wAppBounce(WApplication *wapp) wApplicationIncrementRefcount(wapp); wApplicationSetBouncing(wapp, 1); - AppBouncerData *data = (AppBouncerData *)malloc(sizeof(AppBouncerData)); + AppBouncerData *data = (AppBouncerData *)wmalloc(sizeof(AppBouncerData)); data->wapp = wapp; data->count = data->pow = 0; data->dir = bounceDirection(wApplicationGetAppIcon(wapp)); diff --git a/src/switchpanel.c b/src/switchpanel.c index cb6cda10..ea3aa105 100644 --- a/src/switchpanel.c +++ b/src/switchpanel.c @@ -357,8 +357,7 @@ static void drawTitle(WSwitchPanel *panel, int idecks, const char *title) WMSetLabelText(panel->label, ntitle); } - if (ntitle) - free(ntitle); + wfree(ntitle); } static WMArray *makeWindowListArray(WScreen *scr, int include_unmapped, Bool class_only) diff --git a/src/window.c b/src/window.c index 9e0622df..67bd5434 100644 --- a/src/window.c +++ b/src/window.c @@ -950,10 +950,10 @@ WWindow *wManageWindow(WScreen *scr, Window window) } if (instance) - free(instance); + wfree(instance); if (class) - free(class); + wfree(class); #undef ADEQUATE } @@ -2602,7 +2602,7 @@ void wWindowSetShape(WWindow * wwin) if (!rects) goto alt_code; - urec = malloc(sizeof(XRectangle) * (count + 2)); + urec = wmalloc(sizeof(XRectangle) * (count + 2)); if (!urec) { XFree(rects); goto alt_code; @@ -2779,7 +2779,7 @@ WMagicNumber wWindowAddSavedState(const char *instance, const char *class, { WWindowState *wstate; - wstate = malloc(sizeof(WWindowState)); + wstate = wmalloc(sizeof(WWindowState)); if (!wstate) return NULL; @@ -2841,9 +2841,9 @@ WMagicNumber wWindowGetSavedState(Window win) if (command) wfree(command); if (instance) - free(instance); + wfree(instance); if (class) - free(class); + wfree(class); return wstate; } diff --git a/src/xdnd.c b/src/xdnd.c index 54c0df55..42cf4ee9 100644 --- a/src/xdnd.c +++ b/src/xdnd.c @@ -256,7 +256,7 @@ static void wXDNDGetTypeList(Display *dpy, Window window) return; } - typelist = malloc((count + 1) * sizeof(Atom)); + typelist = wmalloc((count + 1) * sizeof(Atom)); a = (Atom *) data; for (i = 0; i < count; i++) { typelist[i] = a[i]; @@ -267,7 +267,7 @@ static void wXDNDGetTypeList(Display *dpy, Window window) } typelist[count] = 0; XFree(data); - free(typelist); + wfree(typelist); } Bool wXDNDProcessClientMessage(XClientMessageEvent *event) diff --git a/util/Makefile.am b/util/Makefile.am index 0c148702..9dbd997e 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -18,52 +18,70 @@ AM_CPPFLAGS = \ liblist= @LIBRARY_SEARCH_PATH@ @INTLIBS@ -wdwrite_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +wdwrite_LDADD = $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ + $(liblist) -wdread_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +wdread_LDADD = $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ + $(liblist) -wxcopy_LDADD = @XLFLAGS@ @XLIBS@ +wxcopy_LDADD = @XLFLAGS@ @XLIBS@ \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a wxpaste_LDADD = @XLFLAGS@ @XLIBS@ -getstyle_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +getstyle_LDADD = $(top_builddir)/WINGs/libWUtil.la\ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ + $(liblist) getstyle_SOURCES = getstyle.c fontconv.c common.h setstyle_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ @XLFLAGS@ @XLIBS@ $(liblist) setstyle_SOURCES = setstyle.c fontconv.c common.h -convertfonts_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +convertfonts_LDADD = $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ + $(liblist) convertfonts_SOURCES = convertfonts.c fontconv.c common.h -seticons_LDADD= $(top_builddir)/WINGs/libWUtil.la $(liblist) +seticons_LDADD= $(top_builddir)/WINGs/libWUtil.la\ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ + $(liblist) -geticonset_LDADD= $(top_builddir)/WINGs/libWUtil.la $(liblist) +geticonset_LDADD= $(top_builddir)/WINGs/libWUtil.la\ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ + $(liblist) wmagnify_LDADD = \ $(top_builddir)/WINGs/libWINGs.la \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ $(top_builddir)/wrlib/libwraster.la \ @XLFLAGS@ @XLIBS@ @INTLIBS@ wmsetbg_LDADD = \ $(top_builddir)/WINGs/libWINGs.la \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ $(top_builddir)/wrlib/libwraster.la \ @XLFLAGS@ @LIBXINERAMA@ @XLIBS@ @INTLIBS@ wmgenmenu_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ @INTLIBS@ wmgenmenu_SOURCES = wmgenmenu.c wmgenmenu.h wmmenugen_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ @INTLIBS@ wmmenugen_SOURCES = wmmenugen.c wmmenugen.h wmmenugen_misc.c \ @@ -75,6 +93,7 @@ wmiv_CFLAGS = @PANGO_CFLAGS@ @PTHREAD_CFLAGS@ wmiv_LDADD = \ $(top_builddir)/wrlib/libwraster.la \ $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ @XLFLAGS@ @XLIBS@ @GFXLIBS@ \ @PANGO_LIBS@ @PTHREAD_LIBS@ @LIBEXIF@ diff --git a/util/getstyle.c b/util/getstyle.c index a2935892..283758b2 100644 --- a/util/getstyle.c +++ b/util/getstyle.c @@ -242,7 +242,7 @@ static void makeThemePack(WMPropList * style, const char *themeName) WMDeleteFromPLArray(value, 1); WMInsertInPLArray(value, 1, WMCreatePLString(newPath)); - free(newPath); + wfree(newPath); } else { findCopyFile(themeDir, WMGetFromPLString(file)); } @@ -262,7 +262,7 @@ static void makeThemePack(WMPropList * style, const char *themeName) WMDeleteFromPLArray(value, 1); WMInsertInPLArray(value, 1, WMCreatePLString(newPath)); - free(newPath); + wfree(newPath); } else { findCopyFile(themeDir, WMGetFromPLString(file)); } @@ -277,7 +277,7 @@ static void makeThemePack(WMPropList * style, const char *themeName) WMDeleteFromPLArray(value, 2); WMInsertInPLArray(value, 2, WMCreatePLString(newPath)); - free(newPath); + wfree(newPath); } else { findCopyFile(themeDir, WMGetFromPLString(file)); } diff --git a/util/wmiv.c b/util/wmiv.c index 0d33a535..925d3afa 100755 --- a/util/wmiv.c +++ b/util/wmiv.c @@ -202,7 +202,7 @@ int change_title(XTextProperty *prop, char *filename) XSetWMName(dpy, win, prop); if (prop->value) XFree(prop->value); - free(combined_title); + wfree(combined_title); return EXIT_SUCCESS; } @@ -596,8 +596,8 @@ int linked_list_add(linked_list_t *list, const void *data) { link_t *link; - /* calloc sets the "next" field to zero. */ - link = calloc(1, sizeof(link_t)); + /* wmalloc zeros the buffer it returns, so the "next" is zero. */ + link = wmalloc(sizeof(link_t)); if (!link) { fprintf(stderr, "Error: memory allocation failed\n"); return EXIT_FAILURE; @@ -627,8 +627,8 @@ void linked_list_free(linked_list_t *list) /* Store the next value so that we don't access freed memory. */ next = link->next; if (link->data) - free((char *)link->data); - free(link); + wfree((char *)link->data); + wfree(link); } } @@ -651,7 +651,7 @@ link_t *connect_dir(char *dirpath, linked_list_t *li) /* maybe it's a file */ struct stat stDirInfo; if (lstat(dirpath, &stDirInfo) == 0) { - linked_list_add(li, strdup(dirpath)); + linked_list_add(li, wstrdup(dirpath)); return li->first; } else { return NULL; @@ -664,11 +664,11 @@ link_t *connect_dir(char *dirpath, linked_list_t *li) else snprintf(path, PATH_MAX, "%s%c%s", dirpath, FILE_SEPARATOR, dir[idx]->d_name); - free(dir[idx]); + wfree(dir[idx]); if ((lstat(path, &stDirInfo) == 0) && !S_ISDIR(stDirInfo.st_mode)) - linked_list_add(li, strdup(path)); + linked_list_add(li, wstrdup(path)); } - free(dir); + wfree(dir); return li->first; } diff --git a/util/wmmenugen_misc.c b/util/wmmenugen_misc.c index 161a32e0..56f2c254 100644 --- a/util/wmmenugen_misc.c +++ b/util/wmmenugen_misc.c @@ -121,7 +121,7 @@ void parse_locale(const char *what, char **language, char **country, char **enco *language = wstrdup(e); out: - free(e); + wfree(e); return; } diff --git a/util/wmsetbg.c b/util/wmsetbg.c index dc747d04..ac9a51e8 100644 --- a/util/wmsetbg.c +++ b/util/wmsetbg.c @@ -398,7 +398,7 @@ static BackgroundTexture *parseTexture(RContext * rc, char *text) RGradientStyle gtype; int iwidth, iheight; - colors = malloc(sizeof(RColor *) * (count - 1)); + colors = wmalloc(sizeof(RColor *) * (count - 1)); if (!colors) { wwarning("out of memory while parsing texture"); goto error; @@ -425,7 +425,7 @@ static BackgroundTexture *parseTexture(RContext * rc, char *text) wfree(colors); goto error; } - if (!(colors[i - 2] = malloc(sizeof(RColor)))) { + if (!(colors[i - 2] = wmalloc(sizeof(RColor)))) { wwarning("out of memory while parsing texture"); for (j = 0; colors[j] != NULL; j++) diff --git a/util/wxcopy.c b/util/wxcopy.c index 82b4c7f2..6fd76592 100644 --- a/util/wxcopy.c +++ b/util/wxcopy.c @@ -26,6 +26,8 @@ #include #include +#include + #include "../src/wconfig.h" #define LINESIZE (4*1024) @@ -197,7 +199,7 @@ int main(int argc, char **argv) break; } if (buf_len == 0) { - nbuf = malloc(buf_len = l + nl + 1); + nbuf = wmalloc(buf_len = l + nl + 1); } else if (buf_len < l + nl + 1) { /* * To avoid terrible performance on big input buffers, @@ -205,12 +207,7 @@ int main(int argc, char **argv) * current line. */ buf_len = 2 * buf_len + nl + 1; - /* some realloc implementations don't do malloc if buf==NULL */ - if (buf == NULL) { - nbuf = malloc(buf_len); - } else { - nbuf = realloc(buf, buf_len); - } + nbuf = wrealloc(buf, buf_len); } else { nbuf = buf; } diff --git a/wmaker-rs/src/lib.rs b/wmaker-rs/src/lib.rs index f9dd99f1..a759879e 100644 --- a/wmaker-rs/src/lib.rs +++ b/wmaker-rs/src/lib.rs @@ -1,10 +1,11 @@ -pub mod application; pub mod app_icon; pub mod app_menu; +pub mod application; pub mod defaults; pub mod dock; pub mod global; pub mod icon; +pub mod memory; pub mod menu; pub mod properties; pub mod screen; diff --git a/wmaker-rs/src/memory.rs b/wmaker-rs/src/memory.rs new file mode 100644 index 00000000..ad5b1236 --- /dev/null +++ b/wmaker-rs/src/memory.rs @@ -0,0 +1,180 @@ +//! Provides an FFI-compatible allocator for C. +//! +//! This replaces the bulk of the code form WINGs/memory.c, but +//! it should go away once we're in a place that we don't need +//! to rely on `malloc` anymore in C code. + +use std::alloc::{Layout, alloc_zeroed, dealloc, realloc}; +use std::collections::BTreeMap; +use std::ffi::c_void; +use std::sync::Mutex; + +#[derive(Clone, Copy, Debug)] +struct Allocation { + layout: Layout, + rc: u32, +} + +type WMallocMap = BTreeMap; + +static ALLOCS: Mutex = Mutex::new(WMallocMap::new()); +const ALIGN: usize = 64; + +/// A wrapper around the Rust allocator API's `alloc_zeroed` +/// function for FFI. +/// +/// Note: This will always return nil on a zero-sized +/// allocation. Don't try to use this to allocate Rust ZSTs; +/// it is only suitable for FFI. +/// +/// # Safety +/// This will return a 64-byte aligned pointer of the specified +/// length, or nil if the allocation cannot be satisfied. It is +/// the caller's responsibility to ensure that the result is not +/// null, to avoid violating memory safety, and to make sure +/// that any alignment for any value the pointer is used to +/// refer to is suitably aligned. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wmalloc(size: usize) -> *mut c_void { + if size == 0 { + return std::ptr::null_mut(); + } + let layout = Layout::from_size_align(size, ALIGN).expect("layout makes sense"); + let ptr = unsafe { alloc_zeroed(layout) }; + if ptr.is_null() { + return std::ptr::null_mut(); + } + let rc = 1; + let alloc = Allocation { layout, rc }; + let mut allocs = ALLOCS.lock().expect("lock not poisoned"); + allocs.insert(ptr.addr(), alloc); + ptr.cast() +} + +/// A wrapper around the Rust allocator API's `dealloc` function +/// for FFI. +/// +/// If the pointer argument is nil, this is a nop. Otherwise, +/// it must have been allocated by `wmalloc` or `wrealloc`. +/// +/// # Safety +/// The caller must ensure that the pointer that is passed to +/// this function was allocated by `wmalloc`. Note that this +/// will not free unless this was the allocation's last +/// reference. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wfree(ptr: *mut c_void) { + if ptr.is_null() { + return; + } + let alloc = { + let mut allocs = ALLOCS.lock().expect("wfree: allocs is unpoisoned"); + let addr = ptr.addr(); + let alloc = allocs.get_mut(&addr).expect("wfree: allocs records ptr"); + alloc.rc -= 1; + let alloc = alloc.clone(); + if alloc.rc == 0 { + allocs.remove(&addr).expect("wfree: allocs unaltered"); + } + alloc + }; + // We can call `dealloc` without holding the ALLOCS lock. + if alloc.rc == 0 { + unsafe { + dealloc(ptr.cast(), alloc.layout); + } + } +} + +/// A wrapper around the Rust allocator API's `realloc` function +/// for FFI calls. +/// +/// Note that this attempts will always free and return NULL on +/// a reallocation of size 0, so do not try to use this to +/// allocate Rust ZSTs: it is purely for FFI. If the source +/// pointer is nil and size is positive, then the behavior is +/// that of `wmalloc`. +/// +/// The pointer argument just have been allocated previously +/// with `wmalloc` or `wrealloc`. +/// +/// If successful, returns a non-nil pointer, and the old +/// pointer should be considered invalid, and must not be +/// dereferenced. If the old size was smaller than the new +/// size, elements, elements after the old size are zeroed. +/// Elements between the start of the array and the minimum of +/// the old and new sizes are unchanged. +/// +/// On failure, returns nil, and does not free the source +/// pointer or otherwise alter its contents. +/// +/// # Safety +/// This will return a 64-byte aligned pointer of the specified +/// length, or nil if the allocation cannot be satisfied. It is +/// the caller's responsibility to ensure that the result is not +/// null, to avoid violating memory safety, and to make sure +/// that any alignment for any value the pointer is used to +/// refer to is suitably aligned. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wrealloc(ptr: *mut c_void, size: usize) -> *mut c_void { + if size == 0 { + unsafe { + wfree(ptr); + } + return std::ptr::null_mut(); + } + if ptr.is_null() { + return unsafe { wmalloc(size) }; + } + let new_layout = Layout::from_size_align(size, ALIGN).expect("layout ok"); + let addr = ptr.addr(); + let old_layout = { + let allocs = ALLOCS.lock().expect("wrealloc: allocs is unpoisoned"); + let alloc = allocs.get(&addr).expect("wrealloc: allocs records ptr"); + alloc.layout + }; + let old_size = old_layout.size(); + let ptr = unsafe { realloc(ptr.cast(), old_layout, size) }; + if ptr.is_null() { + return std::ptr::null_mut(); + } + { + let mut allocs = ALLOCS.lock().expect("wrealloc: allocs still unpoisoned"); + let alloc = allocs.get_mut(&addr).expect("wmrealloc: allocs knows ptr"); + alloc.layout = new_layout; + } + if old_size < size { + let zlen = size - old_size; + let zptr = ptr.wrapping_add(old_size); + unsafe { + std::ptr::write_bytes(zptr, 0, zlen); + } + } + ptr.cast() +} + +/// Increments the reference count on `ptr`. +/// +/// # Safety +/// The caller must insure that `ptr` refers to a valid, active +/// allocation that was made with `wmalloc` or `wrealloc`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wretain(ptr: *mut c_void) -> *mut c_void { + let addr = ptr.addr(); + let mut allocs = ALLOCS.lock().expect("wretain: allocs is unpoisoned"); + let alloc = allocs.get_mut(&addr).expect("wretain: allocs records ptr"); + alloc.rc += 1; + ptr +} + +/// Decrements the reference count on `ptr`, and frees if it +/// reaches 0, but wrapping `wfree`. +/// +/// # Safety +/// `ptr` must be valid as an argument to `wfree`. +#[unsafe(no_mangle)] +pub unsafe extern "C" fn wrelease(ptr: *mut c_void) { + unsafe { + wfree(ptr); + } +} diff --git a/wmlib/Makefile.am b/wmlib/Makefile.am index 8b87fdf9..33cd075c 100644 --- a/wmlib/Makefile.am +++ b/wmlib/Makefile.am @@ -6,7 +6,8 @@ lib_LTLIBRARIES = libWMaker.la include_HEADERS = WMaker.h -AM_CPPFLAGS = $(DFLAGS) @XCFLAGS@ +AM_CPPFLAGS = $(DFLAGS) @XCFLAGS@ \ + -I$(top_srcdir)/WINGs -I$(top_builddir)/WINGs libWMaker_la_LIBADD = @XLFLAGS@ @XLIBS@ diff --git a/wmlib/app.c b/wmlib/app.c index 9cd27c8c..9a3fdf3d 100644 --- a/wmlib/app.c +++ b/wmlib/app.c @@ -24,6 +24,8 @@ #include #include +#include + #include "WMaker.h" #include "app.h" @@ -31,7 +33,7 @@ WMAppContext *WMAppCreateWithMain(Display * display, int screen_number, Window m { wmAppContext *ctx; - ctx = malloc(sizeof(wmAppContext)); + ctx = wmalloc(sizeof(wmAppContext)); if (!ctx) return NULL; @@ -39,9 +41,9 @@ WMAppContext *WMAppCreateWithMain(Display * display, int screen_number, Window m ctx->screen_number = screen_number; ctx->our_leader_hint = False; ctx->main_window = main_window; - ctx->windows = malloc(sizeof(Window)); + ctx->windows = wmalloc(sizeof(Window)); if (!ctx->windows) { - free(ctx); + wfree(ctx); return NULL; } ctx->win_count = 1; @@ -58,13 +60,13 @@ int WMAppAddWindow(WMAppContext * app, Window window) { Window *win; - win = malloc(sizeof(Window) * (app->win_count + 1)); + win = wmalloc(sizeof(Window) * (app->win_count + 1)); if (!win) return False; memcpy(win, app->windows, sizeof(Window) * app->win_count); - free(app->windows); + wfree(app->windows); win[app->win_count] = window; app->windows = win; diff --git a/wmlib/menu.c b/wmlib/menu.c index 6646cd70..a3a1498d 100644 --- a/wmlib/menu.c +++ b/wmlib/menu.c @@ -26,6 +26,8 @@ #include #include +#include + #include "WMaker.h" #include "app.h" #include "menu.h" @@ -37,7 +39,7 @@ WMMenu *WMMenuCreate(WMAppContext * app, char *title) if (strlen(title) > 255) return NULL; - menu = malloc(sizeof(wmMenu)); + menu = wmalloc(sizeof(wmMenu)); if (!menu) return NULL; @@ -50,12 +52,12 @@ WMMenu *WMMenuCreate(WMAppContext * app, char *title) menu->realized = False; menu->code = app->last_menu_tag++; - menu->entryline = malloc(strlen(title) + 32); - menu->entryline2 = malloc(32); + menu->entryline = wmalloc(strlen(title) + 32); + menu->entryline2 = wmalloc(32); if (!menu->entryline || !menu->entryline2) { if (menu->entryline) - free(menu->entryline); - free(menu); + wfree(menu->entryline); + wfree(menu); return NULL; } sprintf(menu->entryline, "%i %i %s", wmBeginMenu, menu->code, title); @@ -77,13 +79,13 @@ WMMenuAddItem(WMMenu * menu, char *text, WMMenuAction action, if (strlen(text) > 255) return -1; - entry = malloc(sizeof(wmMenuEntry)); + entry = wmalloc(sizeof(wmMenuEntry)); if (!entry) return -1; - entry->entryline = malloc(strlen(text) + 100); + entry->entryline = wmalloc(strlen(text) + 100); if (!entry->entryline) { - free(entry); + wfree(entry); return -1; } @@ -125,13 +127,13 @@ int WMMenuAddSubmenu(WMMenu * menu, char *text, WMMenu * submenu) if (strlen(text) > 255) return -1; - entry = malloc(sizeof(wmMenuEntry)); + entry = wmalloc(sizeof(wmMenuEntry)); if (!entry) return -1; - entry->entryline = malloc(strlen(text) + 100); + entry->entryline = wmalloc(strlen(text) + 100); if (!entry->entryline) { - free(entry); + wfree(entry); return -1; } @@ -217,7 +219,7 @@ int WMRealizeMenus(WMAppContext * app) return True; count++; - slist = malloc(count * sizeof(char *)); + slist = wmalloc(count * sizeof(char *)); if (!slist) { return False; } @@ -227,10 +229,10 @@ int WMRealizeMenus(WMAppContext * app) addItems(slist, &i, app->main_menu); if (!XStringListToTextProperty(slist, i, &text_prop)) { - free(slist); + wfree(slist); return False; } - free(slist); + wfree(slist); XSetTextProperty(app->dpy, app->main_window, &text_prop, getatom(app->dpy)); XFree(text_prop.value); diff --git a/wrlib/Makefile.am b/wrlib/Makefile.am index a82bb9a0..5f88852a 100644 --- a/wrlib/Makefile.am +++ b/wrlib/Makefile.am @@ -82,7 +82,8 @@ libwraster_la_SOURCES += load_magick.c endif AM_CFLAGS = @MAGICKFLAGS@ -AM_CPPFLAGS = $(DFLAGS) @HEADER_SEARCH_PATH@ +AM_CPPFLAGS = $(DFLAGS) @HEADER_SEARCH_PATH@ \ + -I$(top_srcdir)/WINGs -I$(top_builddir)/WINGs libwraster_la_LIBADD = @LIBRARY_SEARCH_PATH@ @GFXLIBS@ @MAGICKLIBS@ @XLIBS@ @LIBXMU@ -lm diff --git a/wrlib/context.c b/wrlib/context.c index 73044506..0a993605 100644 --- a/wrlib/context.c +++ b/wrlib/context.c @@ -37,6 +37,8 @@ #include +#include + #include "wraster.h" #include "scale.h" #include "wr_i18n.h" @@ -104,17 +106,17 @@ static Bool allocateStandardPseudoColor(RContext * ctx, XStandardColormap * stdc return False; } - ctx->colors = malloc(sizeof(XColor) * ctx->ncolors); + ctx->colors = wmalloc(sizeof(XColor) * ctx->ncolors); if (!ctx->colors) { RErrorCode = RERR_NOMEMORY; return False; } - ctx->pixels = malloc(sizeof(unsigned long) * ctx->ncolors); + ctx->pixels = wmalloc(sizeof(unsigned long) * ctx->ncolors); if (!ctx->pixels) { - free(ctx->colors); + wfree(ctx->colors); ctx->colors = NULL; RErrorCode = RERR_NOMEMORY; @@ -246,15 +248,15 @@ static Bool allocatePseudoColor(RContext *ctx) assert(cpc >= 2 && ncolors <= (1 << ctx->depth)); - colors = malloc(sizeof(XColor) * ncolors); + colors = wmalloc(sizeof(XColor) * ncolors); if (!colors) { RErrorCode = RERR_NOMEMORY; return False; } - ctx->pixels = malloc(sizeof(unsigned long) * ncolors); + ctx->pixels = wmalloc(sizeof(unsigned long) * ncolors); if (!ctx->pixels) { - free(colors); + wfree(colors); RErrorCode = RERR_NOMEMORY; return False; } @@ -343,7 +345,7 @@ static XColor *allocateGrayScale(RContext * ctx) ctx->attribs->render_mode = RBestMatchRendering; } - colors = malloc(sizeof(XColor) * ncolors); + colors = wmalloc(sizeof(XColor) * ncolors); if (!colors) { RErrorCode = RERR_NOMEMORY; return False; @@ -526,7 +528,7 @@ RContext *RCreateContext(Display * dpy, int screen_number, const RContextAttribu RContext *context; XGCValues gcv; - context = malloc(sizeof(RContext)); + context = wmalloc(sizeof(RContext)); if (!context) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -537,9 +539,9 @@ RContext *RCreateContext(Display * dpy, int screen_number, const RContextAttribu context->screen_number = screen_number; - context->attribs = malloc(sizeof(RContextAttributes)); + context->attribs = wmalloc(sizeof(RContextAttributes)); if (!context->attribs) { - free(context); + wfree(context); RErrorCode = RERR_NOMEMORY; return NULL; } @@ -568,7 +570,7 @@ RContext *RCreateContext(Display * dpy, int screen_number, const RContextAttribu templ.visualid = context->attribs->visualid; vinfo = XGetVisualInfo(context->dpy, VisualIDMask | VisualScreenMask, &templ, &nret); if (!vinfo || nret == 0) { - free(context); + wfree(context); RErrorCode = RERR_BADVISUALID; return NULL; } @@ -615,13 +617,13 @@ RContext *RCreateContext(Display * dpy, int screen_number, const RContextAttribu if (context->vclass == PseudoColor || context->vclass == StaticColor) { if (!setupPseudoColorColormap(context)) { - free(context); + wfree(context); return NULL; } } else if (context->vclass == GrayScale || context->vclass == StaticGray) { context->colors = allocateGrayScale(context); if (!context->colors) { - free(context); + wfree(context); return NULL; } } else if (context->vclass == TrueColor) { @@ -668,9 +670,9 @@ void RDestroyContext(RContext *context) if ((context->attribs->flags & RC_VisualID) && !(context->attribs->flags & RC_DefaultVisual)) XDestroyWindow(context->dpy, context->drawable); - free(context->attribs); + wfree(context->attribs); } - free(context); + wfree(context); } } diff --git a/wrlib/convert.c b/wrlib/convert.c index 400234f0..0650fc71 100644 --- a/wrlib/convert.c +++ b/wrlib/convert.c @@ -33,13 +33,15 @@ #include #include +#include + #include "wraster.h" #include "convert.h" #include "xutil.h" #include "wr_i18n.h" -#define NFREE(n) if (n) free(n) +#define NFREE(n) wfree(n) #define HAS_ALPHA(I) ((I)->format == RRGBAFormat) @@ -70,7 +72,7 @@ static void release_conversion_table(void) RConversionTable *tmp_to_delete = tmp; tmp = tmp->next; - free(tmp_to_delete); + wfree(tmp_to_delete); } conversionTable = NULL; } @@ -83,7 +85,7 @@ static void release_std_conversion_table(void) RStdConversionTable *tmp_to_delete = tmp; tmp = tmp->next; - free(tmp_to_delete); + wfree(tmp_to_delete); } stdConversionTable = NULL; } @@ -108,7 +110,7 @@ static unsigned short *computeTable(unsigned short mask) if (tmp) return tmp->table; - tmp = (RConversionTable *) malloc(sizeof(RConversionTable)); + tmp = (RConversionTable *) wmalloc(sizeof(RConversionTable)); if (tmp == NULL) return NULL; @@ -135,7 +137,7 @@ static unsigned int *computeStdTable(unsigned int mult, unsigned int max) if (tmp) return tmp->table; - tmp = (RStdConversionTable *) malloc(sizeof(RStdConversionTable)); + tmp = (RStdConversionTable *) wmalloc(sizeof(RStdConversionTable)); if (tmp == NULL) return NULL; @@ -372,8 +374,8 @@ static RXImage *image2TrueColor(RContext * ctx, RImage * image) signed char *nerr; int ch = (HAS_ALPHA(image) ? 4 : 3); - err = malloc(ch * (image->width + 2)); - nerr = malloc(ch * (image->width + 2)); + err = wmalloc(ch * (image->width + 2)); + nerr = wmalloc(ch * (image->width + 2)); if (!err || !nerr) { NFREE(err); NFREE(nerr); @@ -387,8 +389,8 @@ static RXImage *image2TrueColor(RContext * ctx, RImage * image) convertTrueColor_generic(ximg, image, err, nerr, rtable, gtable, btable, dr, dg, db, roffs, goffs, boffs); - free(err); - free(nerr); + wfree(err); + wfree(nerr); } } @@ -540,8 +542,8 @@ static RXImage *image2PseudoColor(RContext * ctx, RImage * image) #ifdef WRLIB_DEBUG fprintf(stderr, "pseudo color dithering with %d colors per channel\n", cpc); #endif - err = malloc(4 * (image->width + 3)); - nerr = malloc(4 * (image->width + 3)); + err = wmalloc(4 * (image->width + 3)); + nerr = wmalloc(4 * (image->width + 3)); if (!err || !nerr) { NFREE(err); NFREE(nerr); @@ -555,8 +557,8 @@ static RXImage *image2PseudoColor(RContext * ctx, RImage * image) convertPseudoColor_to_8(ximg, image, err + 4, nerr + 4, rtable, gtable, btable, dr, dg, db, ctx->pixels, cpc); - free(err); - free(nerr); + wfree(err); + wfree(nerr); } return ximg; @@ -618,8 +620,8 @@ static RXImage *image2StandardPseudoColor(RContext * ctx, RImage * image) fprintf(stderr, "pseudo color dithering with %d colors per channel\n", ctx->attribs->colors_per_channel); #endif - err = (short *)malloc(3 * (image->width + 2) * sizeof(short)); - nerr = (short *)malloc(3 * (image->width + 2) * sizeof(short)); + err = (short *)wmalloc(3 * (image->width + 2) * sizeof(short)); + nerr = (short *)wmalloc(3 * (image->width + 2) * sizeof(short)); if (!err || !nerr) { NFREE(err); NFREE(nerr); @@ -707,8 +709,8 @@ static RXImage *image2StandardPseudoColor(RContext * ctx, RImage * image) ofs += ximg->image->bytes_per_line - image->width; } - free(err); - free(nerr); + wfree(err); + wfree(nerr); } ximg->image->data = (char *)data; @@ -773,8 +775,8 @@ static RXImage *image2GrayScale(RContext * ctx, RImage * image) #ifdef WRLIB_DEBUG fprintf(stderr, "grayscale dither with %d colors per channel\n", cpc); #endif - gerr = (short *)malloc((image->width + 2) * sizeof(short)); - ngerr = (short *)malloc((image->width + 2) * sizeof(short)); + gerr = (short *)wmalloc((image->width + 2) * sizeof(short)); + ngerr = (short *)wmalloc((image->width + 2) * sizeof(short)); if (!gerr || !ngerr) { NFREE(gerr); NFREE(ngerr); @@ -830,8 +832,8 @@ static RXImage *image2GrayScale(RContext * ctx, RImage * image) gerr = ngerr; ngerr = terr; } - free(gerr); - free(ngerr); + wfree(gerr); + wfree(ngerr); } ximg->image->data = (char *)data; diff --git a/wrlib/convolve.c b/wrlib/convolve.c index 8873a620..7d12fef4 100644 --- a/wrlib/convolve.c +++ b/wrlib/convolve.c @@ -26,6 +26,8 @@ #include #include +#include + #include "wraster.h" #include "wr_i18n.h" @@ -46,7 +48,7 @@ int RBlurImage(RImage * image) unsigned char *pptr = NULL, *tmpp; int ch = image->format == RRGBAFormat ? 4 : 3; - pptr = malloc(image->width * ch); + pptr = wmalloc(image->width * ch); if (!pptr) { RErrorCode = RERR_NOMEMORY; return False; @@ -138,7 +140,7 @@ int RBlurImage(RImage * image) } } - free(tmpp); + wfree(tmpp); return True; } diff --git a/wrlib/load.c b/wrlib/load.c index 1f94f0e4..f9907b66 100644 --- a/wrlib/load.c +++ b/wrlib/load.c @@ -34,6 +34,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -125,7 +127,7 @@ static void init_cache(void) RImageCacheMaxImage = IMAGE_CACHE_MAXIMUM_MAXPIXELS; if (RImageCacheSize > 0) { - RImageCache = malloc(sizeof(RCachedImage) * RImageCacheSize); + RImageCache = wmalloc(sizeof(RCachedImage) * RImageCacheSize); if (RImageCache == NULL) { fprintf(stderr, _("wrlib: out of memory for image cache\n")); return; @@ -142,10 +144,10 @@ void RReleaseCache(void) for (i = 0; i < RImageCacheSize; i++) { if (RImageCache[i].file) { RReleaseImage(RImageCache[i].image); - free(RImageCache[i].file); + wfree(RImageCache[i].file); } } - free(RImageCache); + wfree(RImageCache); RImageCache = NULL; RImageCacheSize = -1; } @@ -173,7 +175,7 @@ RImage *RLoadImage(RContext *context, const char *file, int index) return RCloneImage(RImageCache[i].image); } else { - free(RImageCache[i].file); + wfree(RImageCache[i].file); RImageCache[i].file = NULL; RReleaseImage(RImageCache[i].image); } @@ -254,8 +256,7 @@ RImage *RLoadImage(RContext *context, const char *file, int index) for (i = 0; i < RImageCacheSize; i++) { if (!RImageCache[i].file) { - RImageCache[i].file = malloc(strlen(file) + 1); - strcpy(RImageCache[i].file, file); + RImageCache[i].file = wstrdup(file); RImageCache[i].image = RCloneImage(image); RImageCache[i].last_modif = st.st_mtime; RImageCache[i].last_use = time(NULL); @@ -271,10 +272,9 @@ RImage *RLoadImage(RContext *context, const char *file, int index) /* if no slot available, dump least recently used one */ if (!done) { - free(RImageCache[oldest_idx].file); + wfree(RImageCache[oldest_idx].file); RReleaseImage(RImageCache[oldest_idx].image); - RImageCache[oldest_idx].file = malloc(strlen(file) + 1); - strcpy(RImageCache[oldest_idx].file, file); + RImageCache[oldest_idx].file = wstrdup(file); RImageCache[oldest_idx].image = RCloneImage(image); RImageCache[oldest_idx].last_modif = st.st_mtime; RImageCache[oldest_idx].last_use = time(NULL); diff --git a/wrlib/load_gif.c b/wrlib/load_gif.c index 5921735f..870eaf7c 100644 --- a/wrlib/load_gif.c +++ b/wrlib/load_gif.c @@ -28,6 +28,8 @@ #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -127,7 +129,7 @@ RImage *RLoadGIF(const char *file, int index) } } - buffer = malloc(width * sizeof(GifPixelType)); + buffer = wmalloc(width * sizeof(GifPixelType)); if (!buffer) { RErrorCode = RERR_NOMEMORY; goto bye; @@ -219,7 +221,7 @@ RImage *RLoadGIF(const char *file, int index) did_not_get_any_errors: if (buffer) - free(buffer); + wfree(buffer); if (gif) #if (USE_GIF == 5) && (GIFLIB_MINOR >= 1) diff --git a/wrlib/load_jpeg.c b/wrlib/load_jpeg.c index 9c2ef6be..e65fb2d2 100644 --- a/wrlib/load_jpeg.c +++ b/wrlib/load_jpeg.c @@ -35,6 +35,8 @@ #include #endif +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -119,7 +121,7 @@ static RImage *do_read_jpeg_file(struct jpeg_decompress_struct *cinfo, const cha goto abort_and_release_resources; } - buffer[0] = (JSAMPROW) malloc(cinfo->image_width * cinfo->num_components); + buffer[0] = (JSAMPROW) wmalloc(cinfo->image_width * cinfo->num_components); if (!buffer[0]) { RErrorCode = RERR_NOMEMORY; goto abort_and_release_resources; @@ -167,7 +169,7 @@ static RImage *do_read_jpeg_file(struct jpeg_decompress_struct *cinfo, const cha jpeg_destroy_decompress(cinfo); fclose(file); if (buffer[0]) - free(buffer[0]); + wfree(buffer[0]); return image; } diff --git a/wrlib/load_png.c b/wrlib/load_png.c index 50135b51..5eba4ed0 100644 --- a/wrlib/load_png.c +++ b/wrlib/load_png.c @@ -28,6 +28,8 @@ #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -165,7 +167,7 @@ RImage *RLoadPNG(RContext *context, const char *file) image->background.blue = bkcolor->blue >> 8; } - png_rows = calloc(height, sizeof(png_bytep)); + png_rows = wmalloc(height * sizeof(png_bytep)); if (!png_rows) { RErrorCode = RERR_NOMEMORY; fclose(f); @@ -174,7 +176,7 @@ RImage *RLoadPNG(RContext *context, const char *file) return NULL; } for (y = 0; y < height; y++) { - png_rows[y] = malloc(png_get_rowbytes(png, pinfo)); + png_rows[y] = wmalloc(png_get_rowbytes(png, pinfo)); if (!png_rows[y]) { RErrorCode = RERR_NOMEMORY; fclose(f); @@ -182,8 +184,8 @@ RImage *RLoadPNG(RContext *context, const char *file) png_destroy_read_struct(&png, &pinfo, &einfo); while (y-- > 0) if (png_rows[y]) - free(png_rows[y]); - free(png_rows); + wfree(png_rows[y]); + wfree(png_rows); return NULL; } } @@ -214,7 +216,7 @@ RImage *RLoadPNG(RContext *context, const char *file) } for (y = 0; y < height; y++) if (png_rows[y]) - free(png_rows[y]); - free(png_rows); + wfree(png_rows[y]); + wfree(png_rows); return image; } diff --git a/wrlib/load_ppm.c b/wrlib/load_ppm.c index 2f01c8ad..8e817589 100644 --- a/wrlib/load_ppm.c +++ b/wrlib/load_ppm.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -161,7 +163,7 @@ static RImage *load_graymap(FILE *file, int w, int h, int max, int raw, const ch if (raw == '5') { char *buf; - buf = malloc(w + 1); + buf = wmalloc(w + 1); if (!buf) { RErrorCode = RERR_NOMEMORY; RReleaseImage(image); @@ -169,7 +171,7 @@ static RImage *load_graymap(FILE *file, int w, int h, int max, int raw, const ch } for (y = 0; y < h; y++) { if (!fread(buf, w, 1, file)) { - free(buf); + wfree(buf); RErrorCode = RERR_BADIMAGEFILE; RReleaseImage(image); return NULL; @@ -181,7 +183,7 @@ static RImage *load_graymap(FILE *file, int w, int h, int max, int raw, const ch *(ptr++) = buf[x]; } } - free(buf); + wfree(buf); } } } diff --git a/wrlib/load_webp.c b/wrlib/load_webp.c index e1a4ee31..c703fb06 100644 --- a/wrlib/load_webp.c +++ b/wrlib/load_webp.c @@ -29,6 +29,8 @@ #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -110,7 +112,7 @@ RImage *RLoadWEBP(const char *file_name) return NULL; } - raw_data = (uint8_t *) malloc(raw_data_size); + raw_data = (uint8_t *) wmalloc(raw_data_size); if (!raw_data) { RErrorCode = RERR_NOMEMORY; @@ -124,7 +126,7 @@ RImage *RLoadWEBP(const char *file_name) if (r != raw_data_size) { RErrorCode = RERR_READ; - free(raw_data); + wfree(raw_data); return NULL; } @@ -133,7 +135,7 @@ RImage *RLoadWEBP(const char *file_name) fprintf(stderr, _("wrlib: could not get features from WebP file \"%s\", %s\n"), file_name, webp_message_from_status(status)); RErrorCode = RERR_BADIMAGEFILE; - free(raw_data); + wfree(raw_data); return NULL; } @@ -141,7 +143,7 @@ RImage *RLoadWEBP(const char *file_name) image = RCreateImage(features.width, features.height, True); if (!image) { RErrorCode = RERR_NOMEMORY; - free(raw_data); + wfree(raw_data); return NULL; } ret = WebPDecodeRGBAInto(raw_data, raw_data_size, image->data, @@ -151,7 +153,7 @@ RImage *RLoadWEBP(const char *file_name) image = RCreateImage(features.width, features.height, False); if (!image) { RErrorCode = RERR_NOMEMORY; - free(raw_data); + wfree(raw_data); return NULL; } ret = WebPDecodeRGBInto(raw_data, raw_data_size, image->data, @@ -159,7 +161,7 @@ RImage *RLoadWEBP(const char *file_name) features.width * 3); } - free(raw_data); + wfree(raw_data); if (!ret) { fprintf(stderr, _("wrlib: failed to decode WebP from file \"%s\"\n"), file_name); diff --git a/wrlib/load_xpm.c b/wrlib/load_xpm.c index 54697acf..d50e7c98 100644 --- a/wrlib/load_xpm.c +++ b/wrlib/load_xpm.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -59,11 +61,11 @@ static RImage *create_rimage_from_xpm(RContext *context, XpmImage xpm) /* make color table */ for (i = 0; i < 4; i++) { - color_table[i] = malloc(xpm.ncolors * sizeof(unsigned char)); + color_table[i] = wmalloc(xpm.ncolors * sizeof(unsigned char)); if (!color_table[i]) { for (i = i - 1; i >= 0; i--) { if (color_table[i]) - free(color_table[i]); + wfree(color_table[i]); } RReleaseImage(image); RErrorCode = RERR_NOMEMORY; @@ -123,7 +125,7 @@ static RImage *create_rimage_from_xpm(RContext *context, XpmImage xpm) *(data++) = color_table[3][*p]; } for (i = 0; i < 4; i++) - free(color_table[i]); + wfree(color_table[i]); return image; } diff --git a/wrlib/load_xpm_normalized.c b/wrlib/load_xpm_normalized.c index 82be3c4e..2bbb9176 100644 --- a/wrlib/load_xpm_normalized.c +++ b/wrlib/load_xpm_normalized.c @@ -59,15 +59,15 @@ static void free_color_symbol_table(unsigned char *color_table[], unsigned short *symbol_table) { if (color_table[0]) - free(color_table[0]); + wfree(color_table[0]); if (color_table[1]) - free(color_table[1]); + wfree(color_table[1]); if (color_table[2]) - free(color_table[2]); + wfree(color_table[2]); if (color_table[3]) - free(color_table[3]); + wfree(color_table[3]); if (symbol_table) - free(symbol_table); + wfree(symbol_table); } RImage *RGetImageFromXPMData(RContext * context, char **data) @@ -95,11 +95,11 @@ RImage *RGetImageFromXPMData(RContext * context, char **data) if (csize != 1 && csize != 2) goto bad_format; - color_table[0] = malloc(ccount); - color_table[1] = malloc(ccount); - color_table[2] = malloc(ccount); - color_table[3] = malloc(ccount); - symbol_table = malloc(ccount * sizeof(unsigned short)); + color_table[0] = wmalloc(ccount); + color_table[1] = wmalloc(ccount); + color_table[2] = wmalloc(ccount); + color_table[3] = wmalloc(ccount); + symbol_table = wmalloc(ccount * sizeof(unsigned short)); bsize = csize * w + 16; @@ -283,14 +283,14 @@ RImage *RLoadXPM(RContext * context, const char *file) if (csize != 1 && csize != 2) goto bad_format; - color_table[0] = malloc(ccount); - color_table[1] = malloc(ccount); - color_table[2] = malloc(ccount); - color_table[3] = malloc(ccount); - symbol_table = malloc(ccount * sizeof(unsigned short)); + color_table[0] = wmalloc(ccount); + color_table[1] = wmalloc(ccount); + color_table[2] = wmalloc(ccount); + color_table[3] = wmalloc(ccount); + symbol_table = wmalloc(ccount * sizeof(unsigned short)); bsize = csize * w + 16; - buffer = malloc(bsize); + buffer = wmalloc(bsize); if (!color_table[0] || !color_table[1] || !color_table[2] || !color_table[3] || !symbol_table || !bsize || !buffer) { @@ -298,7 +298,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); return NULL; } @@ -355,7 +355,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); return NULL; } @@ -434,7 +434,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); return image; bad_format: @@ -442,7 +442,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); if (image) RReleaseImage(image); return NULL; @@ -452,7 +452,7 @@ RImage *RLoadXPM(RContext * context, const char *file) fclose(f); free_color_symbol_table(color_table, symbol_table); if (buffer) - free(buffer); + wfree(buffer); if (image) RReleaseImage(image); return NULL; diff --git a/wrlib/raster.c b/wrlib/raster.c index 0f8fdd11..14468a21 100644 --- a/wrlib/raster.c +++ b/wrlib/raster.c @@ -27,6 +27,8 @@ #include #include +#include + #include "wraster.h" #include "wr_i18n.h" @@ -53,7 +55,7 @@ RImage *RCreateImage(unsigned width, unsigned height, int alpha) return NULL; } - image = malloc(sizeof(RImage)); + image = wmalloc(sizeof(RImage)); if (!image) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -68,10 +70,10 @@ RImage *RCreateImage(unsigned width, unsigned height, int alpha) /* the +4 is to give extra bytes at the end of the buffer, * so that we can optimize image conversion for MMX(tm).. see convert.c */ - image->data = malloc(width * height * (alpha ? 4 : 3) + 4); + image->data = wmalloc(width * height * (alpha ? 4 : 3) + 4); if (!image->data) { RErrorCode = RERR_NOMEMORY; - free(image); + wfree(image); image = NULL; } @@ -94,8 +96,8 @@ void RReleaseImage(RImage * image) image->refCount--; if (image->refCount < 1) { - free(image->data); - free(image); + wfree(image->data); + wfree(image); } } diff --git a/wrlib/save_jpeg.c b/wrlib/save_jpeg.c index 5320ba47..25c15f6c 100644 --- a/wrlib/save_jpeg.c +++ b/wrlib/save_jpeg.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -59,7 +61,7 @@ Bool RSaveJPEG(RImage *img, const char *filename, char *title) img_depth = 3; /* collect separate RGB values to a buffer */ - buffer = malloc(sizeof(char) * 3 * img->width * img->height); + buffer = wmalloc(sizeof(char) * 3 * img->width * img->height); for (y = 0; y < img->height; y++) { for (x = 0; x < img->width; x++) { RGetPixel(img, x, y, &pixel); @@ -97,7 +99,7 @@ Bool RSaveJPEG(RImage *img, const char *filename, char *title) jpeg_finish_compress(&cinfo); /* Clean */ - free(buffer); + wfree(buffer); fclose(file); return True; diff --git a/wrlib/save_png.c b/wrlib/save_png.c index 40d3b996..3ece7cf7 100644 --- a/wrlib/save_png.c +++ b/wrlib/save_png.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -95,7 +97,7 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) png_write_info(png_ptr, png_info_ptr); /* Allocate memory for one row (3 bytes per pixel - RGB) */ - png_row = (png_bytep) malloc(3 * width * sizeof(png_byte)); + png_row = (png_bytep) wmalloc(3 * width * sizeof(png_byte)); /* Write image data */ for (y = 0; y < height; y++) { @@ -121,7 +123,7 @@ Bool RSavePNG(RImage *img, const char *filename, char *title) if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp) NULL); if (png_row != NULL) - free(png_row); + wfree(png_row); return True; } diff --git a/wrlib/save_xpm.c b/wrlib/save_xpm.c index 243f9506..2dfae605 100644 --- a/wrlib/save_xpm.c +++ b/wrlib/save_xpm.c @@ -28,6 +28,8 @@ #include #include +#include + #include "wraster.h" #include "imgformat.h" #include "wr_i18n.h" @@ -95,7 +97,7 @@ static Bool addcolor(XPMColor ** list, unsigned r, unsigned g, unsigned b, int * if (tmpc) return True; - newc = malloc(sizeof(XPMColor)); + newc = wmalloc(sizeof(XPMColor)); if (!newc) { @@ -149,7 +151,7 @@ static void freecolormap(XPMColor * colormap) while (colormap) { tmp = colormap->next; - free(colormap); + wfree(colormap); colormap = tmp; } } diff --git a/wrlib/scale.c b/wrlib/scale.c index 93c5172d..3d5550c4 100644 --- a/wrlib/scale.c +++ b/wrlib/scale.c @@ -29,6 +29,8 @@ #include #include +#include + #include "wraster.h" #include "scale.h" #include "wr_i18n.h" @@ -295,7 +297,7 @@ typedef struct { /* clamp the input to the specified range */ #define CLAMP(v,l,h) ((v)<(l) ? (l) : (v) > (h) ? (h) : v) -/* return of calloc is not checked if NULL in the function below! */ +/* return of wmalloc is not checked if NULL in the function below! */ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) { CLIST *contrib; /* array of contribution lists */ @@ -319,13 +321,13 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) yscale = (double)new_height / (double)src->height; /* pre-calculate filter contributions for a row */ - contrib = (CLIST *) calloc(new_width, sizeof(CLIST)); + contrib = (CLIST *) wmalloc(new_width * sizeof(CLIST)); if (xscale < 1.0) { width = fwidth / xscale; fscale = 1.0 / xscale; for (i = 0; i < new_width; ++i) { contrib[i].n = 0; - contrib[i].p = (CONTRIB *) calloc((int) ceil(width * 2 + 1), sizeof(CONTRIB)); + contrib[i].p = (CONTRIB *) wmalloc(ceil(width * 2 + 1) * sizeof(CONTRIB)); center = (double)i / xscale; left = ceil(center - width); right = floor(center + width); @@ -348,7 +350,7 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) for (i = 0; i < new_width; ++i) { contrib[i].n = 0; - contrib[i].p = (CONTRIB *) calloc((int) ceil(fwidth * 2 + 1), sizeof(CONTRIB)); + contrib[i].p = (CONTRIB *) wmalloc(ceil(fwidth * 2 + 1) * sizeof(CONTRIB)); center = (double)i / xscale; left = ceil(center - fwidth); right = floor(center + fwidth); @@ -395,18 +397,18 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) /* free the memory allocated for horizontal filter weights */ for (i = 0; i < new_width; ++i) { - free(contrib[i].p); + wfree(contrib[i].p); } - free(contrib); + wfree(contrib); /* pre-calculate filter contributions for a column */ - contrib = (CLIST *) calloc(dst->height, sizeof(CLIST)); + contrib = (CLIST *) wmalloc(dst->height * sizeof(CLIST)); if (yscale < 1.0) { width = fwidth / yscale; fscale = 1.0 / yscale; for (i = 0; i < dst->height; ++i) { contrib[i].n = 0; - contrib[i].p = (CONTRIB *) calloc((int) ceil(width * 2 + 1), sizeof(CONTRIB)); + contrib[i].p = (CONTRIB *) wmalloc(ceil(width * 2 + 1) * sizeof(CONTRIB)); center = (double)i / yscale; left = ceil(center - width); right = floor(center + width); @@ -428,7 +430,7 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) } else { for (i = 0; i < dst->height; ++i) { contrib[i].n = 0; - contrib[i].p = (CONTRIB *) calloc((int) ceil(fwidth * 2 + 1), sizeof(CONTRIB)); + contrib[i].p = (CONTRIB *) wmalloc(ceil(fwidth * 2 + 1) * sizeof(CONTRIB)); center = (double)i / yscale; left = ceil(center - fwidth); right = floor(center + fwidth); @@ -450,7 +452,7 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) } /* apply filter to zoom vertically from tmp to dst */ - sp = malloc(tmp->height * 3); + sp = wmalloc(tmp->height * 3); for (k = 0; k < new_width; ++k) { CONTRIB *pp; @@ -485,13 +487,13 @@ RImage *RSmoothScaleImage(RImage * src, unsigned new_width, unsigned new_height) p += new_width * 3; } } - free(sp); + wfree(sp); /* free the memory allocated for vertical filter weights */ for (i = 0; i < dst->height; ++i) { - free(contrib[i].p); + wfree(contrib[i].p); } - free(contrib); + wfree(contrib); RReleaseImage(tmp); diff --git a/wrlib/tests/testgrad.c b/wrlib/tests/testgrad.c index 0b5a7994..9f6a97f3 100644 --- a/wrlib/tests/testgrad.c +++ b/wrlib/tests/testgrad.c @@ -38,7 +38,7 @@ int main(int argc, char **argv) else ProgName++; - color_name = (char **)malloc(sizeof(char *) * argc); + color_name = (char **)wmalloc(sizeof(char *) * argc); if (color_name == NULL) { fprintf(stderr, "Cannot allocate memory!\n"); exit(1); @@ -106,13 +106,13 @@ int main(int argc, char **argv) exit(1); } - colors = malloc(sizeof(RColor *) * (ncolors + 1)); + colors = wmalloc(sizeof(RColor *) * (ncolors + 1)); for (i = 0; i < ncolors; i++) { if (!XParseColor(dpy, ctx->cmap, color_name[i], &color)) { printf("could not parse color \"%s\"\n", color_name[i]); exit(1); } else { - colors[i] = malloc(sizeof(RColor)); + colors[i] = wmalloc(sizeof(RColor)); colors[i]->red = color.red >> 8; colors[i]->green = color.green >> 8; colors[i]->blue = color.blue >> 8; @@ -149,10 +149,10 @@ int main(int argc, char **argv) getchar(); - free(color_name); + wfree(color_name); for (i = 0; i < ncolors + 1; i++) - free(colors[i]); - free(colors); + wfree(colors[i]); + wfree(colors); RDestroyContext(ctx); RShutdown(); diff --git a/wrlib/xutil.c b/wrlib/xutil.c index c1fbfeef..7131b4b2 100644 --- a/wrlib/xutil.c +++ b/wrlib/xutil.c @@ -31,6 +31,8 @@ #include +#include + #ifdef USE_XSHM #include #include @@ -63,7 +65,7 @@ RXImage *RCreateXImage(RContext * context, int depth, unsigned width, unsigned h RXImage *rximg; Visual *visual = context->visual; - rximg = malloc(sizeof(RXImage)); + rximg = wmalloc(sizeof(RXImage)); if (!rximg) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -71,14 +73,14 @@ RXImage *RCreateXImage(RContext * context, int depth, unsigned width, unsigned h #ifndef USE_XSHM rximg->image = XCreateImage(context->dpy, visual, depth, ZPixmap, 0, NULL, width, height, 8, 0); if (!rximg->image) { - free(rximg); + wfree(rximg); RErrorCode = RERR_XERROR; return NULL; } - rximg->image->data = malloc(rximg->image->bytes_per_line * height); + rximg->image->data = wmalloc(rximg->image->bytes_per_line * height); if (!rximg->image->data) { XDestroyImage(rximg->image); - free(rximg); + wfree(rximg); RErrorCode = RERR_NOMEMORY; return NULL; } @@ -90,14 +92,14 @@ RXImage *RCreateXImage(RContext * context, int depth, unsigned width, unsigned h rximg->is_shared = 0; rximg->image = XCreateImage(context->dpy, visual, depth, ZPixmap, 0, NULL, width, height, 8, 0); if (!rximg->image) { - free(rximg); + wfree(rximg); RErrorCode = RERR_XERROR; return NULL; } - rximg->image->data = malloc(rximg->image->bytes_per_line * height); + rximg->image->data = wmalloc(rximg->image->bytes_per_line * height); if (!rximg->image->data) { XDestroyImage(rximg->image); - free(rximg); + wfree(rximg); RErrorCode = RERR_NOMEMORY; return NULL; } @@ -173,7 +175,7 @@ void RDestroyXImage(RContext * context, RXImage * rximage) XDestroyImage(rximage->image); } #endif - free(rximage); + wfree(rximage); } static unsigned getDepth(Display * dpy, Drawable d) @@ -205,7 +207,7 @@ RXImage *RGetXImage(RContext * context, Drawable d, int x, int y, unsigned width } } if (!ximg) { - ximg = malloc(sizeof(RXImage)); + ximg = wmalloc(sizeof(RXImage)); if (!ximg) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -214,7 +216,7 @@ RXImage *RGetXImage(RContext * context, Drawable d, int x, int y, unsigned width ximg->image = XGetImage(context->dpy, d, x, y, width, height, AllPlanes, ZPixmap); } #else /* !USE_XSHM */ - ximg = malloc(sizeof(RXImage)); + ximg = wmalloc(sizeof(RXImage)); if (!ximg) { RErrorCode = RERR_NOMEMORY; return NULL; @@ -224,7 +226,7 @@ RXImage *RGetXImage(RContext * context, Drawable d, int x, int y, unsigned width #endif /* !USE_XSHM */ if (ximg->image == NULL) { - free(ximg); + wfree(ximg); return NULL; } -- 2.39.5 From e3fb8ddbc8e8700e93cca2732641f5414a6a533c Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 11 Sep 2025 17:10:07 -0400 Subject: [PATCH 02/39] Rip out Boehm GC support. This is done to simplify memory management across the boundary between C and Rust. While rewriting WINGs, we may want to be able to malloc/free with the libc allocator on both sides of that divide. --- WINGs/memory.c | 37 ------------------------------------- configure.ac | 18 ------------------ doc/build/Compilation.texi | 10 ---------- 3 files changed, 65 deletions(-) diff --git a/WINGs/memory.c b/WINGs/memory.c index e05e17a8..63a66dac 100644 --- a/WINGs/memory.c +++ b/WINGs/memory.c @@ -34,13 +34,6 @@ #include #endif -#ifdef USE_BOEHM_GC -#ifndef GC_DEBUG -#define GC_DEBUG -#endif /* !GC_DEBUG */ -#include -#endif /* USE_BOEHM_GC */ - #ifndef False # define False 0 #endif @@ -83,19 +76,11 @@ void *wmalloc(size_t size) assert(size > 0); -#ifdef USE_BOEHM_GC - tmp = GC_MALLOC(size); -#else tmp = malloc(size); -#endif if (tmp == NULL) { wwarning("malloc() failed. Retrying after 2s."); sleep(2); -#ifdef USE_BOEHM_GC - tmp = GC_MALLOC(size); -#else tmp = malloc(size); -#endif if (tmp == NULL) { if (Aborting) { fputs("Really Bad Error: recursive malloc() failure.", stderr); @@ -122,19 +107,11 @@ void *wrealloc(void *ptr, size_t newsize) wfree(ptr); nptr = NULL; } else { -#ifdef USE_BOEHM_GC - nptr = GC_REALLOC(ptr, newsize); -#else nptr = realloc(ptr, newsize); -#endif if (nptr == NULL) { wwarning("realloc() failed. Retrying after 2s."); sleep(2); -#ifdef USE_BOEHM_GC - nptr = GC_REALLOC(ptr, newsize); -#else nptr = realloc(ptr, newsize); -#endif if (nptr == NULL) { if (Aborting) { fputs("Really Bad Error: recursive realloc() failure.", stderr); @@ -179,21 +156,7 @@ void *wretain(void *ptr) void wfree(void *ptr) { if (ptr) -#ifdef USE_BOEHM_GC - /* This should eventually be removed, once the criss-cross - * of wmalloc()d memory being free()d, malloc()d memory being - * wfree()d, various misuses of calling wfree() on objects - * allocated by libc malloc() and calling libc free() on - * objects allocated by Boehm GC (think external libraries) - * is cleaned up. - */ - if (GC_base(ptr) != 0) - GC_FREE(ptr); - else - free(ptr); -#else free(ptr); -#endif ptr = NULL; } diff --git a/configure.ac b/configure.ac index f00c21a6..a20cc59e 100644 --- a/configure.ac +++ b/configure.ac @@ -351,24 +351,6 @@ AS_IF([test "x$enable_mwm_hints" = "xno"], AM_CONDITIONAL([USE_MWM_HINTS], [test "x$enable_mwm_hints" != "xno"]) -dnl Boehm GC -dnl ======== -m4_divert_push([INIT_PREPARE])dnl -AC_ARG_ENABLE([boehm-gc], - [AS_HELP_STRING([--enable-boehm-gc], [use Boehm GC instead of the default libc malloc() [default=no]])], - [AS_CASE(["$enableval"], - [yes], [with_boehm_gc=yes], - [no], [with_boehm_gc=no], - [AC_MSG_ERROR([bad value $enableval for --enable-boehm-gc])] )], - [with_boehm_gc=no]) -m4_divert_pop([INIT_PREPARE])dnl - -AS_IF([test "x$with_boehm_gc" = "xyes"], - AC_SEARCH_LIBS([GC_malloc], [gc], - [AC_DEFINE(USE_BOEHM_GC, 1, [Define if Boehm GC is to be used])], - [AC_MSG_FAILURE([--enable-boehm-gc specified but test for libgc failed])])) - - dnl LCOV dnl ==== m4_divert_push([INIT_PREPARE])dnl diff --git a/doc/build/Compilation.texi b/doc/build/Compilation.texi index 2265e4fd..9908bd61 100644 --- a/doc/build/Compilation.texi +++ b/doc/build/Compilation.texi @@ -253,14 +253,6 @@ If found, then the library @emph{WRaster} can use the @emph{ImageMagick} library @sc{Window Maker} support more image formats, like @emph{SVG}, @emph{BMP}, @emph{TGA}, ... You can get it from @uref{http://www.imagemagick.org/} -@item @emph{Boehm GC} - -This library can be used by the @emph{WINGs} utility toolkit to use a -@cite{Boehm-Demers-Weiser Garbage Collector} instead of the traditional -@command{malloc}/@command{free} functions from the @emph{libc}. -You have to explicitly ask for its support though (@pxref{Configure Options}). -You can get it from @uref{http://www.hboehm.info/gc/} - @end itemize @@ -468,8 +460,6 @@ You can find more information about the libraries in the @ref{Optional Dependencies}. @table @option -@item --enable-boehm-gc -Never enabled by default, use Boehm GC instead of the default @emph{libc} @command{malloc()} @item --disable-gif Disable GIF support in @emph{WRaster} library; when enabled use @file{libgif} or @file{libungif}. -- 2.39.5 From 46fcbb0ff15d5ceff389e0281a94d63d8ed65157 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sun, 21 Sep 2025 15:31:47 -0400 Subject: [PATCH 03/39] Port custom allocators (WINGs memory.c) to Rust. This introduces the crate wutil-rs, which is intended to be the destination for migrating the API of WINGs/WINGs/WUtil.h to Rust. --- Makefile.am | 2 +- WINGs/Makefile.am | 9 +- WINGs/WINGs/WUtil.h | 4 - WINGs/memory.c | 186 ---------------------------------- WPrefs.app/Makefile.am | 1 + WPrefs.app/main.c | 2 - configure.ac | 3 + src/main.c | 1 - wutil-rs/Cargo.toml | 7 ++ wutil-rs/Makefile.am | 20 ++++ wutil-rs/src/lib.rs | 1 + wutil-rs/src/memory.rs | 222 +++++++++++++++++++++++++++++++++++++++++ 12 files changed, 260 insertions(+), 198 deletions(-) delete mode 100644 WINGs/memory.c create mode 100644 wutil-rs/Cargo.toml create mode 100644 wutil-rs/Makefile.am create mode 100644 wutil-rs/src/lib.rs create mode 100644 wutil-rs/src/memory.rs diff --git a/Makefile.am b/Makefile.am index 8043b18b..0a50bf18 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,7 +39,7 @@ ACLOCAL_AMFLAGS = -I m4 AM_DISTCHECK_CONFIGURE_FLAGS = --enable-silent-rules LINGUAS='*' -SUBDIRS = wrlib WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc +SUBDIRS = wrlib wutil-rs WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc DIST_SUBDIRS = $(SUBDIRS) test EXTRA_DIST = TODO BUGS BUGFORM FAQ INSTALL \ diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 60875dab..14fbeccc 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -10,10 +10,12 @@ libWUtil_la_LDFLAGS = -version-info @WUTIL_VERSION@ lib_LTLIBRARIES = libWUtil.la libWINGs.la +wutilrs = $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a +wraster = $(top_builddir)/wrlib/libwraster.la -LDADD= libWUtil.la libWINGs.la $(top_builddir)/wrlib/libwraster.la @INTLIBS@ -libWINGs_la_LIBADD = libWUtil.la $(top_builddir)/wrlib/libwraster.la @XLIBS@ @XFT_LIBS@ @FCLIBS@ @LIBM@ @PANGO_LIBS@ -libWUtil_la_LIBADD = @LIBBSD@ +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) EXTRA_DIST = BUGS make-rgb Examples Extras Tests @@ -70,7 +72,6 @@ libWUtil_la_SOURCES = \ findfile.c \ handlers.c \ hashtable.c \ - memory.c \ menuparser.c \ menuparser.h \ menuparser_macros.c \ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index feff8ae8..f37662bb 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -213,10 +213,6 @@ void wfree(void *ptr); void wrelease(void *ptr); void* wretain(void *ptr); -typedef void waborthandler(int); - -waborthandler* wsetabort(waborthandler* handler); - /* ---[ WINGs/error.c ]--------------------------------------------------- */ enum { diff --git a/WINGs/memory.c b/WINGs/memory.c deleted file mode 100644 index 63a66dac..00000000 --- a/WINGs/memory.c +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Window Maker miscelaneous function library - * - * Copyright (c) 1997-2003 Alfredo K. Kojima - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -#include "wconfig.h" -#include "WUtil.h" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_STDNORETURN -#include -#endif - -#ifndef False -# define False 0 -#endif -#ifndef True -# define True 1 -#endif - -static void defaultHandler(int bla) -{ - if (bla) - kill(getpid(), SIGABRT); - else - exit(1); -} - -static waborthandler *aborthandler = defaultHandler; - -static inline noreturn void wAbort(int bla) -{ - (*aborthandler)(bla); - exit(-1); -} - -waborthandler *wsetabort(waborthandler * handler) -{ - waborthandler *old = aborthandler; - - aborthandler = handler; - - return old; -} - -static int Aborting = 0; /* if we're in the middle of an emergency exit */ - -static WMHashTable *table = NULL; - -void *wmalloc(size_t size) -{ - void *tmp; - - assert(size > 0); - - tmp = malloc(size); - if (tmp == NULL) { - wwarning("malloc() failed. Retrying after 2s."); - sleep(2); - tmp = malloc(size); - if (tmp == NULL) { - if (Aborting) { - fputs("Really Bad Error: recursive malloc() failure.", stderr); - exit(-1); - } else { - wfatal("virtual memory exhausted"); - Aborting = 1; - wAbort(False); - } - } - } - if (tmp != NULL) - memset(tmp, 0, size); - return tmp; -} - -void *wrealloc(void *ptr, size_t newsize) -{ - void *nptr; - - if (!ptr) { - nptr = wmalloc(newsize); - } else if (newsize == 0) { - wfree(ptr); - nptr = NULL; - } else { - nptr = realloc(ptr, newsize); - if (nptr == NULL) { - wwarning("realloc() failed. Retrying after 2s."); - sleep(2); - nptr = realloc(ptr, newsize); - if (nptr == NULL) { - if (Aborting) { - fputs("Really Bad Error: recursive realloc() failure.", stderr); - exit(-1); - } else { - wfatal("virtual memory exhausted"); - Aborting = 1; - wAbort(False); - } - } - } - } - return nptr; -} - -void *wretain(void *ptr) -{ - int *refcount; - - if (!table) { - table = WMCreateHashTable(WMIntHashCallbacks); - } - - refcount = WMHashGet(table, ptr); - if (!refcount) { - refcount = wmalloc(sizeof(int)); - *refcount = 1; - WMHashInsert(table, ptr, refcount); -#ifdef VERBOSE - printf("== %i (%p)\n", *refcount, ptr); -#endif - } else { - (*refcount)++; -#ifdef VERBOSE - printf("+ %i (%p)\n", *refcount, ptr); -#endif - } - - return ptr; -} - -void wfree(void *ptr) -{ - if (ptr) - free(ptr); - ptr = NULL; -} - -void wrelease(void *ptr) -{ - int *refcount; - - refcount = WMHashGet(table, ptr); - if (!refcount) { - wwarning("trying to release unexisting data %p", ptr); - } else { - (*refcount)--; - if (*refcount < 1) { -#ifdef VERBOSE - printf("RELEASING %p\n", ptr); -#endif - WMHashRemove(table, ptr); - wfree(refcount); - wfree(ptr); - } -#ifdef VERBOSE - else { - printf("- %i (%p)\n", *refcount, ptr); - } -#endif - } -} diff --git a/WPrefs.app/Makefile.am b/WPrefs.app/Makefile.am index 4408ff96..50c258eb 100644 --- a/WPrefs.app/Makefile.am +++ b/WPrefs.app/Makefile.am @@ -66,6 +66,7 @@ AM_CPPFLAGS = -DRESOURCE_PATH=\"$(wpdatadir)\" -DWMAKER_RESOURCE_PATH=\"$(pkgdat WPrefs_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la WPrefs_LDADD = \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\ $(top_builddir)/WINGs/libWINGs.la\ $(top_builddir)/WINGs/libWUtil.la\ $(top_builddir)/wrlib/libwraster.la \ diff --git a/WPrefs.app/main.c b/WPrefs.app/main.c index 2a76a203..0c47a2f0 100644 --- a/WPrefs.app/main.c +++ b/WPrefs.app/main.c @@ -87,8 +87,6 @@ int main(int argc, char **argv) int i; char *display_name = ""; - wsetabort(wAbort); - memset(DeadHandlers, 0, sizeof(DeadHandlers)); WMInitializeApplication("WPrefs", &argc, argv); diff --git a/configure.ac b/configure.ac index a20cc59e..37e86edd 100644 --- a/configure.ac +++ b/configure.ac @@ -955,6 +955,9 @@ AC_CONFIG_FILES( wrlib/Makefile wrlib/po/Makefile wrlib/tests/Makefile + dnl Rust implementation of WINGs libraries + wutil-rs/Makefile + dnl WINGs toolkit WINGs/Makefile WINGs/WINGs/Makefile WINGs/po/Makefile WINGs/Documentation/Makefile WINGs/Resources/Makefile WINGs/Extras/Makefile diff --git a/src/main.c b/src/main.c index 0f2f9e00..3f5fd558 100644 --- a/src/main.c +++ b/src/main.c @@ -624,7 +624,6 @@ static int real_main(int argc, char **argv) int d, s; setlocale(LC_ALL, ""); - wsetabort(wAbort); /* for telling WPrefs what's the name of the wmaker binary being ran */ setenv("WMAKER_BIN_NAME", argv[0], 1); diff --git a/wutil-rs/Cargo.toml b/wutil-rs/Cargo.toml new file mode 100644 index 00000000..29c8b68b --- /dev/null +++ b/wutil-rs/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "wutil-rs" +version = "0.1.0" +edition = "2024" + +[lib] +crate-type = ["staticlib"] diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am new file mode 100644 index 00000000..f8fcd540 --- /dev/null +++ b/wutil-rs/Makefile.am @@ -0,0 +1,20 @@ +AUTOMAKE_OPTIONS = + +RUST_SOURCES = \ + src/lib.rs \ + src/memory.rs + +RUST_EXTRA = \ + Cargo.lock \ + Cargo.toml + +target/debug/libwutil_rs.a: $(RUST_SOURCES) $(RUST_EXTRA) + $(CARGO) build + +check-local: + $(CARGO) test + +clean-local: + $(CARGO) clean + +all: target/debug/libwutil_rs.a diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs new file mode 100644 index 00000000..eb291915 --- /dev/null +++ b/wutil-rs/src/lib.rs @@ -0,0 +1 @@ +pub mod memory; diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs new file mode 100644 index 00000000..7ced619b --- /dev/null +++ b/wutil-rs/src/memory.rs @@ -0,0 +1,222 @@ +//! Custom implementations of malloc/free/realloc. +//! +//! These are intended for use by C functions that need to allocate. Window +//! Maker originally provided [`wmalloc`], [`wfree`], and [`wrealloc`] for +//! customizable handling of memory exhaustion (to save workspace state before +//! aborting) and to allow optional use of the Boehm GC library. It also tracked +//! reference counts, via [`wretain`] and [`wrelease`]. +//! +//! If everything gets rewritten in Rust, we won't need this module anymore. For +//! now, it helps to move our allocations into Rust so that it is more +//! straightforward to store Rust objects in heap memory that was allocated from +//! C. (Rust may have stricter requirements for heap-allocated segments than are +//! provided by arbitrary C allocators). +//! +//! TODO: We may want to restore handling of OOM errors. This would require +//! installing a customized Rust allocator, which isn't something you can do yet +//! in stable Rust. And, unless our rewrite ends up taking up obscenely more +//! memory than the baseline Window Maker code, it isn't really necessary in +//! this day and age. + +use std::{alloc, mem, ptr::{self, NonNull}}; + +/// Tracks the layout and reference count of an allocated chunk of memory. +#[derive(Clone, Copy)] +struct Header { + ptr: NonNull, + layout: alloc::Layout, + refcount: u16, +} + +impl Header { + /// Recovers the `Header` for the allocated memory chunk `b`. + /// + /// ## Safety + /// + /// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. + unsafe fn for_alloc_bytes(b: *mut u8) -> *mut Header { + unsafe { + b.sub(mem::size_of::
()) + .cast::
() + } + } +} + +/// Allocates at least `size` bytes and returns a pointer to them. +/// +/// Returns null if `size` is 0. +pub fn alloc_bytes(size: usize) -> *mut u8 { + if size == 0 { + return ptr::null_mut(); + } + let header_layout = match alloc::Layout::from_size_align(mem::size_of::
(), 8) { + Ok(x) => x, + Err(_) => return ptr::null_mut(), + }; + + let layout = match alloc::Layout::from_size_align(size, 8) { + Ok(x) => x, + Err(_) => return ptr::null_mut(), + }; + let (layout, result_offset) = match header_layout.extend(layout) { + Ok(x) => x, + Err(_) => return ptr::null_mut(), + }; + + let full_segment = unsafe { alloc::alloc_zeroed(layout) }; + if full_segment.is_null() { + return ptr::null_mut(); + } + let result = unsafe { full_segment.add(result_offset) }; + if result.is_null() { + return ptr::null_mut(); + } + + unsafe { + let header = result.sub(mem::size_of::
()).cast::
(); + header.write_unaligned(Header { + ptr: NonNull::new_unchecked(full_segment), + layout: header_layout, + refcount: 0, + }); + } + + result +} + +/// Frees the bytes pointed to by `b`. +/// +/// ## Safety +/// +/// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. +pub unsafe fn free_bytes(b: *mut u8) { + if b.is_null() { + return; + } + unsafe { + let header = &*Header::for_alloc_bytes(b); + alloc::dealloc(header.ptr.as_ptr(), header.layout); + } +} + +/// Functions to be called from C. +pub mod ffi { + use super::{alloc_bytes, free_bytes, Header}; + + use std::{ffi::c_void, ptr}; + + /// Allocates `size` bytes. Returns null if `sizes is 0. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wmalloc(size: usize) -> *mut c_void { + alloc_bytes(size).cast::() + } + + /// Frees `ptr`, which must have come from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wfree(ptr: *mut c_void) { + unsafe { free_bytes(ptr.cast::()); } + } + + /// Resizes `ptr` to be at least `newsize` bytes in size, returning the + /// start of the new segment. + /// + /// ## Safety + /// + /// Callers must ensure that `ptr` is a live allocation from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wrealloc(ptr: *mut c_void, newsize: usize) -> *mut c_void { + unsafe { + wfree(ptr); + wmalloc(newsize).cast::() + } + } + + /// Bumps the refcount for `ptr`. + /// + /// ## Safety + /// + /// Callers must ensure that `b` is a live allocation from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wretain(ptr: *mut c_void) -> *mut c_void { + if ptr.is_null() { + return ptr::null_mut(); + } + unsafe { + let header = Header::for_alloc_bytes(ptr.cast::()); + (*header).refcount += 1; + } + ptr + } + + /// Decrements the refcount for `ptr`. If this brings the refcount to 0, + /// frees `ptr`. + /// + /// ## Safety + /// + /// Callers must ensure that `ptr` is a live allocation from [`wmalloc`] or [`wrealloc`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wrelease(ptr: *mut c_void) { + let ptr = ptr.cast::(); + if ptr.is_null() { + return; + } + let header = unsafe { &mut *Header::for_alloc_bytes(ptr) }; + match header.refcount { + 0 | 1 => unsafe { free_bytes(ptr) }, + _ => header.refcount -= 1, + } + } +} + +#[cfg(test)] +mod test { + use super::{alloc_bytes, free_bytes, ffi::wrealloc, Header}; + + use std::{mem, os::raw::c_void, ptr}; + + #[test] + fn recover_header() { + unsafe { + let x = alloc_bytes(mem::size_of::()); + let header = Header::for_alloc_bytes(x); + assert_eq!(header.cast::().add(mem::size_of::
()), x); + // This may be allocator-dependent, but it's a reasonable sanity check for now. + assert!((*header).ptr.as_ptr() <= header.cast::()); + } + } + + #[test] + fn alloc_zero_returns_null() { + assert!(alloc_bytes(0).is_null()); + } + + #[test] + fn free_null() { + unsafe { free_bytes(ptr::null_mut()); } + } + + #[test] + fn realloc_null() { + unsafe { assert!(wrealloc(ptr::null_mut(), 0).is_null()); } + } + + #[test] + fn alloc_free_nonzero() { + let x = alloc_bytes(mem::size_of::()).cast::(); + assert!(!x.is_null()); + unsafe { *x = 42; } + assert_eq!(unsafe { *x }, 42); + unsafe { free_bytes(x.cast::()); } + } + + #[test] + fn realloc_nonzero() { + let x = alloc_bytes(mem::size_of::()).cast::(); + assert!(!x.is_null()); + let y = unsafe { wrealloc(x, mem::size_of::()).cast::() }; + assert!(!y.is_null()); + unsafe { *y = 17; } + assert_eq!(unsafe { *y }, 17); + unsafe { free_bytes(y.cast::()); } + } +} -- 2.39.5 From 5b0ad78f01fa2b736e5d76734585a7ab4bc6064d Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 2 Oct 2025 13:24:51 -0400 Subject: [PATCH 04/39] Remember to AC_SUBST the Rust compiler environment variables so they're visible in Makefiles. --- configure.ac | 2 ++ 1 file changed, 2 insertions(+) diff --git a/configure.ac b/configure.ac index 37e86edd..78313976 100644 --- a/configure.ac +++ b/configure.ac @@ -54,10 +54,12 @@ AC_CHECK_PROG(CARGO, [cargo], [yes], [no]) AS_IF(test x$CARGO = xno, AC_MSG_ERROR([cargo is required. Please set the CARGO environment variable or install the Rust toolchain from https://www.rust-lang.org/]) ) +AC_SUBST(CARGO, [cargo]) AC_CHECK_PROG(RUSTC, [rustc], [yes], [no]) AS_IF(test x$RUSTC = xno, AC_MSG_ERROR([rustc is required. Please set the RUSTC environment variable or install the Rust toolchain from https://www.rust-lang.org/]) ) +AC_SUBST(RUSTC, [rustc]) dnl libtool library versioning dnl ========================== -- 2.39.5 From d50adaa1c8a78f11a2204e157deeea4bb0586087 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 2 Oct 2025 13:26:57 -0400 Subject: [PATCH 05/39] Use free() on memory returned by FcNameUnparse and hand back wfree-managed pointers from our functions. This is necessary because we now allocate memory through a special allocator of our own on the Rust side. Passing raw malloc'd pointers to wfree will break things. --- WINGs/wfont.c | 12 +++++++++--- WPrefs.app/FontSimple.c | 6 +++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/WINGs/wfont.c b/WINGs/wfont.c index 999aaa44..52af4bfc 100644 --- a/WINGs/wfont.c +++ b/WINGs/wfont.c @@ -51,12 +51,15 @@ static char *xlfdToFcName(const char *xlfd) { FcPattern *pattern; char *fname; + char *result; pattern = xlfdToFcPattern(xlfd); fname = (char *)FcNameUnparse(pattern); + result = wstrdup(fname); + free(fname); FcPatternDestroy(pattern); - return fname; + return result; } static Bool hasProperty(FcPattern * pattern, const char *property) @@ -92,6 +95,7 @@ static Bool hasPropertyWithStringValue(FcPattern * pattern, const char *object, static char *makeFontOfSize(const char *font, int size, const char *fallback) { FcPattern *pattern; + char *name; char *result; if (font[0] == '-') { @@ -115,7 +119,9 @@ static char *makeFontOfSize(const char *font, int size, const char *fallback) /*FcPatternPrint(pattern); */ - result = (char *)FcNameUnparse(pattern); + name = (char *)FcNameUnparse(pattern); + result = wstrdup(name); + free(name); FcPatternDestroy(pattern); return result; @@ -421,7 +427,7 @@ WMFont *WMCopyFontWithStyle(WMScreen * scrPtr, WMFont * font, WMFontStyle style) name = (char *)FcNameUnparse(pattern); copy = WMCreateFont(scrPtr, name); FcPatternDestroy(pattern); - wfree(name); + free(name); return copy; } diff --git a/WPrefs.app/FontSimple.c b/WPrefs.app/FontSimple.c index 2147b985..13f01edb 100644 --- a/WPrefs.app/FontSimple.c +++ b/WPrefs.app/FontSimple.c @@ -288,6 +288,7 @@ static char *getSelectedFont(_Panel * panel, FcChar8 * curfont) WMListItem *item; FcPattern *pat; char *name; + char *result; if (curfont) pat = FcNameParse(curfont); @@ -321,9 +322,12 @@ static char *getSelectedFont(_Panel * panel, FcChar8 * curfont) } name = (char *)FcNameUnparse(pat); + result = wstrdup(name); + free(name); FcPatternDestroy(pat); - return name; + + return result; } static void updateSampleFont(_Panel * panel) -- 2.39.5 From 2b9b9157683001ed9e24834d761b83effa1f1aa6 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 2 Oct 2025 14:22:20 -0400 Subject: [PATCH 06/39] Replace most WUtil functions in findfile.c with Rust impls. This is not a bug-for-bug reimplementation, and it may need some shaking down to ensure that everything still works. Once their dependents are ported, it would be appropriate to dispose of them. --- WINGs/WINGs/WUtil.h | 4 +- WINGs/findfile.c | 427 -------------------------------------- WINGs/menuparser.c | 4 +- WINGs/userdefaults.c | 3 +- WINGs/wcolorpanel.c | 9 +- WINGs/wfilepanel.c | 4 +- WPrefs.app/Appearance.c | 8 +- WPrefs.app/TexturePanel.c | 2 +- util/setstyle.c | 2 +- wutil-rs/Makefile.am | 1 + wutil-rs/src/find_file.rs | 221 ++++++++++++++++++++ wutil-rs/src/lib.rs | 1 + 12 files changed, 244 insertions(+), 442 deletions(-) create mode 100644 wutil-rs/src/find_file.rs diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index f37662bb..bf78fa40 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -244,8 +244,8 @@ char* wexpandpath(const char *path); int wcopy_file(const char *toPath, const char *srcFile, const char *destFile); -/* don't free the returned string */ -const char* wgethomedir(void); +/* You must free the returned string! */ +char* wgethomedir(void); /* ---[ WINGs/proplist.c ]------------------------------------------------ */ diff --git a/WINGs/findfile.c b/WINGs/findfile.c index bb2ce7f0..10c26b3b 100644 --- a/WINGs/findfile.c +++ b/WINGs/findfile.c @@ -34,331 +34,6 @@ #include #include -#ifndef PATH_MAX -#define PATH_MAX 1024 -#endif - - -const char *wgethomedir(void) -{ - static char *home = NULL; - char *tmp; - struct passwd *user; - - if (home) - return home; - - tmp = GETENV("HOME"); - if (tmp) { - home = wstrdup(tmp); - return home; - } - - user = getpwuid(getuid()); - if (!user) { - werror(_("could not get password entry for UID %i"), getuid()); - home = "/"; - return home; - } - - if (!user->pw_dir) - home = "/"; - else - home = wstrdup(user->pw_dir); - - return home; -} - -/* - * Return the home directory for the specified used - * - * If user not found, returns NULL, otherwise always returns a path that is - * statically stored. - * - * Please note you must use the path before any other call to 'getpw*' or it - * may be erased. This is a design choice to avoid duplication considering - * the use case for this function. - */ -static const char *getuserhomedir(const char *username) -{ - static const char default_home[] = "/"; - struct passwd *user; - - user = getpwnam(username); - if (!user) { - werror(_("could not get password entry for user %s"), username); - return NULL; - } - if (!user->pw_dir) - return default_home; - else - return user->pw_dir; - -} - -char *wexpandpath(const char *path) -{ - const char *origpath = path; - char buffer2[PATH_MAX + 2]; - char buffer[PATH_MAX + 2]; - int i; - - memset(buffer, 0, PATH_MAX + 2); - - if (*path == '~') { - const char *home; - - path++; - if (*path == '/' || *path == 0) { - home = wgethomedir(); - if (strlen(home) > PATH_MAX || - wstrlcpy(buffer, home, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } else { - int j; - j = 0; - while (*path != 0 && *path != '/') { - if (j > PATH_MAX) - goto error; - buffer2[j++] = *path; - buffer2[j] = 0; - path++; - } - home = getuserhomedir(buffer2); - if (!home || wstrlcat(buffer, home, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } - } - - i = strlen(buffer); - - while (*path != 0 && i <= PATH_MAX) { - char *tmp; - - if (*path == '$') { - int j; - - path++; - /* expand $(HOME) or $HOME style environment variables */ - if (*path == '(') { - path++; - j = 0; - while (*path != 0 && *path != ')') { - if (j > PATH_MAX) - goto error; - buffer2[j++] = *(path++); - } - buffer2[j] = 0; - if (*path == ')') { - path++; - tmp = getenv(buffer2); - } else { - tmp = NULL; - } - if (!tmp) { - if ((i += strlen(buffer2) + 2) > PATH_MAX) - goto error; - buffer[i] = 0; - if (wstrlcat(buffer, "$(", sizeof(buffer)) >= sizeof(buffer) || - wstrlcat(buffer, buffer2, sizeof(buffer)) >= sizeof(buffer)) - goto error; - if (*(path-1)==')') { - if (++i > PATH_MAX || - wstrlcat(buffer, ")", sizeof(buffer)) >= sizeof(buffer)) - goto error; - } - } else { - if ((i += strlen(tmp)) > PATH_MAX || - wstrlcat(buffer, tmp, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } - } else { - j = 0; - while (*path != 0 && *path != '/') { - if (j > PATH_MAX) - goto error; - buffer2[j++] = *(path++); - } - buffer2[j] = 0; - tmp = getenv(buffer2); - if (!tmp) { - if ((i += strlen(buffer2) + 1) > PATH_MAX || - wstrlcat(buffer, "$", sizeof(buffer)) >= sizeof(buffer) || - wstrlcat(buffer, buffer2, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } else { - if ((i += strlen(tmp)) > PATH_MAX || - wstrlcat(buffer, tmp, sizeof(buffer)) >= sizeof(buffer)) - goto error; - } - } - } else { - buffer[i++] = *path; - path++; - } - } - - if (*path!=0) - goto error; - - return wstrdup(buffer); - -error: - errno = ENAMETOOLONG; - werror(_("could not expand %s"), origpath); - - return NULL; -} - -/* return address of next char != tok or end of string whichever comes first */ -static const char *skipchar(const char *string, char tok) -{ - while (*string != 0 && *string == tok) - string++; - - return string; -} - -/* return address of next char == tok or end of string whichever comes first */ -static const char *nextchar(const char *string, char tok) -{ - while (*string != 0 && *string != tok) - string++; - - return string; -} - -/* - *---------------------------------------------------------------------- - * findfile-- - * Finds a file in a : separated list of paths. ~ expansion is also - * done. - * - * Returns: - * The complete path for the file (in a newly allocated string) or - * NULL if the file was not found. - * - * Side effects: - * A new string is allocated. It must be freed later. - * - *---------------------------------------------------------------------- - */ -char *wfindfile(const char *paths, const char *file) -{ - char *path; - const char *tmp, *tmp2; - int len, flen; - char *fullpath; - - if (!file) - return NULL; - - if (*file == '/' || *file == '~' || *file == '$' || !paths || *paths == 0) { - if (access(file, F_OK) < 0) { - fullpath = wexpandpath(file); - if (!fullpath) - return NULL; - - if (access(fullpath, F_OK) < 0) { - wfree(fullpath); - return NULL; - } else { - return fullpath; - } - } else { - return wstrdup(file); - } - } - - flen = strlen(file); - tmp = paths; - while (*tmp) { - tmp = skipchar(tmp, ':'); - if (*tmp == 0) - break; - tmp2 = nextchar(tmp, ':'); - len = tmp2 - tmp; - path = wmalloc(len + flen + 2); - path = memcpy(path, tmp, len); - path[len] = 0; - if (path[len - 1] != '/' && - wstrlcat(path, "/", len + flen + 2) >= len + flen + 2) { - wfree(path); - return NULL; - } - - if (wstrlcat(path, file, len + flen + 2) >= len + flen + 2) { - wfree(path); - return NULL; - } - - fullpath = wexpandpath(path); - wfree(path); - - if (fullpath) { - if (access(fullpath, F_OK) == 0) { - return fullpath; - } - wfree(fullpath); - } - tmp = tmp2; - } - - return NULL; -} - -char *wfindfileinlist(char *const *path_list, const char *file) -{ - int i; - char *path; - int len, flen; - char *fullpath; - - if (!file) - return NULL; - - if (*file == '/' || *file == '~' || !path_list) { - if (access(file, F_OK) < 0) { - fullpath = wexpandpath(file); - if (!fullpath) - return NULL; - - if (access(fullpath, F_OK) < 0) { - wfree(fullpath); - return NULL; - } else { - return fullpath; - } - } else { - return wstrdup(file); - } - } - - flen = strlen(file); - for (i = 0; path_list[i] != NULL; i++) { - len = strlen(path_list[i]); - path = wmalloc(len + flen + 2); - path = memcpy(path, path_list[i], len); - path[len] = 0; - if (wstrlcat(path, "/", len + flen + 2) >= len + flen + 2 || - wstrlcat(path, file, len + flen + 2) >= len + flen + 2) { - wfree(path); - return NULL; - } - /* expand tilde */ - fullpath = wexpandpath(path); - wfree(path); - if (fullpath) { - /* check if file exists */ - if (access(fullpath, F_OK) == 0) { - return fullpath; - } - wfree(fullpath); - } - } - - return NULL; -} char *wfindfileinarray(WMPropList *array, const char *file) { @@ -419,105 +94,3 @@ char *wfindfileinarray(WMPropList *array, const char *file) } return NULL; } - -int wcopy_file(const char *dest_dir, const char *src_file, const char *dest_file) -{ - char *path_dst; - int fd_src, fd_dst; - struct stat stat_src; - mode_t permission_dst; - const size_t buffer_size = 2 * 1024 * 1024; /* 4MB is a decent start choice to allow the OS to take advantage of modern disk's performance */ - char *buffer; /* The buffer is not created on the stack to avoid possible stack overflow as our buffer is big */ - - try_again_src: - fd_src = open(src_file, O_RDONLY | O_NOFOLLOW); - if (fd_src == -1) { - if (errno == EINTR) - goto try_again_src; - werror(_("Could not open input file \"%s\": %s"), src_file, strerror(errno)); - return -1; - } - - /* Only accept to copy regular files */ - if (fstat(fd_src, &stat_src) != 0 || !S_ISREG(stat_src.st_mode)) { - close(fd_src); - return -1; - } - - path_dst = wstrconcat(dest_dir, dest_file); - try_again_dst: - fd_dst = open(path_dst, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); - if (fd_dst == -1) { - if (errno == EINTR) - goto try_again_dst; - werror(_("Could not create target file \"%s\": %s"), path_dst, strerror(errno)); - wfree(path_dst); - close(fd_src); - return -1; - } - - buffer = malloc(buffer_size); /* Don't use wmalloc to avoid the memset(0) we don't need */ - if (buffer == NULL) { - werror(_("could not allocate memory for the copy buffer")); - close(fd_dst); - goto cleanup_and_return_failure; - } - - for (;;) { - ssize_t size_data; - const char *write_ptr; - size_t write_remain; - - try_again_read: - size_data = read(fd_src, buffer, buffer_size); - if (size_data == 0) - break; /* End of File have been reached */ - if (size_data < 0) { - if (errno == EINTR) - goto try_again_read; - werror(_("could not read from file \"%s\": %s"), src_file, strerror(errno)); - close(fd_dst); - goto cleanup_and_return_failure; - } - - write_ptr = buffer; - write_remain = size_data; - while (write_remain > 0) { - ssize_t write_done; - - try_again_write: - write_done = write(fd_dst, write_ptr, write_remain); - if (write_done < 0) { - if (errno == EINTR) - goto try_again_write; - werror(_("could not write data to file \"%s\": %s"), path_dst, strerror(errno)); - close(fd_dst); - goto cleanup_and_return_failure; - } - write_ptr += write_done; - write_remain -= write_done; - } - } - - /* Keep only the permission-related part of the field: */ - permission_dst = stat_src.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX); - if (fchmod(fd_dst, permission_dst) != 0) - wwarning(_("could not set permission 0%03o on file \"%s\": %s"), - permission_dst, path_dst, strerror(errno)); - - if (close(fd_dst) != 0) { - werror(_("could not close the file \"%s\": %s"), path_dst, strerror(errno)); - cleanup_and_return_failure: - free(buffer); - close(fd_src); - unlink(path_dst); - wfree(path_dst); - return -1; - } - - free(buffer); - wfree(path_dst); - close(fd_src); - - return 0; -} diff --git a/WINGs/menuparser.c b/WINGs/menuparser.c index 6d8411cc..894cafd1 100644 --- a/WINGs/menuparser.c +++ b/WINGs/menuparser.c @@ -536,12 +536,14 @@ found_end_define_fname: while (*src != '\0') { idx = 0; if (*src == '~') { - const char *home = wgethomedir(); + const char *home_head = wgethomedir(); + const char *home = home_head;; while (*home != '\0') { if (idx < sizeof(buffer) - 2) buffer[idx++] = *home; home++; } + wfree(home_head); src++; } diff --git a/WINGs/userdefaults.c b/WINGs/userdefaults.c index bc01723c..90b01dd4 100644 --- a/WINGs/userdefaults.c +++ b/WINGs/userdefaults.c @@ -69,13 +69,12 @@ const char *wusergnusteppath(void) } h = wgethomedir(); - if (!h) - return NULL; pathlen = strlen(h); path = wmalloc(pathlen + sizeof(subdir)); strcpy(path, h); strcpy(path + pathlen, subdir); + wfree(h); return path; } diff --git a/WINGs/wcolorpanel.c b/WINGs/wcolorpanel.c index b799e0d0..35443d81 100644 --- a/WINGs/wcolorpanel.c +++ b/WINGs/wcolorpanel.c @@ -2994,10 +2994,13 @@ static void customPaletteMenuNewFromFile(W_ColorPanel * panel) int i; RImage *tmpImg = NULL; - if ((!panel->lastBrowseDir) || (strcmp(panel->lastBrowseDir, "\0") == 0)) - spath = wexpandpath(wgethomedir()); - else + if ((!panel->lastBrowseDir) || (strcmp(panel->lastBrowseDir, "\0") == 0)) { + char *homedir = wgethomedir(); + spath = wexpandpath(homedir); + wfree(homedir); + } else { spath = wexpandpath(panel->lastBrowseDir); + } browseP = WMGetOpenPanel(scr); WMSetFilePanelCanChooseDirectories(browseP, 0); diff --git a/WINGs/wfilepanel.c b/WINGs/wfilepanel.c index c1cb16ac..b71faa64 100644 --- a/WINGs/wfilepanel.c +++ b/WINGs/wfilepanel.c @@ -766,12 +766,10 @@ static void goHome(WMWidget *widget, void *p_panel) /* Parameter not used, but tell the compiler that it is ok */ (void) widget; - /* home is statically allocated. Don't free it! */ home = wgethomedir(); - if (!home) - return; WMSetFilePanelDirectory(panel, home); + wfree(home); } static void handleEvents(XEvent * event, void *data) diff --git a/WPrefs.app/Appearance.c b/WPrefs.app/Appearance.c index 01ddc28e..041b3de5 100644 --- a/WPrefs.app/Appearance.c +++ b/WPrefs.app/Appearance.c @@ -1110,7 +1110,7 @@ static void deleteTexture(WMWidget * w, void *data) static void extractTexture(WMWidget * w, void *data) { _Panel *panel = (_Panel *) data; - char *path; + char *path, *homedir; WMOpenPanel *opanel; WMScreen *scr = WMWidgetScreen(w); @@ -1118,13 +1118,17 @@ static void extractTexture(WMWidget * w, void *data) WMSetFilePanelCanChooseDirectories(opanel, False); WMSetFilePanelCanChooseFiles(opanel, True); - if (WMRunModalFilePanelForDirectory(opanel, panel->parent, wgethomedir(), _("Select File"), NULL)) { + homedir = wgethomedir(); + if (WMRunModalFilePanelForDirectory(opanel, panel->parent, homedir, _("Select File"), NULL)) { path = WMGetFilePanelFileName(opanel); OpenExtractPanelFor(panel); wfree(path); } + if (homedir) { + wfree(homedir); + } } static void changePage(WMWidget * w, void *data) diff --git a/WPrefs.app/TexturePanel.c b/WPrefs.app/TexturePanel.c index f5863a3b..3272013f 100644 --- a/WPrefs.app/TexturePanel.c +++ b/WPrefs.app/TexturePanel.c @@ -627,7 +627,7 @@ static void browseImageCallback(WMWidget *w, void *data) WMSetFilePanelCanChooseFiles(opanel, True); if (!ipath) - ipath = wstrdup(wgethomedir()); + ipath = wgethomedir(); if (WMRunModalFilePanelForDirectory(opanel, panel->win, ipath, _("Open Image"), NULL)) { char *path, *fullpath; diff --git a/util/setstyle.c b/util/setstyle.c index 0883459a..36599ae4 100644 --- a/util/setstyle.c +++ b/util/setstyle.c @@ -465,7 +465,7 @@ int main(int argc, char **argv) } buf[strlen(buf) - 6 /* strlen("/style") */] = '\0'; - homedir = wstrdup(wgethomedir()); + homedir = wgethomedir(); if (strlen(homedir) > 1 && /* this is insane, wgethomedir() returns `/' on error */ strncmp(homedir, buf, strlen(homedir)) == 0) { /* theme pack is under ${HOME}; exchange ${HOME} part diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am index f8fcd540..2a8e3e00 100644 --- a/wutil-rs/Makefile.am +++ b/wutil-rs/Makefile.am @@ -1,6 +1,7 @@ AUTOMAKE_OPTIONS = RUST_SOURCES = \ + src/find_file.rs \ src/lib.rs \ src/memory.rs diff --git a/wutil-rs/src/find_file.rs b/wutil-rs/src/find_file.rs new file mode 100644 index 00000000..5a0dbeee --- /dev/null +++ b/wutil-rs/src/find_file.rs @@ -0,0 +1,221 @@ +//! This module provides approximate reimplementations of file-finding routines +//! from the original WINGs utilities. +//! +//! The [`ffi`] submodule provides functions which may be called directly from C +//! that has not yet been ported to Rust. +//! +//! The original utilities expanded environment variables in path names +//! (expanding `$FOO/bar/baz` to use the value of the environment variable +//! `FOO`) and respected Unix-style denotations of user home directories +//! (resolving `~someuser/foo.txt` to `(home directory of +//! someuser)/foo.txt`. These behaviors have not been preserved. But a path +//! whose first component is `~` will still be resolved relatively to the +//! current user's home directory. +//! +//! Keep in mind that these utilities are not strictly correct as originally +//! designed: a file path that appears valid when it is checked in a subroutine +//! may become invalid if the file is deleted between when the path is checked +//! and when downstream code attempts to open the file. A better design would +//! open the file and return a live file pointer instead of simply returning a +//! path that is likely to work. Future work should redesign this module to +//! avoid this issue. + +use std::{ + env, + ffi::OsStr, + fs::File, + path::{Component, Path, PathBuf}, +}; + +/// If `file` is an absolute path can be opened, returns that path. Paths +/// starting with `~` are treated as absolute, and the user's home directory is +/// substituted for `~` (so `~/foo` becomes `(users's home directory)/foo`). If +/// the user's home directory cannot be determined, `/` is used instead. +/// +/// Returns `None` otherwise. +pub fn absolute(file: &Path) -> Option { + if file.is_absolute() { + return Some(file.to_path_buf()); + } else { + let mut components = file.components(); + if components.next() == Some(Component::Normal(OsStr::new("~"))) { + let mut path = env::home_dir().unwrap_or_else(|| PathBuf::from("/")); + for c in components { + path.push(c); + } + Some(path) + } else { + None + } + } +} + +/// Resolves `file` to a path that can be opened relative to an element of +/// `paths`, or `None` if it cannot be found. If `paths` is empty, an attempt to +/// resolve `file` relative to the current working directory will be made. +pub fn in_paths<'a>(paths: impl Iterator, file: &Path) -> Option { + if file.file_name().map(|f| f.is_empty()).unwrap_or(false) { + return None; + } + let mut paths = paths.peekable(); + + if paths.peek().is_none() { + if let Ok(_) = File::open(file) { + return Some(file.to_path_buf()); + } + return None; + } + + let mut buf = PathBuf::new(); + for parent in paths { + buf.clear(); + buf.push(parent); + buf.push(file); + if let Ok(_) = File::open(&buf) { + return Some(buf); + } + } + None +} + +pub mod ffi { + use super::{absolute, in_paths}; + use crate::memory::alloc_bytes; + + use std::{ + env, + ffi::{CStr, OsStr, c_char, c_int}, + iter, + os::unix::ffi::OsStrExt, + path::{Path, PathBuf}, + ptr, + }; + + fn split_paths(paths: &CStr) -> impl Iterator { + paths.to_bytes().split(|b| *b == b':') + } + + fn to_c_str(p: &Path) -> *mut c_char { + let os_bytes = p.as_os_str().as_encoded_bytes(); + let buf = alloc_bytes(os_bytes.len() + 1); + unsafe { + ptr::copy_nonoverlapping(os_bytes.as_ptr(), buf, os_bytes.len()); + } + buf.cast::() + } + + /// Attempts to find `file` under colon-separated `paths`. Checks if `file` + /// is absolute or prefixed with `~` before attempting to resolve it + /// relatively. If no file can be found, returns NULL. Non-NULL return + /// values must be freed with [`crate::memory::free_bytes`] or + /// [`crate::memory::ffi::wfree`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wfindfile(paths: *const c_char, file: *const c_char) -> *mut c_char { + if file.is_null() { + return ptr::null_mut(); + } + let file = unsafe { CStr::from_ptr(file) }; + let file = Path::new(OsStr::from_bytes(file.to_bytes())); + if let Some(path) = absolute(&file) { + return to_c_str(&path); + } + let path = if paths.is_null() { + in_paths(iter::empty(), file) + } else { + let paths = unsafe { CStr::from_ptr(paths) }; + in_paths( + split_paths(paths).map(|p| Path::new(OsStr::from_bytes(p))), + file, + ) + }; + path.map(|x| to_c_str(x.as_ref())) + .unwrap_or(ptr::null_mut()) + } + + /// Attempts to find `file` under an element of NULL-terminated + /// `path_list`. Checks if `file` is absolute or prefixed with `~` before + /// attempting to resolve it relatively. If no file can be found, returns + /// NULL. Non-NULL return values must be freed with + /// [`crate::memory::free_bytes`] or [`crate::memory::ffi::wfree`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wfindfileinlist( + path_list: *const *const c_char, + file: *const c_char, + ) -> *mut c_char { + if file.is_null() { + return ptr::null_mut(); + } + let file = unsafe { CStr::from_ptr(file) }; + let file = Path::new(OsStr::from_bytes(file.to_bytes())); + if let Some(path) = absolute(&file) { + return to_c_str(&path); + } + + let path = if path_list.is_null() { + in_paths(iter::empty(), file) + } else { + let paths = (0usize..) + .map(|offset| unsafe { path_list.add(offset) }) + .take_while(|&p| unsafe { !(*p).is_null() }) + .map(|p| Path::new(OsStr::from_bytes(unsafe { CStr::from_ptr(*p).to_bytes() }))); + in_paths(paths, file) + }; + + path.map(|x| to_c_str(x.as_ref())) + .unwrap_or(ptr::null_mut()) + } + + /// Attempts to expand `path` if it starts with `~` by replacing the first + /// path element with the user's home directory. Returns NULL if `path` is + /// NULL. Non-NULL return values must be freed with + /// [`crate::memory::free_bytes`] or [`crate::memory::ffi::wfree`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wexpandpath(path: *const c_char) -> *mut c_char { + if path.is_null() { + return ptr::null_mut(); + } + let path = unsafe { CStr::from_ptr(path) }; + let path = Path::new(OsStr::from_bytes(path.to_bytes())); + absolute(path) + .map(|p| to_c_str(&p)) + .unwrap_or_else(|| to_c_str(&path)) + } + + /// Returns the home directory of the current user, or `"/"` if it cannot be + /// determined. The returned value must be freed with + /// [`crate::memory::free_bytes`] or [`crate::memory::ffi::wfree`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wgethomedir() -> *mut c_char { + match env::home_dir() { + Some(x) => to_c_str(x.as_ref()), + None => to_c_str(Path::new("/")), + } + } + + /// Copies `src_file` into `dest_dir/dest_file`. Returns 0 on success, or -1 + /// on error. + /// + /// This is provided solely to support code that has not yet been ported to + /// Rust. Prefer `std::fs::copy` or another utility if you can. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wcopy_file( + dest_dir: *const c_char, + src_file: *const c_char, + dest_file: *const c_char, + ) -> c_int { + if dest_dir.is_null() || src_file.is_null() || dest_file.is_null() { + return -1; + } + let src_file = unsafe { CStr::from_ptr(src_file) }; + let dest_dir = unsafe { CStr::from_ptr(dest_dir) }; + let dest_file = unsafe { CStr::from_ptr(dest_file) }; + let src = Path::new(OsStr::from_bytes(src_file.to_bytes())); + let mut dest = PathBuf::from(OsStr::from_bytes(dest_dir.to_bytes())); + dest.push(OsStr::from_bytes(dest_file.to_bytes())); + if std::fs::copy(src, dest).is_ok() { + return 0; + } else { + return -1; + } + } +} diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs index eb291915..83e49d77 100644 --- a/wutil-rs/src/lib.rs +++ b/wutil-rs/src/lib.rs @@ -1 +1,2 @@ +pub mod find_file; pub mod memory; -- 2.39.5 From 6ede7a5cb09c8a5c64c050e34a327c0e045ea768 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 3 Oct 2025 00:43:56 -0400 Subject: [PATCH 07/39] Reimplement WINGs array.c in Rust. This is another utility that should not be used in any new (Rust) code. (We should prefer Vec or something similar.) This should be removed once dependents are ported to Rust. --- WINGs/Makefile.am | 1 - WINGs/WINGs/WUtil.h | 22 +-- WINGs/array.c | 363 ------------------------------------------- WINGs/handlers.c | 6 +- WINGs/selection.c | 4 +- src/dialog.c | 4 +- src/event.c | 4 +- src/switchpanel.c | 2 +- src/winmenu.c | 2 +- util/Makefile.am | 35 ++++- wutil-rs/Makefile.am | 1 + wutil-rs/src/lib.rs | 1 + 12 files changed, 49 insertions(+), 396 deletions(-) delete mode 100644 WINGs/array.c diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 14fbeccc..308f140c 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -64,7 +64,6 @@ libWINGs_la_SOURCES = \ wwindow.c libWUtil_la_SOURCES = \ - array.c \ bagtree.c \ data.c \ error.c \ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index bf78fa40..b392b305 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -396,7 +396,7 @@ extern const WMHashTableCallbacks WMStringPointerHashCallbacks; /* keys are strings, but they are not copied */ -/* ---[ WINGs/array.c ]--------------------------------------------------- */ +/* ---[ wutil-rs/src/array.rs ]--------------------------------------------------- */ /* * WMArray use an array to store the elements. @@ -418,29 +418,22 @@ WMArray* WMCreateArrayWithDestructor(int initialSize, WMFreeDataProc *destructor WMArray* WMCreateArrayWithArray(WMArray *array); -#define WMDuplicateArray(array) WMCreateArrayWithArray(array) - void WMEmptyArray(WMArray *array); void WMFreeArray(WMArray *array); int WMGetArrayItemCount(WMArray *array); -/* appends other to array. other remains unchanged */ -void WMAppendArray(WMArray *array, WMArray *other); - /* add will place the element at the end of the array */ void WMAddToArray(WMArray *array, void *item); /* insert will increment the index of elements after it by 1 */ void WMInsertInArray(WMArray *array, int index, void *item); -/* replace and set will return the old item WITHOUT calling the +/* set returns the old item WITHOUT calling the * destructor on it even if its available. Free the returned item yourself. */ -void* WMReplaceInArray(WMArray *array, int index, void *item); - -#define WMSetInArray(array, index, item) WMReplaceInArray(array, index, item) +void* WMSetInArray(WMArray *array, int index, void *item); /* delete and remove will remove the elements and cause the elements * after them to decrement their indexes by 1. Also will call the @@ -448,20 +441,21 @@ void* WMReplaceInArray(WMArray *array, int index, void *item); */ int WMDeleteFromArray(WMArray *array, int index); -#define WMRemoveFromArray(array, item) WMRemoveFromArrayMatching(array, NULL, item) +int WMRemoveFromArray(WMArray *array, void *item); int WMRemoveFromArrayMatching(WMArray *array, WMMatchDataProc *match, void *cdata); void* WMGetFromArray(WMArray *array, int index); -#define WMGetFirstInArray(array, item) WMFindInArray(array, NULL, item) - /* pop will return the last element from the array, also removing it * from the array. The destructor is NOT called, even if available. * Free the returned element if needed by yourself */ void* WMPopFromArray(WMArray *array); +/* Like WMFindInArray(array, NULL, item) */ +int WMGetFirstInArray(WMArray *array, void *item); + int WMFindInArray(WMArray *array, WMMatchDataProc *match, void *cdata); int WMCountInArray(WMArray *array, void *item); @@ -475,8 +469,6 @@ void WMSortArray(WMArray *array, WMCompareDataProc *comparer); void WMMapArray(WMArray *array, void (*function)(void*, void*), void *data); -WMArray* WMGetSubarrayWithRange(WMArray* array, WMRange aRange); - void* WMArrayFirst(WMArray *array, WMArrayIterator *iter); void* WMArrayLast(WMArray *array, WMArrayIterator *iter); diff --git a/WINGs/array.c b/WINGs/array.c deleted file mode 100644 index df52358d..00000000 --- a/WINGs/array.c +++ /dev/null @@ -1,363 +0,0 @@ -/* - * Dynamically Resized Array - * - * Authors: Alfredo K. Kojima - * Dan Pascu - * - * This code is released to the Public Domain, but - * proper credit is always appreciated :) - */ - -#include -#include - -#include "WUtil.h" - -#define INITIAL_SIZE 8 -#define RESIZE_INCREMENT 8 - -typedef struct W_Array { - void **items; /* the array data */ - int itemCount; /* # of items in array */ - int allocSize; /* allocated size of array */ - WMFreeDataProc *destructor; /* the destructor to free elements */ -} W_Array; - -WMArray *WMCreateArray(int initialSize) -{ - return WMCreateArrayWithDestructor(initialSize, NULL); -} - -WMArray *WMCreateArrayWithDestructor(int initialSize, WMFreeDataProc * destructor) -{ - WMArray *array; - - array = wmalloc(sizeof(WMArray)); - - if (initialSize <= 0) { - initialSize = INITIAL_SIZE; - } - - array->items = wmalloc(sizeof(void *) * initialSize); - - array->itemCount = 0; - array->allocSize = initialSize; - array->destructor = destructor; - - return array; -} - -WMArray *WMCreateArrayWithArray(WMArray * array) -{ - WMArray *newArray; - - newArray = wmalloc(sizeof(WMArray)); - - newArray->items = wmalloc(sizeof(void *) * array->allocSize); - memcpy(newArray->items, array->items, sizeof(void *) * array->itemCount); - - newArray->itemCount = array->itemCount; - newArray->allocSize = array->allocSize; - newArray->destructor = NULL; - - return newArray; -} - -void WMEmptyArray(WMArray * array) -{ - if (array->destructor) { - while (array->itemCount > 0) { - array->itemCount--; - array->destructor(array->items[array->itemCount]); - } - } - /*memset(array->items, 0, array->itemCount * sizeof(void*)); */ - array->itemCount = 0; -} - -void WMFreeArray(WMArray * array) -{ - if (array == NULL) - return; - - WMEmptyArray(array); - wfree(array->items); - wfree(array); -} - -int WMGetArrayItemCount(WMArray * array) -{ - if (array == NULL) - return 0; - - return array->itemCount; -} - -void WMAppendArray(WMArray * array, WMArray * other) -{ - if (array == NULL || other == NULL) - return; - - if (other->itemCount == 0) - return; - - if (array->itemCount + other->itemCount > array->allocSize) { - array->allocSize += other->allocSize; - array->items = wrealloc(array->items, sizeof(void *) * array->allocSize); - } - - memcpy(array->items + array->itemCount, other->items, sizeof(void *) * other->itemCount); - array->itemCount += other->itemCount; -} - -void WMAddToArray(WMArray * array, void *item) -{ - if (array == NULL) - return; - - if (array->itemCount >= array->allocSize) { - array->allocSize += RESIZE_INCREMENT; - array->items = wrealloc(array->items, sizeof(void *) * array->allocSize); - } - array->items[array->itemCount] = item; - - array->itemCount++; -} - -void WMInsertInArray(WMArray * array, int index, void *item) -{ - if (array == NULL) - return; - - wassertr(index >= 0 && index <= array->itemCount); - - if (array->itemCount >= array->allocSize) { - array->allocSize += RESIZE_INCREMENT; - array->items = wrealloc(array->items, sizeof(void *) * array->allocSize); - } - if (index < array->itemCount) { - memmove(array->items + index + 1, array->items + index, - sizeof(void *) * (array->itemCount - index)); - } - array->items[index] = item; - - array->itemCount++; -} - -void *WMReplaceInArray(WMArray * array, int index, void *item) -{ - void *old; - - if (array == NULL) - return NULL; - - wassertrv(index >= 0 && index <= array->itemCount, NULL); - - /* is it really useful to perform append if index == array->itemCount ? -Dan */ - if (index == array->itemCount) { - WMAddToArray(array, item); - return NULL; - } - - old = array->items[index]; - array->items[index] = item; - - return old; -} - -int WMDeleteFromArray(WMArray * array, int index) -{ - if (array == NULL) - return 0; - - wassertrv(index >= 0 && index < array->itemCount, 0); - - if (array->destructor) { - array->destructor(array->items[index]); - } - - if (index < array->itemCount - 1) { - memmove(array->items + index, array->items + index + 1, - sizeof(void *) * (array->itemCount - index - 1)); - } - - array->itemCount--; - - return 1; -} - -int WMRemoveFromArrayMatching(WMArray * array, WMMatchDataProc * match, void *cdata) -{ - int i; - - if (array == NULL) - return 1; - - if (match != NULL) { - for (i = 0; i < array->itemCount; i++) { - if ((*match) (array->items[i], cdata)) { - WMDeleteFromArray(array, i); - return 1; - } - } - } else { - for (i = 0; i < array->itemCount; i++) { - if (array->items[i] == cdata) { - WMDeleteFromArray(array, i); - return 1; - } - } - } - - return 0; -} - -void *WMGetFromArray(WMArray * array, int index) -{ - if (index < 0 || array == NULL || index >= array->itemCount) - return NULL; - - return array->items[index]; -} - -void *WMPopFromArray(WMArray * array) -{ - if (array == NULL || array->itemCount <= 0) - return NULL; - - array->itemCount--; - - return array->items[array->itemCount]; -} - -int WMFindInArray(WMArray * array, WMMatchDataProc * match, void *cdata) -{ - int i; - - if (array == NULL) - return WANotFound; - - if (match != NULL) { - for (i = 0; i < array->itemCount; i++) { - if ((*match) (array->items[i], cdata)) - return i; - } - } else { - for (i = 0; i < array->itemCount; i++) { - if (array->items[i] == cdata) - return i; - } - } - - return WANotFound; -} - -int WMCountInArray(WMArray * array, void *item) -{ - int i, count; - - if (array == NULL) - return 0; - - for (i = 0, count = 0; i < array->itemCount; i++) { - if (array->items[i] == item) - count++; - } - - return count; -} - -void WMSortArray(WMArray * array, WMCompareDataProc * comparer) -{ - if (array == NULL) - return; - - if (array->itemCount > 1) { /* Don't sort empty or single element arrays */ - qsort(array->items, array->itemCount, sizeof(void *), comparer); - } -} - -void WMMapArray(WMArray * array, void (*function) (void *, void *), void *data) -{ - int i; - - if (array == NULL) - return; - - for (i = 0; i < array->itemCount; i++) { - (*function) (array->items[i], data); - } -} - -WMArray *WMGetSubarrayWithRange(WMArray * array, WMRange aRange) -{ - WMArray *newArray; - - if (aRange.count <= 0 || array == NULL) - return WMCreateArray(0); - - if (aRange.position < 0) - aRange.position = 0; - if (aRange.position >= array->itemCount) - aRange.position = array->itemCount - 1; - if (aRange.position + aRange.count > array->itemCount) - aRange.count = array->itemCount - aRange.position; - - newArray = WMCreateArray(aRange.count); - memcpy(newArray->items, array->items + aRange.position, sizeof(void *) * aRange.count); - newArray->itemCount = aRange.count; - - return newArray; -} - -void *WMArrayFirst(WMArray * array, WMArrayIterator * iter) -{ - if (array == NULL || array->itemCount == 0) { - *iter = WANotFound; - return NULL; - } else { - *iter = 0; - return array->items[0]; - } -} - -void *WMArrayLast(WMArray * array, WMArrayIterator * iter) -{ - if (array == NULL || array->itemCount == 0) { - *iter = WANotFound; - return NULL; - } else { - *iter = array->itemCount - 1; - return array->items[*iter]; - } -} - -void *WMArrayNext(WMArray * array, WMArrayIterator * iter) -{ - if (array == NULL) { - *iter = WANotFound; - return NULL; - } - - if (*iter >= 0 && *iter < array->itemCount - 1) { - return array->items[++(*iter)]; - } else { - *iter = WANotFound; - return NULL; - } -} - -void *WMArrayPrevious(WMArray * array, WMArrayIterator * iter) -{ - if (array == NULL) { - *iter = WANotFound; - return NULL; - } - - if (*iter > 0 && *iter < array->itemCount) { - return array->items[--(*iter)]; - } else { - *iter = WANotFound; - return NULL; - } -} diff --git a/WINGs/handlers.c b/WINGs/handlers.c index 88682867..b2872083 100644 --- a/WINGs/handlers.c +++ b/WINGs/handlers.c @@ -279,7 +279,7 @@ Bool W_CheckIdleHandlers(void) return (idleHandler != NULL && WMGetArrayItemCount(idleHandler) > 0); } - handlerCopy = WMDuplicateArray(idleHandler); + handlerCopy = WMCreateArrayWithArray(idleHandler); WM_ITERATE_ARRAY(handlerCopy, handler, iter) { /* check if the handler still exist or was removed by a callback */ @@ -429,7 +429,7 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) count = poll(fds, nfds + extrafd, timeout); if (count > 0 && nfds > 0) { - WMArray *handlerCopy = WMDuplicateArray(inputHandler); + WMArray *handlerCopy = WMCreateArrayWithArray(inputHandler); int mask; /* use WM_ITERATE_ARRAY() here */ @@ -527,7 +527,7 @@ Bool W_HandleInputEvents(Bool waitForInput, int inputfd) count = select(1 + maxfd, &rset, &wset, &eset, timeoutPtr); if (count > 0 && nfds > 0) { - WMArray *handlerCopy = WMDuplicateArray(inputHandler); + WMArray *handlerCopy = WMCreateArrayWithArray(inputHandler); int mask; /* use WM_ITERATE_ARRAY() here */ diff --git a/WINGs/selection.c b/WINGs/selection.c index 5d5221b8..0d1bc59c 100644 --- a/WINGs/selection.c +++ b/WINGs/selection.c @@ -237,7 +237,7 @@ static void handleRequestEvent(XEvent * event) } /* delete handlers */ - copy = WMDuplicateArray(selHandlers); + copy = WMCreateArrayWithArray(selHandlers); WM_ITERATE_ARRAY(copy, handler, iter) { if (handler && handler->flags.delete_pending) { WMDeleteSelectionHandler(handler->view, handler->selection, handler->timestamp); @@ -300,7 +300,7 @@ static void handleNotifyEvent(XEvent * event) } /* delete callbacks */ - copy = WMDuplicateArray(selCallbacks); + copy = WMCreateArrayWithArray(selCallbacks); WM_ITERATE_ARRAY(copy, handler, iter) { if (handler && handler->flags.delete_pending) { WMDeleteSelectionCallback(handler->view, handler->selection, handler->timestamp); diff --git a/src/dialog.c b/src/dialog.c index 391dda23..a594abcf 100644 --- a/src/dialog.c +++ b/src/dialog.c @@ -367,7 +367,7 @@ static void handleHistoryKeyPress(XEvent * event, void *clientData) case XK_Up: if (p->histpos < WMGetArrayItemCount(p->history) - 1) { if (p->histpos == 0) - wfree(WMReplaceInArray(p->history, 0, WMGetTextFieldText(p->panel->text))); + wfree(WMSetInArray(p->history, 0, WMGetTextFieldText(p->panel->text))); p->histpos++; WMSetTextFieldText(p->panel->text, WMGetFromArray(p->history, p->histpos)); } @@ -468,7 +468,7 @@ int wAdvancedInputDialog(WScreen *scr, const char *title, const char *message, c if (p->panel->result == WAPRDefault) { result = WMGetTextFieldText(p->panel->text); - wfree(WMReplaceInArray(p->history, 0, wstrdup(result))); + wfree(WMSetInArray(p->history, 0, wstrdup(result))); SaveHistory(p->history, filename); } else result = NULL; diff --git a/src/event.c b/src/event.c index 7356a1a2..48666868 100644 --- a/src/event.c +++ b/src/event.c @@ -1796,7 +1796,7 @@ static void handleKeyPress(XEvent * event) } if (wwin->flags.selected && scr->selected_windows) { - scr->shortcutWindows[widx] = WMDuplicateArray(scr->selected_windows); + scr->shortcutWindows[widx] = WMCreateArrayWithArray(scr->selected_windows); /*WMRemoveFromArray(scr->shortcutWindows[index], wwin); WMInsertInArray(scr->shortcutWindows[index], 0, wwin); */ } else { @@ -1816,7 +1816,7 @@ static void handleKeyPress(XEvent * event) if (scr->shortcutWindows[widx]) { WMFreeArray(scr->shortcutWindows[widx]); } - scr->shortcutWindows[widx] = WMDuplicateArray(scr->selected_windows); + scr->shortcutWindows[widx] = WMCreateArrayWithArray(scr->selected_windows); } } diff --git a/src/switchpanel.c b/src/switchpanel.c index cb6cda10..d5260b5e 100644 --- a/src/switchpanel.c +++ b/src/switchpanel.c @@ -132,7 +132,7 @@ static void changeImage(WSwitchPanel *panel, int idecks, int selected, Bool dim, if (flags == desired && !force) return; - WMReplaceInArray(panel->flags, idecks, (void *) (uintptr_t) desired); + WMSetInArray(panel->flags, idecks, (void *) (uintptr_t) desired); if (!panel->bg && !panel->tile && !selected) WMSetFrameRelief(icon, WRFlat); diff --git a/src/winmenu.c b/src/winmenu.c index 422e0663..3680aa79 100644 --- a/src/winmenu.c +++ b/src/winmenu.c @@ -346,7 +346,7 @@ static void makeShortcutCommand(WMenu * menu, WMenuEntry * entry) } if (wwin->flags.selected && scr->selected_windows) { - scr->shortcutWindows[index] = WMDuplicateArray(scr->selected_windows); + scr->shortcutWindows[index] = WMCreateArrayWithArray(scr->selected_windows); /*WMRemoveFromArray(scr->shortcutWindows[index], wwin); WMInsertInArray(scr->shortcutWindows[index], 0, wwin); */ } else { diff --git a/util/Makefile.am b/util/Makefile.am index 0c148702..1fe28f89 100644 --- a/util/Makefile.am +++ b/util/Makefile.am @@ -18,31 +18,50 @@ AM_CPPFLAGS = \ liblist= @LIBRARY_SEARCH_PATH@ @INTLIBS@ -wdwrite_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +wdwrite_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) -wdread_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +wdread_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) wxcopy_LDADD = @XLFLAGS@ @XLIBS@ wxpaste_LDADD = @XLFLAGS@ @XLIBS@ -getstyle_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +getstyle_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) getstyle_SOURCES = getstyle.c fontconv.c common.h setstyle_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ @XLFLAGS@ @XLIBS@ $(liblist) setstyle_SOURCES = setstyle.c fontconv.c common.h -convertfonts_LDADD = $(top_builddir)/WINGs/libWUtil.la $(liblist) +convertfonts_LDADD = \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) convertfonts_SOURCES = convertfonts.c fontconv.c common.h -seticons_LDADD= $(top_builddir)/WINGs/libWUtil.la $(liblist) +seticons_LDADD= \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) -geticonset_LDADD= $(top_builddir)/WINGs/libWUtil.la $(liblist) +geticonset_LDADD= \ + $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ + $(liblist) wmagnify_LDADD = \ $(top_builddir)/WINGs/libWINGs.la \ @@ -52,18 +71,21 @@ wmagnify_LDADD = \ wmsetbg_LDADD = \ $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ $(top_builddir)/WINGs/libWUtil.la \ $(top_builddir)/wrlib/libwraster.la \ @XLFLAGS@ @LIBXINERAMA@ @XLIBS@ @INTLIBS@ wmgenmenu_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ @INTLIBS@ wmgenmenu_SOURCES = wmgenmenu.c wmgenmenu.h wmmenugen_LDADD = \ $(top_builddir)/WINGs/libWUtil.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ @INTLIBS@ wmmenugen_SOURCES = wmmenugen.c wmmenugen.h wmmenugen_misc.c \ @@ -75,6 +97,7 @@ wmiv_CFLAGS = @PANGO_CFLAGS@ @PTHREAD_CFLAGS@ wmiv_LDADD = \ $(top_builddir)/wrlib/libwraster.la \ $(top_builddir)/WINGs/libWINGs.la \ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a \ @XLFLAGS@ @XLIBS@ @GFXLIBS@ \ @PANGO_LIBS@ @PTHREAD_LIBS@ @LIBEXIF@ diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am index 2a8e3e00..8974f2d8 100644 --- a/wutil-rs/Makefile.am +++ b/wutil-rs/Makefile.am @@ -1,6 +1,7 @@ AUTOMAKE_OPTIONS = RUST_SOURCES = \ + src/array.rs \ src/find_file.rs \ src/lib.rs \ src/memory.rs diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs index 83e49d77..970d30a5 100644 --- a/wutil-rs/src/lib.rs +++ b/wutil-rs/src/lib.rs @@ -1,2 +1,3 @@ +pub mod array; pub mod find_file; pub mod memory; -- 2.39.5 From dd361307309b347625e8cab0d39d6e315daed99d Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 3 Oct 2025 00:44:58 -0400 Subject: [PATCH 08/39] Fix const qualifier on strings returned by wgethomedir. --- WINGs/menuparser.c | 4 ++-- WINGs/userdefaults.c | 2 +- WINGs/wfilepanel.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/WINGs/menuparser.c b/WINGs/menuparser.c index 894cafd1..d2070dd5 100644 --- a/WINGs/menuparser.c +++ b/WINGs/menuparser.c @@ -536,8 +536,8 @@ found_end_define_fname: while (*src != '\0') { idx = 0; if (*src == '~') { - const char *home_head = wgethomedir(); - const char *home = home_head;; + char *home_head = wgethomedir(); + char *home = home_head;; while (*home != '\0') { if (idx < sizeof(buffer) - 2) buffer[idx++] = *home; diff --git a/WINGs/userdefaults.c b/WINGs/userdefaults.c index 90b01dd4..78f8f836 100644 --- a/WINGs/userdefaults.c +++ b/WINGs/userdefaults.c @@ -51,7 +51,7 @@ const char *wusergnusteppath(void) static const char subdir[] = "/" GSUSER_SUBDIR; static char *path = NULL; char *gspath; - const char *h; + char *h; int pathlen; if (path) diff --git a/WINGs/wfilepanel.c b/WINGs/wfilepanel.c index b71faa64..1da05355 100644 --- a/WINGs/wfilepanel.c +++ b/WINGs/wfilepanel.c @@ -761,7 +761,7 @@ static void goFloppy(WMWidget *widget, void *p_panel) static void goHome(WMWidget *widget, void *p_panel) { WMFilePanel *panel = p_panel; - const char *home; + char *home; /* Parameter not used, but tell the compiler that it is ok */ (void) widget; -- 2.39.5 From 3beadbb6cb4d5584adfe271d905618293cddd597 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 3 Oct 2025 00:50:02 -0400 Subject: [PATCH 09/39] Forgot to add new array.rs impl. --- wutil-rs/src/array.rs | 447 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 wutil-rs/src/array.rs diff --git a/wutil-rs/src/array.rs b/wutil-rs/src/array.rs new file mode 100644 index 00000000..89288e69 --- /dev/null +++ b/wutil-rs/src/array.rs @@ -0,0 +1,447 @@ +use std::{ffi::c_void, ptr::NonNull}; + +pub struct Array { + items: Vec>, + destructor: Option, +} + +pub mod ffi { + use super::Array; + + use std::{ + ffi::{c_int, c_void}, + ptr::{self, NonNull}, + }; + + pub const NOT_FOUND: c_int = -1; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateArray(initial_size: c_int) -> *mut Array { + let cap = if initial_size < 0 { + 0 + } else { + initial_size as usize + }; + Box::leak(Box::new(Array { + items: Vec::with_capacity(cap), + destructor: None, + })) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateArrayWithDestructor( + initial_size: c_int, + destructor: unsafe extern "C" fn(x: *mut c_void), + ) -> *mut Array { + let cap = if initial_size < 0 { + 0 + } else { + initial_size as usize + }; + Box::leak(Box::new(Array { + items: Vec::with_capacity(cap), + destructor: Some(destructor), + })) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateArrayWithArray(array: *mut Array) -> *mut Array { + if array.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + Box::leak(Box::new(Array { + items: array.items.clone(), + destructor: array.destructor, + })) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMEmptyArray(array: *mut Array) { + if array.is_null() { + return; + } + let array = unsafe { &mut *array }; + if let Some(f) = array.destructor { + for item in &mut array.items { + unsafe { (f)(item.as_ptr()) } + } + } + array.items.clear(); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMFreeArray(array: *mut Array) { + if array.is_null() { + return; + } + unsafe { + WMEmptyArray(array); + let _ = ptr::read(array); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetArrayItemCount(array: *mut Array) -> c_int { + if array.is_null() { + return 0; + } + unsafe { (*array).items.len() as c_int } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAddToArray(array: *mut Array, item: *mut c_void) { + if array.is_null() { + return; + } + if let Some(item) = NonNull::new(item) { + unsafe { + (*array).items.push(item); + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMInsertInArray(array: *mut Array, index: c_int, item: *mut c_void) { + if array.is_null() { + return; + } + if index < 0 { + return; + } + let array = unsafe { &mut (*array).items }; + let index = index as usize; + if index >= array.len() { + return; + } + if let Some(item) = NonNull::new(item) { + array.insert(index, item); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetInArray( + array: *mut Array, + index: c_int, + item: *mut c_void, + ) -> *mut c_void { + if array.is_null() { + return ptr::null_mut(); + } + if index < 0 { + return ptr::null_mut(); + } + let index = index as usize; + + /* is it really useful to perform append if index == array->itemCount ? -Dan */ + if index == unsafe { (*array).items.len() } { + unsafe { + WMAddToArray(array, item); + } + return ptr::null_mut(); + } + + let item = match NonNull::new(item) { + Some(x) => x, + None => return ptr::null_mut(), + }; + let array = unsafe { &mut (*array).items }; + + let old = array[index]; + array[index] = item; + old.as_ptr() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMDeleteFromArray(array: *mut Array, index: c_int) -> c_int { + if array.is_null() { + return 0; + } + let array = unsafe { &mut *array }; + if index < 0 { + return 0; + } + let index = index as usize; + if index >= array.items.len() { + 0 + } else { + let old = array.items.remove(index); + if let Some(f) = array.destructor { + unsafe { + (f)(old.as_ptr()); + } + } + 1 + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRemoveFromArray(array: *mut Array, item: *mut c_void) -> c_int { + unsafe { WMRemoveFromArrayMatching(array, None, item) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRemoveFromArrayMatching( + array: *mut Array, + pred: Option c_int>, + cdata: *mut c_void, + ) -> c_int { + if array.is_null() { + return 1; + } + let array = unsafe { &mut *array }; + let original_len = array.items.len(); + match pred { + Some(f) => array.items.retain(|x| unsafe { f(x.as_ptr(), cdata) != 0 }), + None => array.items.retain(|x| ptr::eq(x.as_ptr(), cdata)), + } + (original_len - array.items.len()) as c_int + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFromArray(array: *mut Array, index: c_int) -> *mut c_void { + if array.is_null() || index < 0 { + return ptr::null_mut(); + } + unsafe { + (*array) + .items + .get(index as usize) + .map(|p| p.as_ptr()) + .unwrap_or(ptr::null_mut()) + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFirstInArray(array: *mut Array, item: *mut c_void) -> c_int { + unsafe { WMFindInArray(array, None, item) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMPopFromArray(array: *mut Array) -> *mut c_void { + if array.is_null() { + return ptr::null_mut(); + } + unsafe { + (*array) + .items + .pop() + .map(|p| p.as_ptr()) + .unwrap_or(ptr::null_mut()) + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMFindInArray( + array: *mut Array, + pred: Option c_int>, + cdata: *mut c_void, + ) -> c_int { + if array.is_null() { + return NOT_FOUND; + } + let array = unsafe { &*array }; + if let Some(f) = pred { + array + .items + .iter() + .enumerate() + .find(|(_, item)| unsafe { f(item.as_ptr(), cdata) != 0 }) + .map(|(i, _)| i as c_int) + .unwrap_or(NOT_FOUND) + } else { + array + .items + .iter() + .enumerate() + .find(|(_, item)| ptr::eq(item.as_ptr(), cdata)) + .map(|(i, _)| i as c_int) + .unwrap_or(NOT_FOUND) + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCountInArray(array: *mut Array, item: *const c_void) -> c_int { + if array.is_null() { + return 0; + } + let array = unsafe { &*array }; + array + .items + .iter() + .filter(|x| ptr::eq(x.as_ptr(), item)) + .count() as c_int + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSortArray( + array: *mut Array, + comparator: unsafe extern "C" fn(a: *const c_void, b: *const c_void) -> c_int, + ) { + if array.is_null() { + return; + } + unsafe { + (*array) + .items + .sort_by(|&a, &b| match comparator(a.as_ptr(), b.as_ptr()).signum() { + -1 => std::cmp::Ordering::Less, + 0 => std::cmp::Ordering::Equal, + 1 => std::cmp::Ordering::Greater, + _ => unreachable!(), + }) + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMMapArray( + array: *mut Array, + f: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void, + data: *mut c_void, + ) { + if array.is_null() { + return; + } + for a in unsafe { &mut (*array).items } { + *a = NonNull::new(unsafe { f(a.as_ptr(), data) }).unwrap(); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMArrayFirst(array: *mut Array, iter: *mut c_int) -> *mut c_void { + if array.is_null() || iter.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + match array.items.get(0) { + None => { + unsafe { + *iter = NOT_FOUND; + } + ptr::null_mut() + } + Some(x) => { + unsafe { + *iter = 0; + } + x.as_ptr() + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMArrayLast(array: *mut Array, iter: *mut c_int) -> *mut c_void { + if array.is_null() || iter.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + match array.items.last() { + None => { + unsafe { + *iter = NOT_FOUND; + } + ptr::null_mut() + } + Some(x) => { + unsafe { + *iter = (array.items.len() - 1) as c_int; + } + x.as_ptr() + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMArrayNext(array: *mut Array, iter: *mut c_int) -> *mut c_void { + if array.is_null() || iter.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + let index = unsafe { *iter }; + if index < 0 { + return ptr::null_mut(); + } + match array.items.get(index as usize) { + Some(i) => { + unsafe { + *iter += 1; + } + i.as_ptr() + } + None => { + unsafe { + *iter = NOT_FOUND; + } + ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMArrayPrevious(array: *mut Array, iter: *mut c_int) -> *mut c_void { + if array.is_null() || iter.is_null() { + return ptr::null_mut(); + } + let array = unsafe { &*array }; + let index = unsafe { *iter }; + if index < 0 { + return ptr::null_mut(); + } + match array.items.get(index as usize) { + Some(i) => { + unsafe { + *iter -= 1; + } + i.as_ptr() + } + None => { + unsafe { + *iter = NOT_FOUND; + } + ptr::null_mut() + } + } + } +} + +#[cfg(test)] +mod test { + use std::{ffi::c_void, ptr}; + + use super::ffi::*; + + #[test] + fn create_destroy_with_size() { + unsafe { + let array = WMCreateArray(10); + assert_eq!((*array).items.len(), 0); + assert!((*array).items.capacity() >= 10); + WMFreeArray(array); + } + } + + #[test] + fn create_push_clear_destroy() { + static mut SENTINEL: *mut c_void = ptr::null_mut(); + unsafe extern "C" fn destructor(item: *mut c_void) { + unsafe { + SENTINEL = item; + } + } + unsafe { + let array = WMCreateArrayWithDestructor(10, destructor); + assert!(SENTINEL.is_null()); + + let mut x = 0xdeadbeefu32; + WMAddToArray(array, (&mut x as *mut u32).cast::()); + assert_eq!(WMGetArrayItemCount(array), 1); + WMEmptyArray(array); + assert!(ptr::eq(SENTINEL, (&x as *const u32).cast::())); + assert_eq!(0xdeadbeefu32, *SENTINEL.cast::()); + + SENTINEL = ptr::null_mut(); + WMFreeArray(array); + assert!(SENTINEL.is_null()); + } + } +} -- 2.39.5 From dea8c36cd5da408062dbbc534424c5c9b52e3eb3 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 3 Oct 2025 01:01:15 -0400 Subject: [PATCH 10/39] Eliminate the unused WINGs function WMCreatePLDataWithBytesNoCopy. --- WINGs/WINGs/WUtil.h | 4 ---- WINGs/proplist.c | 14 -------------- 2 files changed, 18 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index b392b305..727377fd 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -756,10 +756,6 @@ WMPropList* WMCreatePLData(WMData *data); WMPropList* WMCreatePLDataWithBytes(const unsigned char *bytes, unsigned int length); -WMPropList* WMCreatePLDataWithBytesNoCopy(unsigned char *bytes, - unsigned int length, - WMFreeDataProc *destructor); - WMPropList* WMCreatePLArray(WMPropList *elem, ...); WMPropList* WMCreatePLDictionary(WMPropList *key, WMPropList *value, ...); diff --git a/WINGs/proplist.c b/WINGs/proplist.c index e7a764df..9ee6e3a1 100644 --- a/WINGs/proplist.c +++ b/WINGs/proplist.c @@ -942,20 +942,6 @@ WMPropList *WMCreatePLDataWithBytes(const unsigned char *bytes, unsigned int len return plist; } -WMPropList *WMCreatePLDataWithBytesNoCopy(unsigned char *bytes, unsigned int length, WMFreeDataProc * destructor) -{ - WMPropList *plist; - - wassertrv(bytes != NULL, NULL); - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLData; - plist->d.data = WMCreateDataWithBytesNoCopy(bytes, length, destructor); - plist->retainCount = 1; - - return plist; -} - WMPropList *WMCreatePLArray(WMPropList * elem, ...) { WMPropList *plist, *nelem; -- 2.39.5 From 9d07e2d3d8baa6a984873dfb9a9fb0646778cc2b Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 3 Oct 2025 01:02:40 -0400 Subject: [PATCH 11/39] Eliminate the unused WINGs function WMGetSubdataWithRange. --- WINGs/WINGs/WUtil.h | 2 -- WINGs/data.c | 16 ---------------- 2 files changed, 18 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 727377fd..35bcedbf 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -621,8 +621,6 @@ void WMGetDataBytesWithLength(WMData *aData, void *buffer, unsigned length); void WMGetDataBytesWithRange(WMData *aData, void *buffer, WMRange aRange); -WMData* WMGetSubdataWithRange(WMData *aData, WMRange aRange); - /* Testing data */ Bool WMIsDataEqualToData(WMData *aData, WMData *anotherData); diff --git a/WINGs/data.c b/WINGs/data.c index f4489099..f42a5c37 100644 --- a/WINGs/data.c +++ b/WINGs/data.c @@ -195,22 +195,6 @@ void WMGetDataBytesWithRange(WMData * aData, void *buffer, WMRange aRange) memcpy(buffer, (unsigned char *)aData->bytes + aRange.position, aRange.count); } -WMData *WMGetSubdataWithRange(WMData * aData, WMRange aRange) -{ - void *buffer; - WMData *newData; - - if (aRange.count <= 0) - return WMCreateDataWithCapacity(0); - - buffer = wmalloc(aRange.count); - WMGetDataBytesWithRange(aData, buffer, aRange); - newData = WMCreateDataWithBytesNoCopy(buffer, aRange.count, wfree); - newData->format = aData->format; - - return newData; -} - /* Testing data */ Bool WMIsDataEqualToData(WMData * aData, WMData * anotherData) -- 2.39.5 From bbcf40ee4799b56aa7e8ee190d60e0e32d2aae35 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 3 Oct 2025 01:16:32 -0400 Subject: [PATCH 12/39] Eliminate the WINGs function WMCreateDataWithBytesNoCopy. This constructor was only needed in one particular place. We can duplidate the data instead of borrowing it. This ensures that WMData always owns its data segment, which simplifies porting to Rust significantly. --- WINGs/WINGs/WUtil.h | 5 ----- WINGs/data.c | 16 ---------------- WINGs/selection.c | 3 ++- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 35bcedbf..1277f985 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -592,11 +592,6 @@ WMData* WMCreateDataWithLength(unsigned length); WMData* WMCreateDataWithBytes(const void *bytes, unsigned length); -/* destructor is a function called to free the data when releasing the data - * object, or NULL if no freeing of data is necesary. */ -WMData* WMCreateDataWithBytesNoCopy(void *bytes, unsigned length, - WMFreeDataProc *destructor); - WMData* WMCreateDataWithData(WMData *aData); WMData* WMRetainData(WMData *aData); diff --git a/WINGs/data.c b/WINGs/data.c index f42a5c37..9fdcd10e 100644 --- a/WINGs/data.c +++ b/WINGs/data.c @@ -78,22 +78,6 @@ WMData *WMCreateDataWithBytes(const void *bytes, unsigned length) return aData; } -WMData *WMCreateDataWithBytesNoCopy(void *bytes, unsigned length, WMFreeDataProc * destructor) -{ - WMData *aData; - - aData = (WMData *) wmalloc(sizeof(WMData)); - aData->length = length; - aData->capacity = length; - aData->growth = length / 2 > 0 ? length / 2 : 1; - aData->bytes = bytes; - aData->retainCount = 1; - aData->format = 0; - aData->destructor = destructor; - - return aData; -} - WMData *WMCreateDataWithData(WMData * aData) { WMData *newData; diff --git a/WINGs/selection.c b/WINGs/selection.c index 0d1bc59c..63d364b0 100644 --- a/WINGs/selection.c +++ b/WINGs/selection.c @@ -261,8 +261,9 @@ static WMData *getSelectionData(Display * dpy, Window win, Atom where) bpi = bits / 8; - wdata = WMCreateDataWithBytesNoCopy(data, len * bpi, (void *) XFree); + wdata = WMCreateDataWithBytes(data, len * bpi); WMSetDataFormat(wdata, bits); + XFree(data); return wdata; } -- 2.39.5 From 52b0e6b182707b2aace2287db3bbf98b9651d937 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 3 Oct 2025 01:19:07 -0400 Subject: [PATCH 13/39] Drop WMData's destructor field. WMData always owns its data and allocates it with wmalloc, so we can always free it with wfree (and don't need to call anything else). --- WINGs/data.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/WINGs/data.c b/WINGs/data.c index 9fdcd10e..c775451b 100644 --- a/WINGs/data.c +++ b/WINGs/data.c @@ -28,7 +28,6 @@ typedef struct W_Data { unsigned growth; /* How much to grow */ void *bytes; /* Actual data */ unsigned retainCount; - WMFreeDataProc *destructor; int format; /* 0, 8, 16 or 32 */ } W_Data; @@ -50,7 +49,6 @@ WMData *WMCreateDataWithCapacity(unsigned capacity) aData->length = 0; aData->retainCount = 1; aData->format = 0; - aData->destructor = wfree; return aData; } @@ -103,8 +101,8 @@ void WMReleaseData(WMData * aData) aData->retainCount--; if (aData->retainCount > 0) return; - if (aData->bytes != NULL && aData->destructor != NULL) { - aData->destructor(aData->bytes); + if (aData->bytes != NULL) { + wfree(aData->bytes); } wfree(aData); } -- 2.39.5 From 791149fe7053d6159232d883b063106ff8adb11b Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 3 Oct 2025 22:55:52 -0400 Subject: [PATCH 14/39] Correct oversight in how WMMapArray is supposed to work. --- wutil-rs/src/array.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wutil-rs/src/array.rs b/wutil-rs/src/array.rs index 89288e69..a0efe1bd 100644 --- a/wutil-rs/src/array.rs +++ b/wutil-rs/src/array.rs @@ -296,14 +296,16 @@ pub mod ffi { #[unsafe(no_mangle)] pub unsafe extern "C" fn WMMapArray( array: *mut Array, - f: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void, + f: unsafe extern "C" fn(*mut c_void, *mut c_void), data: *mut c_void, ) { if array.is_null() { return; } - for a in unsafe { &mut (*array).items } { - *a = NonNull::new(unsafe { f(a.as_ptr(), data) }).unwrap(); + unsafe { + for a in &mut (*array).items { + (f)(a.as_ptr(), data); + } } } -- 2.39.5 From f2e9123db6f52e0902e1468c59b5ec17eacb77a7 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sat, 4 Oct 2025 12:31:54 -0400 Subject: [PATCH 15/39] Port WINGs data.c (WMData) to Rust. This is not tested super well, but I hope we can be rid of it soon enough. (Once we have WMPropList migrated, WMData and other WINGs data structures should be easier to prune.) --- WINGs/Makefile.am | 1 - WINGs/WINGs/WUtil.h | 18 --- WINGs/data.c | 255 ------------------------------------------- wutil-rs/src/data.rs | 196 +++++++++++++++++++++++++++++++++ wutil-rs/src/lib.rs | 1 + 5 files changed, 197 insertions(+), 274 deletions(-) delete mode 100644 WINGs/data.c create mode 100644 wutil-rs/src/data.rs diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 308f140c..88fcad31 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -65,7 +65,6 @@ libWINGs_la_SOURCES = \ libWUtil_la_SOURCES = \ bagtree.c \ - data.c \ error.c \ error.h \ findfile.c \ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 1277f985..a4390640 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -598,24 +598,10 @@ WMData* WMRetainData(WMData *aData); void WMReleaseData(WMData *aData); -/* Adjusting capacity */ - -void WMSetDataCapacity(WMData *aData, unsigned capacity); - -void WMSetDataLength(WMData *aData, unsigned length); - -void WMIncreaseDataLengthBy(WMData *aData, unsigned extraLength); - /* Accessing data */ const void* WMDataBytes(WMData *aData); -void WMGetDataBytes(WMData *aData, void *buffer); - -void WMGetDataBytesWithLength(WMData *aData, void *buffer, unsigned length); - -void WMGetDataBytesWithRange(WMData *aData, void *buffer, WMRange aRange); - /* Testing data */ Bool WMIsDataEqualToData(WMData *aData, WMData *anotherData); @@ -630,10 +616,6 @@ void WMAppendData(WMData *aData, WMData *anotherData); /* Modifying data */ -void WMReplaceDataBytesInRange(WMData *aData, WMRange aRange, const void *bytes); - -void WMResetDataBytesInRange(WMData *aData, WMRange aRange); - void WMSetData(WMData *aData, WMData *anotherData); diff --git a/WINGs/data.c b/WINGs/data.c deleted file mode 100644 index c775451b..00000000 --- a/WINGs/data.c +++ /dev/null @@ -1,255 +0,0 @@ -/* - * WINGs WMData function library - * - * Copyright (c) 1999-2003 Dan Pascu - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ - -#include -#include "WUtil.h" - -typedef struct W_Data { - unsigned length; /* How many bytes we have */ - unsigned capacity; /* How many bytes it can hold */ - unsigned growth; /* How much to grow */ - void *bytes; /* Actual data */ - unsigned retainCount; - int format; /* 0, 8, 16 or 32 */ -} W_Data; - -/* Creating and destroying data objects */ - -WMData *WMCreateDataWithCapacity(unsigned capacity) -{ - WMData *aData; - - aData = (WMData *) wmalloc(sizeof(WMData)); - - if (capacity > 0) - aData->bytes = wmalloc(capacity); - else - aData->bytes = NULL; - - aData->capacity = capacity; - aData->growth = capacity / 2 > 0 ? capacity / 2 : 1; - aData->length = 0; - aData->retainCount = 1; - aData->format = 0; - - return aData; -} - -WMData *WMCreateDataWithLength(unsigned length) -{ - WMData *aData; - - aData = WMCreateDataWithCapacity(length); - if (length > 0) { - aData->length = length; - } - - return aData; -} - -WMData *WMCreateDataWithBytes(const void *bytes, unsigned length) -{ - WMData *aData; - - aData = WMCreateDataWithCapacity(length); - aData->length = length; - memcpy(aData->bytes, bytes, length); - - return aData; -} - -WMData *WMCreateDataWithData(WMData * aData) -{ - WMData *newData; - - if (aData->length > 0) { - newData = WMCreateDataWithBytes(aData->bytes, aData->length); - } else { - newData = WMCreateDataWithCapacity(0); - } - newData->format = aData->format; - - return newData; -} - -WMData *WMRetainData(WMData * aData) -{ - aData->retainCount++; - return aData; -} - -void WMReleaseData(WMData * aData) -{ - aData->retainCount--; - if (aData->retainCount > 0) - return; - if (aData->bytes != NULL) { - wfree(aData->bytes); - } - wfree(aData); -} - -/* Adjusting capacity */ - -void WMSetDataCapacity(WMData * aData, unsigned capacity) -{ - if (aData->capacity != capacity) { - aData->bytes = wrealloc(aData->bytes, capacity); - aData->capacity = capacity; - aData->growth = capacity / 2 > 0 ? capacity / 2 : 1; - } - if (aData->length > capacity) { - aData->length = capacity; - } -} - -void WMSetDataLength(WMData * aData, unsigned length) -{ - if (length > aData->capacity) { - WMSetDataCapacity(aData, length); - } - if (length > aData->length) { - memset((unsigned char *)aData->bytes + aData->length, 0, length - aData->length); - } - aData->length = length; -} - -void WMSetDataFormat(WMData * aData, unsigned format) -{ - aData->format = format; -} - -void WMIncreaseDataLengthBy(WMData * aData, unsigned extraLength) -{ - WMSetDataLength(aData, aData->length + extraLength); -} - -/* Accessing data */ - -const void *WMDataBytes(WMData * aData) -{ - return aData->bytes; -} - -void WMGetDataBytes(WMData * aData, void *buffer) -{ - wassertr(aData->length > 0); - - memcpy(buffer, aData->bytes, aData->length); -} - -unsigned WMGetDataFormat(WMData * aData) -{ - return aData->format; -} - -void WMGetDataBytesWithLength(WMData * aData, void *buffer, unsigned length) -{ - wassertr(aData->length > 0); - wassertr(length <= aData->length); - - memcpy(buffer, aData->bytes, length); -} - -void WMGetDataBytesWithRange(WMData * aData, void *buffer, WMRange aRange) -{ - wassertr(aRange.position < aData->length); - wassertr(aRange.count <= aData->length - aRange.position); - - memcpy(buffer, (unsigned char *)aData->bytes + aRange.position, aRange.count); -} - -/* Testing data */ - -Bool WMIsDataEqualToData(WMData * aData, WMData * anotherData) -{ - if (aData->length != anotherData->length) - return False; - else if (!aData->bytes && !anotherData->bytes) /* both are empty */ - return True; - else if (!aData->bytes || !anotherData->bytes) /* one of them is empty */ - return False; - return (memcmp(aData->bytes, anotherData->bytes, aData->length) == 0); -} - -unsigned WMGetDataLength(WMData * aData) -{ - return aData->length; -} - -/* Adding data */ -void WMAppendDataBytes(WMData * aData, const void *bytes, unsigned length) -{ - unsigned oldLength = aData->length; - unsigned newLength = oldLength + length; - - if (newLength > aData->capacity) { - unsigned nextCapacity = aData->capacity + aData->growth; - unsigned nextGrowth = aData->capacity ? aData->capacity : 1; - - while (nextCapacity < newLength) { - unsigned tmp = nextCapacity + nextGrowth; - - nextGrowth = nextCapacity; - nextCapacity = tmp; - } - WMSetDataCapacity(aData, nextCapacity); - aData->growth = nextGrowth; - } - memcpy((unsigned char *)aData->bytes + oldLength, bytes, length); - aData->length = newLength; -} - -void WMAppendData(WMData * aData, WMData * anotherData) -{ - if (anotherData->length > 0) - WMAppendDataBytes(aData, anotherData->bytes, anotherData->length); -} - -/* Modifying data */ - -void WMReplaceDataBytesInRange(WMData * aData, WMRange aRange, const void *bytes) -{ - wassertr(aRange.position < aData->length); - wassertr(aRange.count <= aData->length - aRange.position); - - memcpy((unsigned char *)aData->bytes + aRange.position, bytes, aRange.count); -} - -void WMResetDataBytesInRange(WMData * aData, WMRange aRange) -{ - wassertr(aRange.position < aData->length); - wassertr(aRange.count <= aData->length - aRange.position); - - memset((unsigned char *)aData->bytes + aRange.position, 0, aRange.count); -} - -void WMSetData(WMData * aData, WMData * anotherData) -{ - unsigned length = anotherData->length; - - WMSetDataCapacity(aData, length); - if (length > 0) - memcpy(aData->bytes, anotherData->bytes, length); - aData->length = length; -} - -/* Storing data */ diff --git a/wutil-rs/src/data.rs b/wutil-rs/src/data.rs new file mode 100644 index 00000000..aff4788e --- /dev/null +++ b/wutil-rs/src/data.rs @@ -0,0 +1,196 @@ +//! Self-owning shared data segment. + +use std::{cell::RefCell, rc::Rc}; + +#[derive(Clone, Copy, Debug)] +pub enum Format { + Z = 0, + E = 8, + S = 16, + T = 32, +} + +/// Reference-counted, self-owned, dynamically sized chunk of bytes. +/// +/// In the original WINGs, this type either owned or borrowed a data buffer and +/// had some associated metadata. In Rust, this is little more than a thin +/// wrapper around an `Rc>>`. It is mostly used by proplists, +/// and it should be done away with once its dependents have been ported to +/// Rust. +pub struct Data(Rc>); + +struct Inner { + bytes: Vec, + format: Format, +} + +pub mod ffi { + use super::{Data, Format, Inner}; + + use std::{ + cell::RefCell, + ffi::{c_int, c_uint, c_void}, + ptr, + rc::Rc, + }; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateDataWithCapacity(capacity: c_uint) -> *mut Data { + Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner { + bytes: Vec::with_capacity(capacity as usize), + format: Format::Z, + }))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateDataWithLength(length: c_uint) -> *mut Data { + Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner { + bytes: vec![0; length as usize], + format: Format::Z, + }))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateDataWithBytes( + bytes: *const c_void, + length: c_uint, + ) -> *mut Data { + let bytes = unsafe { &*ptr::slice_from_raw_parts(bytes.cast::(), length as usize) }; + let bytes = Vec::from(bytes); + Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner { + bytes, + format: Format::Z, + }))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateDataWithData(data: *mut Data) -> *mut Data { + if data.is_null() { + return ptr::null_mut(); + } + let data = unsafe { &*data }; + Box::leak(Box::new(Data(Rc::new(RefCell::new(Inner { + bytes: data.0.borrow().bytes.clone(), + format: data.0.borrow().format, + }))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRetainData(data: *mut Data) -> *mut Data { + if data.is_null() { + return ptr::null_mut(); + } + let data = unsafe { &*data }; + Box::leak(Box::new(Data(data.0.clone()))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMReleaseData(data: *mut Data) { + if data.is_null() { + return; + } + let _ = unsafe { ptr::read(data) }; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMDataBytes(data: *mut Data) -> *const c_void { + if data.is_null() { + return ptr::null(); + } + + unsafe { (*data).0.borrow().bytes.as_ptr().cast::() } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMIsDataEqualToData(a: *mut Data, b: *mut Data) -> c_int { + if a.is_null() || b.is_null() { + return 0; + } + if ptr::eq(a, b) { + return 1; + } + let a = unsafe { &*a }; + let b = unsafe { &*b }; + (a.0.borrow().bytes == b.0.borrow().bytes) as c_int + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetDataLength(data: *mut Data) -> c_uint { + if data.is_null() { + return 0; + } + unsafe { (*data).0.borrow().bytes.len() as c_uint } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAppendDataBytes( + data: *mut Data, + bytes: *const c_void, + length: c_uint, + ) { + if data.is_null() || bytes.is_null() || length == 0 { + return; + } + let data = unsafe { &mut *data }; + let bytes = unsafe { &*ptr::slice_from_raw_parts(bytes.cast::(), length as usize) }; + data.0.borrow_mut().bytes.extend_from_slice(bytes); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAppendData(data: *mut Data, ext: *mut Data) { + if data.is_null() || ext.is_null() { + return; + } + let data = unsafe { &mut *data }; + let ext = unsafe { &*ext }; + data.0 + .borrow_mut() + .bytes + .extend_from_slice(&ext.0.borrow().bytes); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetData(data: *mut Data, other: *mut Data) { + if data.is_null() || other.is_null() { + return; + } + let data = unsafe { &mut *data }; + let other = unsafe { &*other }; + data.0 + .borrow_mut() + .bytes + .copy_from_slice(&other.0.borrow().bytes); + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetDataFormat(data: *mut Data) -> c_uint { + if data.is_null() { + return 0; + } + return unsafe { + match (*data).0.borrow().format { + Format::Z => 0, + Format::E => 8, + Format::S => 16, + Format::T => 32, + } + }; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSetDataFormat(data: *mut Data, format: c_uint) { + if data.is_null() { + return; + } + let format = match format { + 0 => Format::Z, + 8 => Format::E, + 16 => Format::S, + 32 => Format::T, + _ => return, + }; + unsafe { + (*data).0.borrow_mut().format = format; + } + } +} diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs index 970d30a5..5caabff8 100644 --- a/wutil-rs/src/lib.rs +++ b/wutil-rs/src/lib.rs @@ -1,3 +1,4 @@ pub mod array; +pub mod data; pub mod find_file; pub mod memory; -- 2.39.5 From 5b593fb19a2c4481b04d8ee72bb744608d07bcc8 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 16 Oct 2025 09:38:01 -0400 Subject: [PATCH 16/39] Expose the GSUSER_SUBDIR preprocessor symbol to Rust. This symbol's value must be known to port `wmkdirhier` and `wrmdirhier` from `proplist.c` to Rust. This change introduces a basic C library under wutil-rs that is linked into the Rust code to expose preprocessor symbols and other Autotools configuration decisions to Rust. See the rust rewrite notes at the top of `wutil-rs/src/defines.rs` for further thoughts. --- wutil-rs/Cargo.toml | 3 +++ wutil-rs/build.rs | 8 ++++++++ wutil-rs/src/defines.c | 5 +++++ wutil-rs/src/defines.rs | 45 +++++++++++++++++++++++++++++++++++++++++ wutil-rs/src/lib.rs | 1 + 5 files changed, 62 insertions(+) create mode 100644 wutil-rs/build.rs create mode 100644 wutil-rs/src/defines.c create mode 100644 wutil-rs/src/defines.rs diff --git a/wutil-rs/Cargo.toml b/wutil-rs/Cargo.toml index 29c8b68b..9eff16fb 100644 --- a/wutil-rs/Cargo.toml +++ b/wutil-rs/Cargo.toml @@ -5,3 +5,6 @@ edition = "2024" [lib] crate-type = ["staticlib"] + +[build-dependencies] +cc = "1.0" diff --git a/wutil-rs/build.rs b/wutil-rs/build.rs new file mode 100644 index 00000000..58c269c3 --- /dev/null +++ b/wutil-rs/build.rs @@ -0,0 +1,8 @@ +use cc; + +fn main() { + cc::Build::new() + .file("src/defines.c") + .compile("defines"); + println!("cargo::rerun-if-changed=src/defines.c"); +} diff --git a/wutil-rs/src/defines.c b/wutil-rs/src/defines.c new file mode 100644 index 00000000..e75292f9 --- /dev/null +++ b/wutil-rs/src/defines.c @@ -0,0 +1,5 @@ +#include "../../config-paths.h" + +const char *get_GSUSER_SUBDIR() { + return GSUSER_SUBDIR; +} diff --git a/wutil-rs/src/defines.rs b/wutil-rs/src/defines.rs new file mode 100644 index 00000000..678c5883 --- /dev/null +++ b/wutil-rs/src/defines.rs @@ -0,0 +1,45 @@ +//! Lookup functions for preprocessor symbols. +//! +//! Functions in this module may be called to get the value of various +//! preprocessor symbols that are available on the C side of things. +//! +//! ## Rust rewrite notes +//! +//! Until we move away from autootols entirely, we might be stuck with this as +//! along as it makes sense to keep the configure script as the main entrypoint +//! for compile-time configuration. + +use std::ffi::{c_char, CStr}; + +// Functions defined in src/defines.c. +unsafe extern "C" { + fn get_GSUSER_SUBDIR() -> *const c_char; +} + +/// Returns the value of the GSUSER_SUBDIR preprocessor symbol defined at +/// Autotools configuration time prior to compilation. This is the final +/// component of the root path where user data files are stored (usually +/// `GNUstep`, as in `$HOME/GNUstep`). Returns `None` if this value cannot be +/// determined or is empty. +pub fn gsuser_subdir() -> Option { + let s = unsafe { get_GSUSER_SUBDIR() }; + if s.is_null() { + return None; + } + let s = unsafe { CStr::from_ptr(s) }; + if s.is_empty() { + return None; + } + String::from_utf8(s.to_bytes().iter().copied().collect()).ok() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn gsuser_subdir_is_set() { + let s = gsuser_subdir().unwrap(); + assert!(!s.is_empty()); + } +} diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs index 5caabff8..081e4412 100644 --- a/wutil-rs/src/lib.rs +++ b/wutil-rs/src/lib.rs @@ -1,4 +1,5 @@ pub mod array; pub mod data; +pub mod defines; pub mod find_file; pub mod memory; -- 2.39.5 From f3961ba66f1ee716936181478ebca4c8c6ba61ae Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 16 Oct 2025 21:33:47 -0400 Subject: [PATCH 17/39] Reimplement the PropList data structure in Rust. While this large change has some unit tests, it has not been integration tested thoroughly. Removing the global case insensitivity flag may be an issue in particular. A few of PropList API functions have been modified (mostly to get rid of varargs). The definition of one such function has been left in C for cleanup later. --- WINGs/WINGs/WUtil.h | 34 +- WINGs/proplist.c | 1834 +----------------------------- WINGs/userdefaults.c | 37 +- WPrefs.app/Appearance.c | 4 +- WPrefs.app/HotCornerShortcuts.c | 2 +- WPrefs.app/Paths.c | 4 +- WPrefs.app/WPrefs.c | 4 +- WPrefs.app/main.c | 7 +- src/appicon.c | 2 +- src/defaults.c | 7 +- src/dialog.c | 2 +- src/dock.c | 20 +- src/menu.c | 5 +- src/screen.c | 9 +- src/session.c | 37 +- src/wdefaults.c | 49 +- src/winspector.c | 15 +- src/workspace.c | 4 +- util/convertfonts.c | 7 +- util/geticonset.c | 2 +- util/getstyle.c | 9 +- util/setstyle.c | 7 +- util/wdwrite.c | 2 +- util/wmsetbg.c | 4 +- wutil-rs/Cargo.toml | 5 + wutil-rs/Makefile.am | 5 +- wutil-rs/src/data.rs | 13 + wutil-rs/src/find_file.rs | 196 +++- wutil-rs/src/lib.rs | 1 + wutil-rs/src/prop_list.rs | 871 ++++++++++++++ wutil-rs/src/prop_list/parser.rs | 1463 ++++++++++++++++++++++++ wutil-rs/src/prop_list/writer.rs | 722 ++++++++++++ 32 files changed, 3417 insertions(+), 1966 deletions(-) create mode 100644 wutil-rs/src/prop_list.rs create mode 100644 wutil-rs/src/prop_list/parser.rs create mode 100644 wutil-rs/src/prop_list/writer.rs diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index a4390640..9475e2b4 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -249,6 +249,9 @@ char* wgethomedir(void); /* ---[ WINGs/proplist.c ]------------------------------------------------ */ +/* + * Creates the directory path and all its parents. + */ int wmkdirhier(const char *path); int wrmdirhier(const char *path); @@ -719,11 +722,13 @@ void WMEnqueueCoalesceNotification(WMNotificationQueue *queue, unsigned coalesceMask); -/* ---[ WINGs/proplist.c ]------------------------------------------------ */ - /* Property Lists handling */ -void WMPLSetCaseSensitive(Bool caseSensitive); +/* ---[ WINGs/proplist.c ]------------------------------------------------ */ + +WMPropList* WMCreatePLArray(WMPropList *elem, ...); + +/* ---[ wutil-rs/src/prop_list.rs ]--------------------------------------- */ WMPropList* WMCreatePLString(const char *str); @@ -731,9 +736,13 @@ WMPropList* WMCreatePLData(WMData *data); WMPropList* WMCreatePLDataWithBytes(const unsigned char *bytes, unsigned int length); -WMPropList* WMCreatePLArray(WMPropList *elem, ...); +WMPropList* WMCreatePLArrayFromSlice(WMPropList *elems, unsigned int length); -WMPropList* WMCreatePLDictionary(WMPropList *key, WMPropList *value, ...); +WMPropList* WMCreateEmptyPLArray(); + +WMPropList* WMCreatePLDictionary(WMPropList *key, WMPropList *value); + +WMPropList* WMCreateEmptyPLDictionary(); WMPropList* WMRetainPropList(WMPropList *plist); @@ -782,14 +791,6 @@ Bool WMIsPropListEqualTo(WMPropList *plist, WMPropList *other); /* Returns a reference. Do not free it! */ char* WMGetFromPLString(WMPropList *plist); -/* Returns a reference. Do not free it! */ -WMData* WMGetFromPLData(WMPropList *plist); - -/* Returns a reference. Do not free it! */ -const unsigned char* WMGetPLDataBytes(WMPropList *plist); - -int WMGetPLDataLength(WMPropList *plist); - /* Returns a reference. */ WMPropList* WMGetFromPLArray(WMPropList *plist, int index); @@ -797,14 +798,9 @@ WMPropList* WMGetFromPLArray(WMPropList *plist, int index); WMPropList* WMGetFromPLDictionary(WMPropList *plist, WMPropList *key); /* Returns a PropList array with all the dictionary keys. Release it when - * you're done. Keys in array are retained from the original dictionary - * not copied and need NOT to be released individually. */ + * you're done. */ WMPropList* WMGetPLDictionaryKeys(WMPropList *plist); -/* Creates only the first level deep object. All the elements inside are - * retained from the original */ -WMPropList* WMShallowCopyPropList(WMPropList *plist); - /* Makes a completely separate replica of the original proplist */ WMPropList* WMDeepCopyPropList(WMPropList *plist); diff --git a/WINGs/proplist.c b/WINGs/proplist.c index 9ee6e3a1..4a574cd6 100644 --- a/WINGs/proplist.c +++ b/WINGs/proplist.c @@ -1,961 +1,21 @@ - -#include -#include - -#include -#include -#include #include -#include -#include -#include -#include -#include #include "WUtil.h" -#include "wconfig.h" - -typedef enum { - WPLString = 0x57504c01, - WPLData = 0x57504c02, - WPLArray = 0x57504c03, - WPLDictionary = 0x57504c04 -} WPLType; - -typedef struct W_PropList { - WPLType type; - - union { - char *string; - WMData *data; - WMArray *array; - WMHashTable *dict; - } d; - - int retainCount; -} W_PropList; - -typedef struct PLData { - const char *ptr; - int pos; - const char *filename; - int lineNumber; -} PLData; - -typedef struct StringBuffer { - char *str; - int size; -} StringBuffer; - -static unsigned hashPropList(const void *param); -static WMPropList *getPLString(PLData * pldata); -static WMPropList *getPLQString(PLData * pldata); -static WMPropList *getPLData(PLData * pldata); -static WMPropList *getPLArray(PLData * pldata); -static WMPropList *getPLDictionary(PLData * pldata); -static WMPropList *getPropList(PLData * pldata); - -typedef Bool(*isEqualFunc) (const void *, const void *); - -static const WMHashTableCallbacks WMPropListHashCallbacks = { - hashPropList, - (isEqualFunc) WMIsPropListEqualTo, - NULL, - NULL -}; - -static Bool caseSensitive = True; - -#define BUFFERSIZE 8192 -#define BUFFERSIZE_INCREMENT 1024 - -#if 0 -# define DPUT(s) puts(s) -#else -# define DPUT(s) -#endif - -#define COMPLAIN(pld, msg) wwarning(_("syntax error in %s %s, line %i: %s"),\ - (pld)->filename ? "file" : "PropList",\ - (pld)->filename ? (pld)->filename : "description",\ - (pld)->lineNumber, msg) - -#define ISSTRINGABLE(c) (isalnum(c) || (c)=='.' || (c)=='_' || (c)=='/' \ - || (c)=='+') - -#define CHECK_BUFFER_SIZE(buf, ptr) \ - if ((ptr) >= (buf).size-1) {\ - (buf).size += BUFFERSIZE_INCREMENT;\ - (buf).str = wrealloc((buf).str, (buf).size);\ - } - -#define inrange(ch, min, max) ((ch)>=(min) && (ch)<=(max)) -#define noquote(ch) (inrange(ch, 'a', 'z') || inrange(ch, 'A', 'Z') || inrange(ch, '0', '9') || ((ch)=='_') || ((ch)=='.') || ((ch)=='$')) -#define charesc(ch) (inrange(ch, 0x07, 0x0c) || ((ch)=='"') || ((ch)=='\\')) -#define numesc(ch) (((ch)<=0x06) || inrange(ch, 0x0d, 0x1f) || ((ch)>0x7e)) -#define ishexdigit(ch) (inrange(ch, 'a', 'f') || inrange(ch, 'A', 'F') || inrange(ch, '0', '9')) -#define char2num(ch) (inrange(ch,'0','9') ? ((ch)-'0') : (inrange(ch,'a','f') ? ((ch)-0x57) : ((ch)-0x37))) -#define num2char(num) ((num) < 0xa ? ((num)+'0') : ((num)+0x57)) - -#define MaxHashLength 64 - -static unsigned hashPropList(const void *param) -{ - WMPropList *plist= (WMPropList *) param; - unsigned ret = 0; - unsigned ctr = 0; - const char *key; - int i, len; - - switch (plist->type) { - case WPLString: - key = plist->d.string; - len = WMIN(strlen(key), MaxHashLength); - for (i = 0; i < len; i++) { - ret ^= tolower(key[i]) << ctr; - ctr = (ctr + 1) % sizeof(char *); - } - /*while (*key) { - ret ^= tolower(*key++) << ctr; - ctr = (ctr + 1) % sizeof (char *); - } */ - break; - - case WPLData: - key = WMDataBytes(plist->d.data); - len = WMIN(WMGetDataLength(plist->d.data), MaxHashLength); - for (i = 0; i < len; i++) { - ret ^= key[i] << ctr; - ctr = (ctr + 1) % sizeof(char *); - } - break; - - default: - wwarning(_("Only string or data is supported for a proplist dictionary key")); - wassertrv(False, 0); - break; - } - - return ret; -} - -static WMPropList *retainPropListByCount(WMPropList * plist, int count) -{ - WMPropList *key, *value; - WMHashEnumerator e; - int i; - - plist->retainCount += count; - - switch (plist->type) { - case WPLString: - case WPLData: - break; - case WPLArray: - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - retainPropListByCount(WMGetFromArray(plist->d.array, i), count); - } - break; - case WPLDictionary: - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - retainPropListByCount(key, count); - retainPropListByCount(value, count); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return plist; -} - -static void releasePropListByCount(WMPropList * plist, int count) -{ - WMPropList *key, *value; - WMHashEnumerator e; - int i; - - plist->retainCount -= count; - - switch (plist->type) { - case WPLString: - if (plist->retainCount < 1) { - wfree(plist->d.string); - wfree(plist); - } - break; - case WPLData: - if (plist->retainCount < 1) { - WMReleaseData(plist->d.data); - wfree(plist); - } - break; - case WPLArray: - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - releasePropListByCount(WMGetFromArray(plist->d.array, i), count); - } - if (plist->retainCount < 1) { - WMFreeArray(plist->d.array); - wfree(plist); - } - break; - case WPLDictionary: - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - releasePropListByCount(key, count); - releasePropListByCount(value, count); - } - if (plist->retainCount < 1) { - WMFreeHashTable(plist->d.dict); - wfree(plist); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertr(False); - break; - } -} - -static char *dataDescription(WMPropList * plist) -{ - const unsigned char *data; - char *retVal; - int i, j, length; - - data = WMDataBytes(plist->d.data); - length = WMGetDataLength(plist->d.data); - - retVal = (char *)wmalloc(2 * length + length / 4 + 3); - - retVal[0] = '<'; - for (i = 0, j = 1; i < length; i++) { - retVal[j++] = num2char((data[i] >> 4) & 0x0f); - retVal[j++] = num2char(data[i] & 0x0f); - if ((i & 0x03) == 3 && i != length - 1) { - /* if we've just finished a 32-bit int, add a space */ - retVal[j++] = ' '; - } - } - retVal[j++] = '>'; - retVal[j] = '\0'; - - return retVal; -} - -static char *stringDescription(WMPropList * plist) -{ - const char *str; - char *retVal, *sPtr, *dPtr; - int len, quote; - unsigned char ch; - - str = plist->d.string; - - if (strlen(str) == 0) { - return wstrdup("\"\""); - } - - /* FIXME: make this work with unichars. */ - - quote = 0; - sPtr = (char *)str; - len = 0; - while ((ch = *sPtr)) { - if (!noquote(ch)) { - quote = 1; - if (charesc(ch)) - len++; - else if (numesc(ch)) - len += 3; - } - sPtr++; - len++; - } - - if (quote) - len += 2; - - retVal = (char *)wmalloc(len + 1); - - sPtr = (char *)str; - dPtr = retVal; - - if (quote) - *dPtr++ = '"'; - - while ((ch = *sPtr)) { - if (charesc(ch)) { - *(dPtr++) = '\\'; - switch (ch) { - case '\a': - *dPtr = 'a'; - break; - case '\b': - *dPtr = 'b'; - break; - case '\t': - *dPtr = 't'; - break; - case '\n': - *dPtr = 'n'; - break; - case '\v': - *dPtr = 'v'; - break; - case '\f': - *dPtr = 'f'; - break; - default: - *dPtr = ch; /* " or \ */ - } - } else if (numesc(ch)) { - *(dPtr++) = '\\'; - *(dPtr++) = '0' + ((ch >> 6) & 07); - *(dPtr++) = '0' + ((ch >> 3) & 07); - *dPtr = '0' + (ch & 07); - } else { - *dPtr = ch; - } - sPtr++; - dPtr++; - } - - if (quote) - *dPtr++ = '"'; - - *dPtr = '\0'; - - return retVal; -} - -static char *description(WMPropList * plist) -{ - WMPropList *key, *val; - char *retstr = NULL; - char *str, *tmp, *skey, *sval; - WMHashEnumerator e; - int i; - - switch (plist->type) { - case WPLString: - retstr = stringDescription(plist); - break; - case WPLData: - retstr = dataDescription(plist); - break; - case WPLArray: - retstr = wstrdup("("); - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - str = description(WMGetFromArray(plist->d.array, i)); - if (i == 0) { - retstr = wstrappend(retstr, str); - } else { - tmp = (char *)wmalloc(strlen(retstr) + strlen(str) + 3); - sprintf(tmp, "%s, %s", retstr, str); - wfree(retstr); - retstr = tmp; - } - wfree(str); - } - retstr = wstrappend(retstr, ")"); - break; - case WPLDictionary: - retstr = wstrdup("{"); - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&val, (void **)&key)) { - skey = description(key); - sval = description(val); - tmp = (char *)wmalloc(strlen(retstr) + strlen(skey) + strlen(sval) + 5); - sprintf(tmp, "%s%s = %s;", retstr, skey, sval); - wfree(skey); - wfree(sval); - wfree(retstr); - retstr = tmp; - } - retstr = wstrappend(retstr, "}"); - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return retstr; -} - -static char *indentedDescription(WMPropList * plist, int level) -{ - WMPropList *key, *val; - char *retstr = NULL; - char *str, *tmp, *skey, *sval; - WMHashEnumerator e; - int i; - - if (plist->type == WPLArray /* || plist->type==WPLDictionary */ ) { - retstr = description(plist); - - if (retstr && ((2 * (level + 1) + strlen(retstr)) <= 77)) { - return retstr; - } else if (retstr) { - wfree(retstr); - retstr = NULL; - } - } - - switch (plist->type) { - case WPLString: - retstr = stringDescription(plist); - break; - case WPLData: - retstr = dataDescription(plist); - break; - case WPLArray: - retstr = wstrdup("(\n"); - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - str = indentedDescription(WMGetFromArray(plist->d.array, i), level + 1); - if (i == 0) { - tmp = (char *)wmalloc(2 * (level + 1) + strlen(retstr) + strlen(str) + 1); - sprintf(tmp, "%s%*s%s", retstr, 2 * (level + 1), "", str); - wfree(retstr); - retstr = tmp; - } else { - tmp = (char *)wmalloc(2 * (level + 1) + strlen(retstr) + strlen(str) + 3); - sprintf(tmp, "%s,\n%*s%s", retstr, 2 * (level + 1), "", str); - wfree(retstr); - retstr = tmp; - } - wfree(str); - } - tmp = (char *)wmalloc(strlen(retstr) + 2 * level + 3); - sprintf(tmp, "%s\n%*s)", retstr, 2 * level, ""); - wfree(retstr); - retstr = tmp; - break; - case WPLDictionary: - retstr = wstrdup("{\n"); - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&val, (void **)&key)) { - skey = indentedDescription(key, level + 1); - sval = indentedDescription(val, level + 1); - tmp = (char *)wmalloc(2 * (level + 1) + strlen(retstr) + strlen(skey) - + strlen(sval) + 6); - sprintf(tmp, "%s%*s%s = %s;\n", retstr, 2 * (level + 1), "", skey, sval); - wfree(skey); - wfree(sval); - wfree(retstr); - retstr = tmp; - } - tmp = (char *)wmalloc(strlen(retstr) + 2 * level + 2); - sprintf(tmp, "%s%*s}", retstr, 2 * level, ""); - wfree(retstr); - retstr = tmp; - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return retstr; -} - -static inline int getChar(PLData * pldata) -{ - int c; - - c = pldata->ptr[pldata->pos]; - if (c == 0) { - return 0; - } - - pldata->pos++; - - if (c == '\n') - pldata->lineNumber++; - - return c; -} - -static inline int getNonSpaceChar(PLData * pldata) -{ - int c; - - while (1) { - c = pldata->ptr[pldata->pos]; - if (c == 0) { - break; - } - pldata->pos++; - if (c == '\n') { - pldata->lineNumber++; - } else if (!isspace(c)) { - break; - } - } - - return c; -} - -static char *unescapestr(const char *src) -{ - char *dest = wmalloc(strlen(src) + 1); - char *dPtr; - char ch; - - for (dPtr = dest; ; dPtr++) { - ch = *src++; - if (ch == '\0') - break; - else if (ch != '\\') - *dPtr = ch; - else { - ch = *(src++); - if (ch == '\0') { - *dPtr = '\\'; - break; - } else if ((ch >= '0') && (ch <= '7')) { - char wch; - - /* Convert octal number to character */ - wch = (ch & 07); - ch = *src; - if ((ch >= '0') && (ch <= '7')) { - src++; - wch = (wch << 3) | (ch & 07); - ch = *src; - if ((ch >= '0') && (ch <= '7')) { - src++; - wch = (wch << 3) | (ch & 07); - } - } - *dPtr = wch; - } else { - switch (ch) { - case 'a': - *dPtr = '\a'; - break; - case 'b': - *dPtr = '\b'; - break; - case 't': - *dPtr = '\t'; - break; - case 'r': - *dPtr = '\r'; - break; - case 'n': - *dPtr = '\n'; - break; - case 'v': - *dPtr = '\v'; - break; - case 'f': - *dPtr = '\f'; - break; - default: - *dPtr = ch; - } - } - } - } - - *dPtr = 0; - - return dest; -} - -static WMPropList *getPLString(PLData * pldata) -{ - WMPropList *plist; - StringBuffer sBuf; - int ptr = 0; - int c; - - sBuf.str = wmalloc(BUFFERSIZE); - sBuf.size = BUFFERSIZE; - - while (1) { - c = getChar(pldata); - if (ISSTRINGABLE(c)) { - CHECK_BUFFER_SIZE(sBuf, ptr); - sBuf.str[ptr++] = c; - } else { - if (c != 0) { - pldata->pos--; - } - break; - } - } - - sBuf.str[ptr] = 0; - - if (ptr == 0) { - plist = NULL; - } else { - char *tmp = unescapestr(sBuf.str); - plist = WMCreatePLString(tmp); - wfree(tmp); - } - - wfree(sBuf.str); - - return plist; -} - -static WMPropList *getPLQString(PLData * pldata) -{ - WMPropList *plist; - int ptr = 0, escaping = 0, ok = 1; - int c; - StringBuffer sBuf; - - sBuf.str = wmalloc(BUFFERSIZE); - sBuf.size = BUFFERSIZE; - - while (1) { - c = getChar(pldata); - if (!escaping) { - if (c == '\\') { - escaping = 1; - continue; - } else if (c == '"') { - break; - } - } else { - CHECK_BUFFER_SIZE(sBuf, ptr); - sBuf.str[ptr++] = '\\'; - escaping = 0; - } - - if (c == 0) { - COMPLAIN(pldata, _("unterminated PropList string")); - ok = 0; - break; - } else { - CHECK_BUFFER_SIZE(sBuf, ptr); - sBuf.str[ptr++] = c; - } - } - - sBuf.str[ptr] = 0; - - if (!ok) { - plist = NULL; - } else { - char *tmp = unescapestr(sBuf.str); - plist = WMCreatePLString(tmp); - wfree(tmp); - } - - wfree(sBuf.str); - - return plist; -} - -static WMPropList *getPLData(PLData * pldata) -{ - int ok = 1; - int len = 0; - int c1, c2; - unsigned char buf[BUFFERSIZE], byte; - WMPropList *plist; - WMData *data; - - data = WMCreateDataWithCapacity(0); - - while (1) { - c1 = getNonSpaceChar(pldata); - if (c1 == 0) { - COMPLAIN(pldata, _("unterminated PropList data")); - ok = 0; - break; - } else if (c1 == '>') { - break; - } else if (ishexdigit(c1)) { - c2 = getNonSpaceChar(pldata); - if (c2 == 0 || c2 == '>') { - COMPLAIN(pldata, _("unterminated PropList data (missing hexdigit)")); - ok = 0; - break; - } else if (ishexdigit(c2)) { - byte = char2num(c1) << 4; - byte |= char2num(c2); - buf[len++] = byte; - if (len == sizeof(buf)) { - WMAppendDataBytes(data, buf, len); - len = 0; - } - } else { - COMPLAIN(pldata, _("non hexdigit character in PropList data")); - ok = 0; - break; - } - } else { - COMPLAIN(pldata, _("non hexdigit character in PropList data")); - ok = 0; - break; - } - } - - if (!ok) { - WMReleaseData(data); - return NULL; - } - - if (len > 0) - WMAppendDataBytes(data, buf, len); - - plist = WMCreatePLData(data); - WMReleaseData(data); - - return plist; -} - -static WMPropList *getPLArray(PLData * pldata) -{ - Bool first = True; - int ok = 1; - int c; - WMPropList *array, *obj; - - array = WMCreatePLArray(NULL); - - while (1) { - c = getNonSpaceChar(pldata); - if (c == 0) { - COMPLAIN(pldata, _("unterminated PropList array")); - ok = 0; - break; - } else if (c == ')') { - break; - } else if (c == ',') { - /* continue normally */ - } else if (!first) { - COMPLAIN(pldata, _("missing or unterminated PropList array")); - ok = 0; - break; - } else { - pldata->pos--; - } - first = False; - - obj = getPropList(pldata); - if (!obj) { - COMPLAIN(pldata, _("could not get PropList array element")); - ok = 0; - break; - } - WMAddToPLArray(array, obj); - WMReleasePropList(obj); - } - - if (!ok) { - WMReleasePropList(array); - array = NULL; - } - - return array; -} - -static WMPropList *getPLDictionary(PLData * pldata) -{ - int ok = 1; - int c; - WMPropList *dict, *key, *value; - - dict = WMCreatePLDictionary(NULL, NULL); - - while (1) { - c = getNonSpaceChar(pldata); - if (c == 0) { - COMPLAIN(pldata, _("unterminated PropList dictionary")); - ok = 0; - break; - } else if (c == '}') { - break; - } - - DPUT("getting PropList dictionary key"); - if (c == '<') { - key = getPLData(pldata); - } else if (c == '"') { - key = getPLQString(pldata); - } else if (ISSTRINGABLE(c)) { - pldata->pos--; - key = getPLString(pldata); - } else { - if (c == '=') { - COMPLAIN(pldata, _("missing PropList dictionary key")); - } else { - COMPLAIN(pldata, _("missing PropList dictionary entry key " - "or unterminated dictionary")); - } - ok = 0; - break; - } - - if (!key) { - COMPLAIN(pldata, _("error parsing PropList dictionary key")); - ok = 0; - break; - } - - c = getNonSpaceChar(pldata); - if (c != '=') { - WMReleasePropList(key); - COMPLAIN(pldata, _("missing = in PropList dictionary entry")); - ok = 0; - break; - } - - DPUT("getting PropList dictionary entry value for key"); - value = getPropList(pldata); - if (!value) { - COMPLAIN(pldata, _("error parsing PropList dictionary entry value")); - WMReleasePropList(key); - ok = 0; - break; - } - - c = getNonSpaceChar(pldata); - if (c != ';') { - COMPLAIN(pldata, _("missing ; in PropList dictionary entry")); - WMReleasePropList(key); - WMReleasePropList(value); - ok = 0; - break; - } - - WMPutInPLDictionary(dict, key, value); - WMReleasePropList(key); - WMReleasePropList(value); - } - - if (!ok) { - WMReleasePropList(dict); - dict = NULL; - } - - return dict; -} - -static WMPropList *getPropList(PLData * pldata) -{ - WMPropList *plist; - int c; - - c = getNonSpaceChar(pldata); - - switch (c) { - case 0: - DPUT("End of PropList"); - plist = NULL; - break; - - case '{': - DPUT("Getting PropList dictionary"); - plist = getPLDictionary(pldata); - break; - - case '(': - DPUT("Getting PropList array"); - plist = getPLArray(pldata); - break; - - case '<': - DPUT("Getting PropList data"); - plist = getPLData(pldata); - break; - - case '"': - DPUT("Getting PropList quoted string"); - plist = getPLQString(pldata); - break; - - default: - if (ISSTRINGABLE(c)) { - DPUT("Getting PropList string"); - pldata->pos--; - plist = getPLString(pldata); - } else { - COMPLAIN(pldata, _("was expecting a string, data, array or " - "dictionary. If it's a string, try enclosing " "it with \".")); - if (c == '#' || c == '/') { - wwarning(_("Comments are not allowed inside WindowMaker owned" " domain files.")); - } - plist = NULL; - } - break; - } - - return plist; -} - -void WMPLSetCaseSensitive(Bool caseSensitiveness) -{ - caseSensitive = caseSensitiveness; -} - -WMPropList *WMCreatePLString(const char *str) -{ - WMPropList *plist; - - wassertrv(str != NULL, NULL); - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLString; - plist->d.string = wstrdup(str); - plist->retainCount = 1; - - return plist; -} - -WMPropList *WMCreatePLData(WMData * data) -{ - WMPropList *plist; - - wassertrv(data != NULL, NULL); - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLData; - plist->d.data = WMRetainData(data); - plist->retainCount = 1; - - return plist; -} - -WMPropList *WMCreatePLDataWithBytes(const unsigned char *bytes, unsigned int length) -{ - WMPropList *plist; - - wassertrv(bytes != NULL, NULL); - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLData; - plist->d.data = WMCreateDataWithBytes(bytes, length); - plist->retainCount = 1; - - return plist; -} +/* + * This should be written in Rust whenever va_args support improves. + */ WMPropList *WMCreatePLArray(WMPropList * elem, ...) { WMPropList *plist, *nelem; va_list ap; - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLArray; - plist->d.array = WMCreateArray(4); - plist->retainCount = 1; + plist = WMCreateEmptyPLArray(); if (!elem) return plist; - WMAddToArray(plist->d.array, WMRetainPropList(elem)); + WMAddToPLArray(plist, elem); va_start(ap, elem); @@ -965,888 +25,6 @@ WMPropList *WMCreatePLArray(WMPropList * elem, ...) va_end(ap); return plist; } - WMAddToArray(plist->d.array, WMRetainPropList(nelem)); + WMAddToPLArray(plist, elem); } } - -WMPropList *WMCreatePLDictionary(WMPropList * key, WMPropList * value, ...) -{ - WMPropList *plist, *nkey, *nvalue, *k, *v; - va_list ap; - - plist = (WMPropList *) wmalloc(sizeof(W_PropList)); - plist->type = WPLDictionary; - plist->d.dict = WMCreateHashTable(WMPropListHashCallbacks); - plist->retainCount = 1; - - if (!key || !value) - return plist; - - WMHashInsert(plist->d.dict, WMRetainPropList(key), WMRetainPropList(value)); - - va_start(ap, value); - - while (1) { - nkey = va_arg(ap, WMPropList *); - if (!nkey) { - va_end(ap); - return plist; - } - nvalue = va_arg(ap, WMPropList *); - if (!nvalue) { - va_end(ap); - return plist; - } - if (WMHashGetItemAndKey(plist->d.dict, nkey, (void **)&v, (void **)&k)) { - WMHashRemove(plist->d.dict, k); - WMReleasePropList(k); - WMReleasePropList(v); - } - WMHashInsert(plist->d.dict, WMRetainPropList(nkey), WMRetainPropList(nvalue)); - } -} - -WMPropList *WMRetainPropList(WMPropList * plist) -{ - WMPropList *key, *value; - WMHashEnumerator e; - int i; - - plist->retainCount++; - - switch (plist->type) { - case WPLString: - case WPLData: - break; - case WPLArray: - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - WMRetainPropList(WMGetFromArray(plist->d.array, i)); - } - break; - case WPLDictionary: - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - WMRetainPropList(key); - WMRetainPropList(value); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return plist; -} - -void WMReleasePropList(WMPropList * plist) -{ - WMPropList *key, *value; - WMHashEnumerator e; - int i; - - plist->retainCount--; - - switch (plist->type) { - case WPLString: - if (plist->retainCount < 1) { - wfree(plist->d.string); - wfree(plist); - } - break; - case WPLData: - if (plist->retainCount < 1) { - WMReleaseData(plist->d.data); - wfree(plist); - } - break; - case WPLArray: - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - WMReleasePropList(WMGetFromArray(plist->d.array, i)); - } - if (plist->retainCount < 1) { - WMFreeArray(plist->d.array); - wfree(plist); - } - break; - case WPLDictionary: - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - WMReleasePropList(key); - WMReleasePropList(value); - } - if (plist->retainCount < 1) { - WMFreeHashTable(plist->d.dict); - wfree(plist); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertr(False); - break; - } -} - -void WMInsertInPLArray(WMPropList * plist, int index, WMPropList * item) -{ - wassertr(plist->type == WPLArray); - - retainPropListByCount(item, plist->retainCount); - WMInsertInArray(plist->d.array, index, item); -} - -void WMAddToPLArray(WMPropList * plist, WMPropList * item) -{ - wassertr(plist->type == WPLArray); - - retainPropListByCount(item, plist->retainCount); - WMAddToArray(plist->d.array, item); -} - -void WMDeleteFromPLArray(WMPropList * plist, int index) -{ - WMPropList *item; - - wassertr(plist->type == WPLArray); - - item = WMGetFromArray(plist->d.array, index); - if (item != NULL) { - WMDeleteFromArray(plist->d.array, index); - releasePropListByCount(item, plist->retainCount); - } -} - -void WMRemoveFromPLArray(WMPropList * plist, WMPropList * item) -{ - WMPropList *iPtr; - int i; - - wassertr(plist->type == WPLArray); - - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - iPtr = WMGetFromArray(plist->d.array, i); - if (WMIsPropListEqualTo(item, iPtr)) { - WMDeleteFromArray(plist->d.array, i); - releasePropListByCount(iPtr, plist->retainCount); - break; - } - } -} - -void WMPutInPLDictionary(WMPropList * plist, WMPropList * key, WMPropList * value) -{ - wassertr(plist->type == WPLDictionary); - - /*WMRetainPropList(key); */ - WMRemoveFromPLDictionary(plist, key); - retainPropListByCount(key, plist->retainCount); - retainPropListByCount(value, plist->retainCount); - WMHashInsert(plist->d.dict, key, value); - /*WMReleasePropList(key); */ -} - -void WMRemoveFromPLDictionary(WMPropList * plist, WMPropList * key) -{ - WMPropList *k, *v; - - wassertr(plist->type == WPLDictionary); - - if (WMHashGetItemAndKey(plist->d.dict, key, (void **)&v, (void **)&k)) { - WMHashRemove(plist->d.dict, k); - releasePropListByCount(k, plist->retainCount); - releasePropListByCount(v, plist->retainCount); - } -} - -WMPropList *WMMergePLDictionaries(WMPropList * dest, WMPropList * source, Bool recursive) -{ - WMPropList *key, *value, *dvalue; - WMHashEnumerator e; - - wassertrv(source->type == WPLDictionary && dest->type == WPLDictionary, NULL); - - if (source == dest) - return dest; - - e = WMEnumerateHashTable(source->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - if (recursive && value->type == WPLDictionary) { - dvalue = WMHashGet(dest->d.dict, key); - if (dvalue && dvalue->type == WPLDictionary) { - WMMergePLDictionaries(dvalue, value, True); - } else { - WMPutInPLDictionary(dest, key, value); - } - } else { - WMPutInPLDictionary(dest, key, value); - } - } - - return dest; -} - -WMPropList *WMSubtractPLDictionaries(WMPropList * dest, WMPropList * source, Bool recursive) -{ - WMPropList *key, *value, *dvalue; - WMHashEnumerator e; - - wassertrv(source->type == WPLDictionary && dest->type == WPLDictionary, NULL); - - if (source == dest) { - WMPropList *keys = WMGetPLDictionaryKeys(dest); - int i; - - for (i = 0; i < WMGetArrayItemCount(keys->d.array); i++) { - WMRemoveFromPLDictionary(dest, WMGetFromArray(keys->d.array, i)); - } - WMReleasePropList(keys); - return dest; - } - - e = WMEnumerateHashTable(source->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&value, (void **)&key)) { - dvalue = WMHashGet(dest->d.dict, key); - if (!dvalue) - continue; - if (WMIsPropListEqualTo(value, dvalue)) { - WMRemoveFromPLDictionary(dest, key); - } else if (recursive && value->type == WPLDictionary && dvalue->type == WPLDictionary) { - WMSubtractPLDictionaries(dvalue, value, True); - } - } - - return dest; -} - -int WMGetPropListItemCount(WMPropList * plist) -{ - switch (plist->type) { - case WPLString: - case WPLData: - return 0; /* should this be 1 instead? */ - case WPLArray: - return WMGetArrayItemCount(plist->d.array); - case WPLDictionary: - return (int)WMCountHashTable(plist->d.dict); - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, 0); - break; - } - - return 0; -} - -Bool WMIsPLString(WMPropList * plist) -{ - if (plist) - return (plist->type == WPLString); - else - return False; -} - -Bool WMIsPLData(WMPropList * plist) -{ - if (plist) - return (plist->type == WPLData); - else - return False; -} - -Bool WMIsPLArray(WMPropList * plist) -{ - if (plist) - return (plist->type == WPLArray); - else - return False; -} - -Bool WMIsPLDictionary(WMPropList * plist) -{ - if (plist) - return (plist->type == WPLDictionary); - else - return False; -} - -Bool WMIsPropListEqualTo(WMPropList * plist, WMPropList * other) -{ - WMPropList *key1, *item1, *item2; - WMHashEnumerator enumerator; - int n, i; - - if (plist->type != other->type) - return False; - - switch (plist->type) { - case WPLString: - if (caseSensitive) { - return (strcmp(plist->d.string, other->d.string) == 0); - } else { - return (strcasecmp(plist->d.string, other->d.string) == 0); - } - case WPLData: - return WMIsDataEqualToData(plist->d.data, other->d.data); - case WPLArray: - n = WMGetArrayItemCount(plist->d.array); - if (n != WMGetArrayItemCount(other->d.array)) - return False; - for (i = 0; i < n; i++) { - item1 = WMGetFromArray(plist->d.array, i); - item2 = WMGetFromArray(other->d.array, i); - if (!WMIsPropListEqualTo(item1, item2)) - return False; - } - return True; - case WPLDictionary: - if (WMCountHashTable(plist->d.dict) != WMCountHashTable(other->d.dict)) - return False; - enumerator = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&enumerator, (void **)&item1, (void **)&key1)) { - item2 = WMHashGet(other->d.dict, key1); - if (!item2 || !item1 || !WMIsPropListEqualTo(item1, item2)) - return False; - } - return True; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, False); - break; - } - - return False; -} - -char *WMGetFromPLString(WMPropList * plist) -{ - wassertrv(plist->type == WPLString, NULL); - - return plist->d.string; -} - -WMData *WMGetFromPLData(WMPropList * plist) -{ - wassertrv(plist->type == WPLData, NULL); - - return plist->d.data; -} - -const unsigned char *WMGetPLDataBytes(WMPropList * plist) -{ - wassertrv(plist->type == WPLData, NULL); - - return WMDataBytes(plist->d.data); -} - -int WMGetPLDataLength(WMPropList * plist) -{ - wassertrv(plist->type == WPLData, 0); - - return WMGetDataLength(plist->d.data); -} - -WMPropList *WMGetFromPLArray(WMPropList * plist, int index) -{ - wassertrv(plist->type == WPLArray, NULL); - - return WMGetFromArray(plist->d.array, index); -} - -WMPropList *WMGetFromPLDictionary(WMPropList * plist, WMPropList * key) -{ - wassertrv(plist->type == WPLDictionary, NULL); - - return WMHashGet(plist->d.dict, key); -} - -WMPropList *WMGetPLDictionaryKeys(WMPropList * plist) -{ - WMPropList *array, *key; - WMHashEnumerator enumerator; - - wassertrv(plist->type == WPLDictionary, NULL); - - array = (WMPropList *) wmalloc(sizeof(W_PropList)); - array->type = WPLArray; - array->d.array = WMCreateArray(WMCountHashTable(plist->d.dict)); - array->retainCount = 1; - - enumerator = WMEnumerateHashTable(plist->d.dict); - while ((key = WMNextHashEnumeratorKey(&enumerator))) { - WMAddToArray(array->d.array, WMRetainPropList(key)); - } - - return array; -} - -WMPropList *WMShallowCopyPropList(WMPropList * plist) -{ - WMPropList *ret = NULL; - WMPropList *key, *item; - WMHashEnumerator e; - WMData *data; - int i; - - switch (plist->type) { - case WPLString: - ret = WMCreatePLString(plist->d.string); - break; - case WPLData: - data = WMCreateDataWithData(plist->d.data); - ret = WMCreatePLData(data); - WMReleaseData(data); - break; - case WPLArray: - ret = (WMPropList *) wmalloc(sizeof(W_PropList)); - ret->type = WPLArray; - ret->d.array = WMCreateArrayWithArray(plist->d.array); - ret->retainCount = 1; - - for (i = 0; i < WMGetArrayItemCount(ret->d.array); i++) - WMRetainPropList(WMGetFromArray(ret->d.array, i)); - - break; - case WPLDictionary: - ret = WMCreatePLDictionary(NULL, NULL); - e = WMEnumerateHashTable(plist->d.dict); - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&item, (void **)&key)) { - WMPutInPLDictionary(ret, key, item); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return ret; -} - -WMPropList *WMDeepCopyPropList(WMPropList * plist) -{ - WMPropList *ret = NULL; - WMPropList *key, *item; - WMHashEnumerator e; - WMData *data; - int i; - - switch (plist->type) { - case WPLString: - ret = WMCreatePLString(plist->d.string); - break; - case WPLData: - data = WMCreateDataWithData(plist->d.data); - ret = WMCreatePLData(data); - WMReleaseData(data); - break; - case WPLArray: - ret = WMCreatePLArray(NULL); - for (i = 0; i < WMGetArrayItemCount(plist->d.array); i++) { - item = WMDeepCopyPropList(WMGetFromArray(plist->d.array, i)); - WMAddToArray(ret->d.array, item); - } - break; - case WPLDictionary: - ret = WMCreatePLDictionary(NULL, NULL); - e = WMEnumerateHashTable(plist->d.dict); - /* While we copy an existing dictionary there is no way that we can - * have duplicate keys, so we don't need to first remove a key/value - * pair before inserting the new key/value. - */ - while (WMNextHashEnumeratorItemAndKey(&e, (void **)&item, (void **)&key)) { - WMHashInsert(ret->d.dict, WMDeepCopyPropList(key), WMDeepCopyPropList(item)); - } - break; - default: - wwarning(_("Used proplist functions on non-WMPropLists objects")); - wassertrv(False, NULL); - break; - } - - return ret; -} - -WMPropList *WMCreatePropListFromDescription(const char *desc) -{ - WMPropList *plist = NULL; - PLData *pldata; - - pldata = (PLData *) wmalloc(sizeof(PLData)); - pldata->ptr = desc; - pldata->lineNumber = 1; - - plist = getPropList(pldata); - - if (getNonSpaceChar(pldata) != 0 && plist) { - COMPLAIN(pldata, _("extra data after end of property list")); - /* - * We can't just ignore garbage after the end of the description - * (especially if the description was read from a file), because - * the "garbage" can be the real data and the real garbage is in - * fact in the beginning of the file (which is now inside plist) - */ - WMReleasePropList(plist); - plist = NULL; - } - - wfree(pldata); - - return plist; -} - -char *WMGetPropListDescription(WMPropList * plist, Bool indented) -{ - return (indented ? indentedDescription(plist, 0) : description(plist)); -} - -WMPropList *WMReadPropListFromFile(const char *file) -{ - WMPropList *plist = NULL; - PLData *pldata; - char *read_buf; - FILE *f; - struct stat stbuf; - size_t length; - - f = fopen(file, "rb"); - if (!f) { - /* let the user print the error message if he really needs to */ - /*werror(_("could not open domain file '%s' for reading"), file); */ - return NULL; - } - - if (stat(file, &stbuf) == 0) { - length = (size_t) stbuf.st_size; - } else { - werror(_("could not get size for file '%s'"), file); - fclose(f); - return NULL; - } - - read_buf = wmalloc(length + 1); - if (fread(read_buf, length, 1, f) != 1) { - if (ferror(f)) { - werror(_("error reading from file '%s'"), file); - } - fclose(f); - wfree(read_buf); - return NULL; - } - read_buf[length] = '\0'; - fclose(f); - - pldata = (PLData *) wmalloc(sizeof(PLData)); - pldata->ptr = read_buf; - pldata->filename = file; - pldata->lineNumber = 1; - - plist = getPropList(pldata); - - if (getNonSpaceChar(pldata) != 0 && plist) { - COMPLAIN(pldata, _("extra data after end of property list")); - /* - * We can't just ignore garbage after the end of the description - * (especially if the description was read from a file), because - * the "garbage" can be the real data and the real garbage is in - * fact in the beginning of the file (which is now inside plist) - */ - WMReleasePropList(plist); - plist = NULL; - } - - wfree(read_buf); - wfree(pldata); - - return plist; -} - -WMPropList *WMReadPropListFromPipe(const char *command) -{ - FILE *file; - WMPropList *plist; - PLData *pldata; - char *read_buf, *read_ptr; - size_t remain_size, line_size; - const size_t block_read_size = 4096; - const size_t block_read_margin = 512; - - file = popen(command, "r"); - - if (!file) { - werror(_("%s:could not open menu file"), command); - return NULL; - } - - /* read from file till EOF or OOM and fill proplist buffer*/ - remain_size = block_read_size; - read_buf = wmalloc(remain_size); - read_ptr = read_buf; - while (fgets(read_ptr, remain_size, file) != NULL) { - line_size = strlen(read_ptr); - - remain_size -= line_size; - read_ptr += line_size; - - if (remain_size < block_read_margin) { - size_t read_length; - - read_length = read_ptr - read_buf; - read_buf = wrealloc(read_buf, read_length + block_read_size); - read_ptr = read_buf + read_length; - remain_size = block_read_size; - } - } - - pclose(file); - - pldata = (PLData *) wmalloc(sizeof(PLData)); - pldata->ptr = read_buf; - pldata->filename = command; - pldata->lineNumber = 1; - - plist = getPropList(pldata); - - if (getNonSpaceChar(pldata) != 0 && plist) { - COMPLAIN(pldata, _("extra data after end of property list")); - /* - * We can't just ignore garbage after the end of the description - * (especially if the description was read from a file), because - * the "garbage" can be the real data and the real garbage is in - * fact in the beginning of the file (which is now inside plist) - */ - WMReleasePropList(plist); - plist = NULL; - } - - wfree(read_buf); - wfree(pldata); - - return plist; -} - -/* TODO: review this function's code */ - -Bool WMWritePropListToFile(WMPropList * plist, const char *path) -{ - char *thePath = NULL; - char *desc; - FILE *theFile; -#ifdef HAVE_MKSTEMP - int fd, mask; -#endif - - if (!wmkdirhier(path)) - return False; - - /* Use the path name of the destination file as a prefix for the - * mkstemp() call so that we can be sure that both files are on - * the same filesystem and the subsequent rename() will work. */ - thePath = wstrconcat(path, ".XXXXXX"); - -#ifdef HAVE_MKSTEMP - /* - * We really just want to read the current umask, but as Coverity is - * pointing a possible security issue: - * some versions of mkstemp do not set file rights properly on the - * created file, so it is recommended so set the umask beforehand. - * As we need to set an umask to read the current value, we take this - * opportunity to set a temporary aggresive umask so Coverity won't - * complain, even if we do not really care in the present use case. - */ - mask = umask(S_IRWXG | S_IRWXO); - if ((fd = mkstemp(thePath)) < 0) { - werror(_("mkstemp (%s) failed"), thePath); - goto failure; - } - umask(mask); - fchmod(fd, 0666 & ~mask); - if ((theFile = fdopen(fd, "wb")) == NULL) { - close(fd); - } -#else - if (mktemp(thePath) == NULL) { - werror(_("mktemp (%s) failed"), thePath); - goto failure; - } - theFile = fopen(thePath, "wb"); -#endif - - if (theFile == NULL) { - werror(_("open (%s) failed"), thePath); - goto failure; - } - - desc = indentedDescription(plist, 0); - - if (fprintf(theFile, "%s\n", desc) != strlen(desc) + 1) { - werror(_("writing to file: %s failed"), thePath); - wfree(desc); - fclose(theFile); - goto failure; - } - - wfree(desc); - - (void)fsync(fileno(theFile)); - if (fclose(theFile) != 0) { - werror(_("fclose (%s) failed"), thePath); - goto failure; - } - - /* If we used a temporary file, we still need to rename() it be the - * real file. Also, we need to try to retain the file attributes of - * the original file we are overwriting (if we are) */ - if (rename(thePath, path) != 0) { - werror(_("rename ('%s' to '%s') failed"), thePath, path); - goto failure; - } - - wfree(thePath); - return True; - - failure: - unlink(thePath); - wfree(thePath); - return False; -} - -/* - * create a directory hierarchy - * - * if the last octet of `path' is `/', the full path is - * assumed to be a directory; otherwise path is assumed to be a - * file, and the last component is stripped off. the rest is the - * the hierarchy to be created. - * - * refuses to create anything outside $WMAKER_USER_ROOT - * - * returns 1 on success, 0 on failure - */ -int wmkdirhier(const char *path) -{ - const char *t; - char *thePath = NULL, buf[1024]; - size_t p, plen; - struct stat st; - - /* Only create directories under $WMAKER_USER_ROOT */ - if ((t = wusergnusteppath()) == NULL) - return 0; - if (strncmp(path, t, strlen(t)) != 0) - return 0; - - thePath = wstrdup(path); - /* Strip the trailing component if it is a file */ - p = strlen(thePath); - while (p && thePath[p] != '/') - thePath[p--] = '\0'; - - thePath[p] = '\0'; - - /* Shortcut if it already exists */ - if (stat(thePath, &st) == 0) { - wfree(thePath); - if (S_ISDIR(st.st_mode)) { - /* Is a directory alright */ - return 1; - } else { - /* Exists, but not a directory, the caller - * might just as well abort now */ - return 0; - } - } - - memset(buf, 0, sizeof(buf)); - strncpy(buf, t, sizeof(buf) - 1); - p = strlen(buf); - plen = strlen(thePath); - - do { - while (p++ < plen && thePath[p] != '/') - ; - - strncpy(buf, thePath, p); - if (mkdir(buf, 0777) == -1 && errno == EEXIST && - stat(buf, &st) == 0 && !S_ISDIR(st.st_mode)) { - werror(_("Could not create component %s"), buf); - wfree(thePath); - return 0; - } - } while (p < plen); - - wfree(thePath); - return 1; -} - -/* ARGSUSED2 */ -static int wrmdirhier_fn(const char *path, const struct stat *st, - int type, struct FTW *ftw) -{ - /* Parameter not used, but tell the compiler that it is ok */ - (void) st; - (void) ftw; - - switch(type) { - case FTW_D: - break; - case FTW_DP: - return rmdir(path); - break; - case FTW_F: - case FTW_SL: - case FTW_SLN: - return unlink(path); - break; - case FTW_DNR: - case FTW_NS: - default: - return EPERM; - } - - /* NOTREACHED */ - return 0; -} - -/* - * remove a directory hierarchy - * - * refuses to remove anything outside $WMAKER_USER_ROOT/Defaults or $WMAKER_USER_ROOT/Library - * - * returns 1 on success, 0 on failure - * - * TODO: revisit what's error and what's not - * - * with inspirations from OpenBSD's bin/rm/rm.c - */ -int wrmdirhier(const char *path) -{ - const char *libpath; - char *udefpath = NULL; - struct stat st; - int error; - - /* Only remove directories under $WMAKER_USER_ROOT/Defaults or $WMAKER_USER_ROOT/Library */ - libpath = wuserdatapath(); - if (strncmp(path, libpath, strlen(libpath)) == 0) - if (path[strlen(libpath)] == '/') - goto path_in_valid_tree; - - udefpath = wdefaultspathfordomain(""); - if (strncmp(path, udefpath, strlen(udefpath)) == 0) - /* Note: by side effect, 'udefpath' already contains a final '/' */ - goto path_in_valid_tree; - - wfree(udefpath); - return EPERM; - - path_in_valid_tree: - wfree(udefpath); - - /* Shortcut if it doesn't exist to begin with */ - if (stat(path, &st) == -1) - return ENOENT; - - error = nftw(path, wrmdirhier_fn, 1, FTW_PHYS); - - return error; -} diff --git a/WINGs/userdefaults.c b/WINGs/userdefaults.c index 78f8f836..40010c55 100644 --- a/WINGs/userdefaults.c +++ b/WINGs/userdefaults.c @@ -46,39 +46,6 @@ static void synchronizeUserDefaults(void *foo); #define UD_SYNC_INTERVAL 2000 #endif -const char *wusergnusteppath(void) -{ - static const char subdir[] = "/" GSUSER_SUBDIR; - static char *path = NULL; - char *gspath; - char *h; - int pathlen; - - if (path) - /* Value have been already computed, re-use it */ - return path; - - gspath = GETENV("WMAKER_USER_ROOT"); - if (gspath) { - gspath = wexpandpath(gspath); - if (gspath) { - path = gspath; - return path; - } - wwarning(_("variable WMAKER_USER_ROOT defined with invalid path, not used")); - } - - h = wgethomedir(); - - pathlen = strlen(h); - path = wmalloc(pathlen + sizeof(subdir)); - strcpy(path, h); - strcpy(path + pathlen, subdir); - wfree(h); - - return path; -} - const char *wuserdatapath(void) { static char *path = NULL; @@ -330,7 +297,7 @@ WMUserDefaults *WMGetStandardUserDefaults(void) /* terminate list */ defaults->searchList[2] = NULL; - defaults->searchListArray = WMCreatePLArray(NULL, NULL); + defaults->searchListArray = WMCreateEmptyPLArray(); i = 0; while (defaults->searchList[i]) { @@ -403,7 +370,7 @@ WMUserDefaults *WMGetDefaultsFromPath(const char *path) /* terminate list */ defaults->searchList[1] = NULL; - defaults->searchListArray = WMCreatePLArray(NULL, NULL); + defaults->searchListArray = WMCreateEmptyPLArray(); i = 0; while (defaults->searchList[i]) { diff --git a/WPrefs.app/Appearance.c b/WPrefs.app/Appearance.c index 041b3de5..028c96e0 100644 --- a/WPrefs.app/Appearance.c +++ b/WPrefs.app/Appearance.c @@ -2228,7 +2228,7 @@ static void prepareForClose(_Panel * panel) WMUserDefaults *udb = WMGetStandardUserDefaults(); int i; - textureList = WMCreatePLArray(NULL, NULL); + textureList = WMCreateEmptyPLArray(); /* store list of textures */ for (i = 8; i < WMGetListNumberOfRows(panel->texLs); i++) { @@ -2250,7 +2250,7 @@ static void prepareForClose(_Panel * panel) WMReleasePropList(textureList); /* store list of colors */ - textureList = WMCreatePLArray(NULL, NULL); + textureList = WMCreateEmptyPLArray(); for (i = 0; i < wlengthof(sample_colors); i++) { WMColor *color; char *str; diff --git a/WPrefs.app/HotCornerShortcuts.c b/WPrefs.app/HotCornerShortcuts.c index d3259d80..56b437a6 100644 --- a/WPrefs.app/HotCornerShortcuts.c +++ b/WPrefs.app/HotCornerShortcuts.c @@ -151,7 +151,7 @@ static void storeData(_Panel * panel) SetIntegerForKey(WMGetSliderValue(panel->hceS), "HotCornerEdge"); - list = WMCreatePLArray(NULL, NULL); + list = WMCreateEmptyPLArray(); for (i = 0; i < sizeof(panel->hcactionsT) / sizeof(WMTextField *); i++) { str = WMGetTextFieldText(panel->hcactionsT[i]); if (strlen(str) == 0) diff --git a/WPrefs.app/Paths.c b/WPrefs.app/Paths.c index f48eb484..d9fe4978 100644 --- a/WPrefs.app/Paths.c +++ b/WPrefs.app/Paths.c @@ -203,7 +203,7 @@ static void storeData(_Panel * panel) int i; char *p; - list = WMCreatePLArray(NULL, NULL); + list = WMCreateEmptyPLArray(); for (i = 0; i < WMGetListNumberOfRows(panel->icoL); i++) { p = WMGetListItem(panel->icoL, i)->text; tmp = WMCreatePLString(p); @@ -211,7 +211,7 @@ static void storeData(_Panel * panel) } SetObjectForKey(list, "IconPath"); - list = WMCreatePLArray(NULL, NULL); + list = WMCreateEmptyPLArray(); for (i = 0; i < WMGetListNumberOfRows(panel->pixL); i++) { p = WMGetListItem(panel->pixL, i)->text; tmp = WMCreatePLString(p); diff --git a/WPrefs.app/WPrefs.c b/WPrefs.app/WPrefs.c index cbffa5af..c95f8a17 100644 --- a/WPrefs.app/WPrefs.c +++ b/WPrefs.app/WPrefs.c @@ -705,10 +705,10 @@ static void loadConfigurations(WMScreen * scr, WMWindow * mainw) } if (!db) { - db = WMCreatePLDictionary(NULL, NULL); + db = WMCreateEmptyPLDictionary(); } if (!gdb) { - gdb = WMCreatePLDictionary(NULL, NULL); + gdb = WMCreateEmptyPLDictionary(); } GlobalDB = gdb; diff --git a/WPrefs.app/main.c b/WPrefs.app/main.c index 0c47a2f0..ede35fda 100644 --- a/WPrefs.app/main.c +++ b/WPrefs.app/main.c @@ -153,7 +153,12 @@ int main(int argc, char **argv) exit(0); } - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ Initialize(scr); diff --git a/src/appicon.c b/src/appicon.c index 9e94b961..4160696f 100644 --- a/src/appicon.c +++ b/src/appicon.c @@ -1360,7 +1360,7 @@ static void wApplicationSaveIconPathFor(const char *iconPath, const char *wm_ins val = WMGetFromPLDictionary(adict, iconk); } else { /* no dictionary for app, so create one */ - adict = WMCreatePLDictionary(NULL, NULL); + adict = WMCreateEmptyPLDictionary(); WMPutInPLDictionary(dict, key, adict); WMReleasePropList(adict); val = NULL; diff --git a/src/defaults.c b/src/defaults.c index 26961e60..1532692a 100644 --- a/src/defaults.c +++ b/src/defaults.c @@ -862,7 +862,12 @@ static void initDefaults(void) unsigned int i; WDefaultEntry *entry; - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ for (i = 0; i < wlengthof(optionList); i++) { entry = &optionList[i]; diff --git a/src/dialog.c b/src/dialog.c index a594abcf..53d52d04 100644 --- a/src/dialog.c +++ b/src/dialog.c @@ -250,7 +250,7 @@ static void SaveHistory(WMArray * history, const char *filename) int i; WMPropList *plhistory; - plhistory = WMCreatePLArray(NULL); + plhistory = WMCreateEmptyPLArray(); for (i = 0; i < WMGetArrayItemCount(history); ++i) WMAddToPLArray(plhistory, WMCreatePLString(WMGetFromArray(history, i))); diff --git a/src/dock.c b/src/dock.c index d59cf35c..08bb91b5 100644 --- a/src/dock.c +++ b/src/dock.c @@ -1603,11 +1603,13 @@ static WMPropList *make_icon_state(WAppIcon *btn) snprintf(buffer, sizeof(buffer), "%hi,%hi", wAppIconGetXIndex(btn), wAppIconGetYIndex(btn)); position = WMCreatePLString(buffer); - node = WMCreatePLDictionary(dCommand, command, - dName, name, - dAutoLaunch, autolaunch, - dLock, lock, - dForced, forced, dBuggyApplication, buggy, dPosition, position, NULL); + node = WMCreatePLDictionary(dCommand, command); + WMPutInPLDictionary(node, dName, name); + WMPutInPLDictionary(node, dAutoLaunch, autolaunch); + WMPutInPLDictionary(node, dLock, lock); + WMPutInPLDictionary(node, dForced, forced); + WMPutInPLDictionary(node, dBuggyApplication, buggy); + WMPutInPLDictionary(node, dPosition, position); WMReleasePropList(command); WMReleasePropList(name); WMReleasePropList(position); @@ -1642,7 +1644,7 @@ static WMPropList *dockSaveState(WDock *dock) WMPropList *value, *key; char buffer[256]; - list = WMCreatePLArray(NULL); + list = WMCreateEmptyPLArray(); for (i = (dock->type == WM_DOCK ? 0 : 1); i < dock->max_icons; i++) { WAppIcon *btn = dock->icon_array[i]; @@ -1657,7 +1659,7 @@ static WMPropList *dockSaveState(WDock *dock) } } - dock_state = WMCreatePLDictionary(dApplications, list, NULL); + dock_state = WMCreatePLDictionary(dApplications, list); if (dock->type == WM_DOCK) { snprintf(buffer, sizeof(buffer), "Applications%i", dock->screen_ptr->scr_height); @@ -5072,7 +5074,7 @@ static WMPropList *drawerSaveState(WDock *drawer) ai = drawer->icon_array[0]; /* Store its name */ pstr = WMCreatePLString(wAppIconGetWmInstance(ai)); - drawer_state = WMCreatePLDictionary(dName, pstr, NULL); /* we need this final NULL */ + drawer_state = WMCreatePLDictionary(dName, pstr); WMReleasePropList(pstr); /* Store its position */ @@ -5114,7 +5116,7 @@ void wDrawersSaveState(WScreen *scr) make_keys(); - all_drawers = WMCreatePLArray(NULL); + all_drawers = WMCreateEmptyPLArray(); for (i=0, dc = scr->drawers; i < scr->drawer_count; i++, dc = dc->next) { diff --git a/src/menu.c b/src/menu.c index e84b1caa..2a83633b 100644 --- a/src/menu.c +++ b/src/menu.c @@ -2309,7 +2309,8 @@ static void saveMenuInfo(WMPropList * dict, WMenu * menu, WMPropList * key) snprintf(buffer, sizeof(buffer), "%i,%i", menu->frame_x, menu->frame_y); value = WMCreatePLString(buffer); - list = WMCreatePLArray(value, NULL); + list = WMCreateEmptyPLArray(); + WMAddToPLArray(list, value); if (menu->flags.lowered) WMAddToPLArray(list, WMCreatePLString("lowered")); WMPutInPLDictionary(dict, key, list); @@ -2322,7 +2323,7 @@ void wMenuSaveState(WScreen * scr) WMPropList *menus, *key; int save_menus = 0; - menus = WMCreatePLDictionary(NULL, NULL); + menus = WMCreateEmptyPLDictionary(); if (scr->switch_menu && scr->switch_menu->flags.buttoned) { key = WMCreatePLString("SwitchMenu"); diff --git a/src/screen.c b/src/screen.c index 98e9f949..6bb7832c 100644 --- a/src/screen.c +++ b/src/screen.c @@ -968,8 +968,6 @@ void wScreenSaveState(WScreen * scr) old_state = scr->session_state; scr->session_state = WMCreatePLDictionary(NULL, NULL); - WMPLSetCaseSensitive(True); - /* save dock state to file */ if (!wPreferences.flags.nodock) { wDockSaveState(scr, old_state); @@ -1009,8 +1007,13 @@ void wScreenSaveState(WScreen * scr) WMPutInPLDictionary(scr->session_state, dWorkspace, foo); } + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ /* clean up */ - WMPLSetCaseSensitive(False); + /* WMPLSetCaseSensitive(False); */ wMenuSaveState(scr); diff --git a/src/session.c b/src/session.c index c01eba2f..ae5f1525 100644 --- a/src/session.c +++ b/src/session.c @@ -241,14 +241,15 @@ static WMPropList *makeWindowState(WWindow * wwin, WApplication * wapp) snprintf(buffer, sizeof(buffer), "%u", mask); shortcut = WMCreatePLString(buffer); - win_state = WMCreatePLDictionary(sName, name, - sCommand, cmd, - sWorkspace, workspace, - sShaded, shaded, - sMiniaturized, miniaturized, - sMaximized, maximized, - sHidden, hidden, - sShortcutMask, shortcut, sGeometry, geometry, NULL); + win_state = WMCreatePLDictionary(sName, name); + WMPutInPLDictionary(win_state, sCommand, cmd); + WMPutInPLDictionary(win_state, sWorkspace, workspace); + WMPutInPLDictionary(win_state, sShaded, shaded); + WMPutInPLDictionary(win_state, sMiniaturized, miniaturized); + WMPutInPLDictionary(win_state, sMaximized, maximized); + WMPutInPLDictionary(win_state, sHidden, hidden); + WMPutInPLDictionary(win_state, sShortcutMask, shortcut); + WMPutInPLDictionary(win_state, sGeometry, geometry); WMReleasePropList(name); WMReleasePropList(cmd); @@ -313,7 +314,7 @@ void wSessionSaveState(WScreen * scr) return; } - list = WMCreatePLArray(NULL); + list = WMCreateEmptyPLArray(); wapp_list = WMCreateArray(16); @@ -487,8 +488,6 @@ void wSessionRestoreState(WScreen *scr) if (!scr->session_state) return; - WMPLSetCaseSensitive(True); - apps = WMGetFromPLDictionary(scr->session_state, sApplications); if (!apps) return; @@ -579,8 +578,13 @@ void wSessionRestoreState(WScreen *scr) if (class) wfree(class); } + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ /* clean up */ - WMPLSetCaseSensitive(False); + /* WMPLSetCaseSensitive(False); */ } void wSessionRestoreLastWorkspace(WScreen * scr) @@ -594,8 +598,6 @@ void wSessionRestoreLastWorkspace(WScreen * scr) if (!scr->session_state) return; - WMPLSetCaseSensitive(True); - wks = WMGetFromPLDictionary(scr->session_state, sWorkspace); if (!wks || !WMIsPLString(wks)) return; @@ -605,8 +607,13 @@ void wSessionRestoreLastWorkspace(WScreen * scr) if (!value) return; + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ /* clean up */ - WMPLSetCaseSensitive(False); + /* WMPLSetCaseSensitive(False); */ /* Get the workspace number for the workspace name */ w = wGetWorkspaceNumber(scr, value); diff --git a/src/wdefaults.c b/src/wdefaults.c index 7aad3db5..e2fe5009 100644 --- a/src/wdefaults.c +++ b/src/wdefaults.c @@ -174,15 +174,18 @@ static WMPropList *get_value_from_instanceclass(const char *value) key = WMCreatePLString(value); - WMPLSetCaseSensitive(True); - if (w_global.domain.window_attr->dictionary) val = key ? WMGetFromPLDictionary(w_global.domain.window_attr->dictionary, key) : NULL; if (key) WMReleasePropList(key); - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ return val; } @@ -219,8 +222,6 @@ void wDefaultFillAttributes(const char *instance, const char *class, dn = get_value_from_instanceclass(instance); dc = get_value_from_instanceclass(class); - WMPLSetCaseSensitive(True); - if ((w_global.domain.window_attr->dictionary) && (useGlobalDefault)) da = WMGetFromPLDictionary(w_global.domain.window_attr->dictionary, AnyWindow); @@ -311,8 +312,13 @@ void wDefaultFillAttributes(const char *instance, const char *class, APPLY_VAL(value, no_language_button, ANoLanguageButton); #endif + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ /* clean up */ - WMPLSetCaseSensitive(False); + /* WMPLSetCaseSensitive(False); */ } static WMPropList *get_generic_value(const char *instance, const char *class, @@ -322,8 +328,6 @@ static WMPropList *get_generic_value(const char *instance, const char *class, value = NULL; - WMPLSetCaseSensitive(True); - /* Search the icon name using class and instance */ if (class && instance) { char *buffer; @@ -371,7 +375,12 @@ static WMPropList *get_generic_value(const char *instance, const char *class, value = WMGetFromPLDictionary(dict, option); } - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ return value; } @@ -545,15 +554,13 @@ void wDefaultChangeIcon(const char *instance, const char *class, const char *fil int same = 0; if (!dict) { - dict = WMCreatePLDictionary(NULL, NULL); + dict = WMCreateEmptyPLDictionary(); if (dict) db->dictionary = dict; else return; } - WMPLSetCaseSensitive(True); - if (instance && class) { char *buffer; @@ -570,7 +577,7 @@ void wDefaultChangeIcon(const char *instance, const char *class, const char *fil if (file) { value = WMCreatePLString(file); - icon_value = WMCreatePLDictionary(AIcon, value, NULL); + icon_value = WMCreatePLDictionary(AIcon, value); WMReleasePropList(value); def_win = WMGetFromPLDictionary(dict, AnyWindow); @@ -600,7 +607,12 @@ void wDefaultChangeIcon(const char *instance, const char *class, const char *fil if (icon_value) WMReleasePropList(icon_value); - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ } void wDefaultPurgeInfo(const char *instance, const char *class) @@ -612,8 +624,6 @@ void wDefaultPurgeInfo(const char *instance, const char *class) init_wdefaults(); } - WMPLSetCaseSensitive(True); - buffer = wmalloc(strlen(class) + strlen(instance) + 2); sprintf(buffer, "%s.%s", instance, class); key = WMCreatePLString(buffer); @@ -631,7 +641,12 @@ void wDefaultPurgeInfo(const char *instance, const char *class) wfree(buffer); WMReleasePropList(key); - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ } /* --------------------------- Local ----------------------- */ diff --git a/src/winspector.c b/src/winspector.c index eacfdf95..0af1a789 100644 --- a/src/winspector.c +++ b/src/winspector.c @@ -597,7 +597,7 @@ static void saveSettings(WMWidget *button, void *client_data) dict = db->dictionary; if (!dict) { - dict = WMCreatePLDictionary(NULL, NULL); + dict = WMCreateEmptyPLDictionary(); if (dict) { db->dictionary = dict; } else { @@ -609,10 +609,8 @@ static void saveSettings(WMWidget *button, void *client_data) if (showIconFor(WMWidgetScreen(button), panel, NULL, NULL, USE_TEXT_FIELD) < 0) return; - WMPLSetCaseSensitive(True); - - winDic = WMCreatePLDictionary(NULL, NULL); - appDic = WMCreatePLDictionary(NULL, NULL); + winDic = WMCreateEmptyPLDictionary(); + appDic = WMCreateEmptyPLDictionary(); /* Save the icon info */ /* The flag "Ignore client suplied icon is not selected" */ @@ -709,8 +707,13 @@ static void saveSettings(WMWidget *button, void *client_data) UpdateDomainFile(db); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ /* clean up */ - WMPLSetCaseSensitive(False); + /* WMPLSetCaseSensitive(False); */ } static void applySettings(WMWidget *button, void *client_data) diff --git a/src/workspace.c b/src/workspace.c index dad14eb5..6686c1a1 100644 --- a/src/workspace.c +++ b/src/workspace.c @@ -852,10 +852,10 @@ void wWorkspaceSaveState(WScreen * scr, WMPropList * old_state) make_keys(); old_wks_state = WMGetFromPLDictionary(old_state, dWorkspaces); - parr = WMCreatePLArray(NULL); + parr = WMCreateEmptyPLArray(); for (i = 0; i < scr->workspace_count; i++) { pstr = WMCreatePLString(scr->workspaces[i]->name); - wks_state = WMCreatePLDictionary(dName, pstr, NULL); + wks_state = WMCreatePLDictionary(dName, pstr); WMReleasePropList(pstr); if (!wPreferences.flags.noclip) { pstr = wClipSaveWorkspaceState(scr, i); diff --git a/util/convertfonts.c b/util/convertfonts.c index 1674ced8..d653e724 100644 --- a/util/convertfonts.c +++ b/util/convertfonts.c @@ -131,7 +131,12 @@ int main(int argc, char **argv) /* this contradicts big time with getstyle */ setlocale(LC_ALL, ""); - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ style = WMReadPropListFromFile(file); if (!style) { diff --git a/util/geticonset.c b/util/geticonset.c index 4505551c..8941bb11 100644 --- a/util/geticonset.c +++ b/util/geticonset.c @@ -109,7 +109,7 @@ int main(int argc, char **argv) if (window_attrs && WMIsPLDictionary(window_attrs)) { icon_value = WMGetFromPLDictionary(window_attrs, icon_key); if (icon_value) { - icondic = WMCreatePLDictionary(icon_key, icon_value, NULL); + icondic = WMCreatePLDictionary(icon_key, icon_value); WMPutInPLDictionary(iconset, window_name, icondic); } } diff --git a/util/getstyle.c b/util/getstyle.c index a2935892..3cde01ac 100644 --- a/util/getstyle.c +++ b/util/getstyle.c @@ -337,7 +337,12 @@ int main(int argc, char **argv) return 1; } - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ path = wdefaultspathfordomain("WindowMaker"); @@ -357,7 +362,7 @@ int main(int argc, char **argv) prop = val; } - style = WMCreatePLDictionary(NULL, NULL); + style = WMCreateEmptyPLDictionary(); for (i = 0; options[i] != NULL; i++) { key = WMCreatePLString(options[i]); diff --git a/util/setstyle.c b/util/setstyle.c index 36599ae4..4fcdfcc2 100644 --- a/util/setstyle.c +++ b/util/setstyle.c @@ -427,7 +427,12 @@ int main(int argc, char **argv) file = argv[0]; - WMPLSetCaseSensitive(False); + /* + * Rust rewrite note: this API surface has been removed, but we leave in + * a record of where it was invoked to set a value other than the + * default, in case it helps to track down bugs in the future. + */ + /* WMPLSetCaseSensitive(False); */ path = wdefaultspathfordomain("WindowMaker"); diff --git a/util/wdwrite.c b/util/wdwrite.c index 2ff16706..c169ad64 100644 --- a/util/wdwrite.c +++ b/util/wdwrite.c @@ -103,7 +103,7 @@ int main(int argc, char **argv) dict = WMReadPropListFromFile(path); if (!dict) { - dict = WMCreatePLDictionary(key, value, NULL); + dict = WMCreatePLDictionary(key, value); } else { WMPutInPLDictionary(dict, key, value); } diff --git a/util/wmsetbg.c b/util/wmsetbg.c index dc747d04..2e65cace 100644 --- a/util/wmsetbg.c +++ b/util/wmsetbg.c @@ -1199,14 +1199,14 @@ static void changeTextureForWorkspace(const char *domain, char *texture, int wor array = getValueForKey("WindowMaker", "WorkspaceSpecificBack"); if (!array) { - array = WMCreatePLArray(NULL, NULL); + array = WMCreateEmptyPLArray(); } j = WMGetPropListItemCount(array); if (workspace >= j) { WMPropList *empty; - empty = WMCreatePLArray(NULL, NULL); + empty = WMCreateEmptyPLArray(); while (j++ < workspace - 1) { WMAddToPLArray(array, empty); diff --git a/wutil-rs/Cargo.toml b/wutil-rs/Cargo.toml index 9eff16fb..66e739ea 100644 --- a/wutil-rs/Cargo.toml +++ b/wutil-rs/Cargo.toml @@ -8,3 +8,8 @@ crate-type = ["staticlib"] [build-dependencies] cc = "1.0" + +[dependencies] +atomic-write-file = "0.3" +nom = "8.0" +nom-language = "0.1" diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am index 8974f2d8..7955beb3 100644 --- a/wutil-rs/Makefile.am +++ b/wutil-rs/Makefile.am @@ -2,9 +2,12 @@ AUTOMAKE_OPTIONS = RUST_SOURCES = \ src/array.rs \ + src/defines.c \ + src/defines.rs \ src/find_file.rs \ src/lib.rs \ - src/memory.rs + src/memory.rs \ + src/prop_list.rs RUST_EXTRA = \ Cargo.lock \ diff --git a/wutil-rs/src/data.rs b/wutil-rs/src/data.rs index aff4788e..dab84a49 100644 --- a/wutil-rs/src/data.rs +++ b/wutil-rs/src/data.rs @@ -19,6 +19,19 @@ pub enum Format { /// Rust. pub struct Data(Rc>); +impl Data { + /// Runs `f` on a borrow of `self`'s data, returning whatever `f` does. + /// + /// This is provided because the internal structure of `Data` does not make + /// it easy to borrow its contents directly. As we migrate away from the + /// original C interfaces to WINGs data structures, we may be able to + /// provide a direct borrow, which would make this method obsolete. (That + /// would be a good thing.) + pub fn with_bytes(&self, f: impl FnOnce(&[u8]) -> R) -> R { + f(&self.0.borrow().bytes) + } +} + struct Inner { bytes: Vec, format: Format, diff --git a/wutil-rs/src/find_file.rs b/wutil-rs/src/find_file.rs index 5a0dbeee..bcc4447d 100644 --- a/wutil-rs/src/find_file.rs +++ b/wutil-rs/src/find_file.rs @@ -12,21 +12,34 @@ //! whose first component is `~` will still be resolved relatively to the //! current user's home directory. //! -//! Keep in mind that these utilities are not strictly correct as originally -//! designed: a file path that appears valid when it is checked in a subroutine -//! may become invalid if the file is deleted between when the path is checked -//! and when downstream code attempts to open the file. A better design would -//! open the file and return a live file pointer instead of simply returning a -//! path that is likely to work. Future work should redesign this module to -//! avoid this issue. +//! ## Rust rewrite notes +//! +//! Many of these utilities are not strictly correct as originally designed: a +//! file path that appears valid when it is checked in a subroutine may become +//! invalid if the file is deleted between when the path is checked and when +//! downstream code attempts to open the file. (This is a TOCTOU issue.) A +//! better design would open the file and return a live file pointer instead of +//! simply returning a path that is likely to work. Future work should redesign +//! this module to avoid this issue. + +use crate::defines; use std::{ env, - ffi::OsStr, + ffi::{CStr, OsStr}, fs::File, + io, path::{Component, Path, PathBuf}, }; +/// Tries to interpret `s` as a UTF-8 path. Returns `None` if decoding `s` +/// fails. +pub fn path_from_cstr(s: &CStr) -> Option { + String::from_utf8(s.to_bytes().iter().copied().collect()) + .ok() + .map(|p| PathBuf::from(p)) +} + /// If `file` is an absolute path can be opened, returns that path. Paths /// starting with `~` are treated as absolute, and the user's home directory is /// substituted for `~` (so `~/foo` becomes `(users's home directory)/foo`). If @@ -78,13 +91,116 @@ pub fn in_paths<'a>(paths: impl Iterator, file: &Path) -> Optio None } +/// Returns the root path that user data will be stored at. This is probably +/// `$HOME/GNUstep` (or some other default if a different path was specified for +/// [`defaults::gsuser_subdir`] at compilation time), unless the environment +/// variable `WMAKER_USER_ROOT` is defined, in which case it's that. +pub fn user_gnustep_path() -> Option { + match env::var("WMAKER_USER_ROOT") { + Ok(path) => { + if let Some(path) = absolute(&PathBuf::from(&path)) { + return Some(path); + } + } + Err(env::VarError::NotUnicode(_)) => { + // TODO: warn. + } + Err(env::VarError::NotPresent) => (), + } + + match (env::home_dir(), defines::gsuser_subdir()) { + (None, _) => { + // TODO: warn. + None + } + (Some(mut parent), Some(subdir)) => { + parent.push(subdir); + Some(parent) + } + (Some(_), None) => { + // TODO: warn. + None + } + } +} + +/// Creates the directory at `p` and all of its parents, respecting the current +/// umask as much as possible. Only allows creation of paths under +/// [`user_gnustep_path`]. +pub fn create_path_hierarchy>(p: P) -> io::Result<()> { + let p = p.as_ref(); + let Ok(canonical_p) = p.canonicalize() else { + return Err(io::Error::other("cannot canonicalize requested path")); + }; + let Some(wmaker_user_root) = user_gnustep_path() else { + return Err(io::Error::other("cannot determine WMAKER_USER_ROOT. try setting the environment variable WMAKER_USER_ROOT.")); + }; + let Ok(wmaker_user_root) = Path::new(&wmaker_user_root).canonicalize() else { + return Err(io::Error::other( + "cannot canonicalize path for WMAKER_USER_ROOT", + )); + }; + + if !canonical_p.starts_with(&wmaker_user_root) { + return Err(io::Error::other( + "requested path is not under WMAKER_USER_ROOT", + )); + } + + create_path_hierarchy_impl(&canonical_p) +} + +/// Removes the file at `p` (and anything under it, if `p` is a directory). Only +/// allows deletion of files under [`user_gnustep_path`]`/Defaults` or +/// [`user_gnustep_path`]`/Library`. +pub fn remove_path_hierarchy>(p: P) -> io::Result<()> { + let p = p.as_ref(); + let Ok(canonical_p) = p.canonicalize() else { + return Err(io::Error::other("cannot canonicalize requested path")); + }; + let Some(wmaker_user_root) = user_gnustep_path() else { + return Err(io::Error::other("cannot determine WMAKER_USER_ROOT. try setting the environment variable WMAKER_USER_ROOT.")); + }; + let Ok(wmaker_user_root) = Path::new(&wmaker_user_root).canonicalize() else { + return Err(io::Error::other( + "cannot canonicalize path for WMAKER_USER_ROOT", + )); + }; + + let mut defaults = wmaker_user_root.clone(); + defaults.push("Defaults"); + let mut library = wmaker_user_root; + library.push("Library"); + if !canonical_p.starts_with(&defaults) && !canonical_p.starts_with(&library) { + return Err(io::Error::other( + "requested path is not under WMAKER_USER_ROOT/Defaults or WMAKER_USER_ROOT/Library", + )); + } + + std::fs::remove_dir_all(canonical_p) +} + +#[cfg(target_family = "unix")] +fn create_path_hierarchy_impl(p: &Path) -> io::Result<()> { + use std::os::unix::fs::DirBuilderExt; + std::fs::DirBuilder::new() + .recursive(true) + .mode(0o777) + .create(p) +} + +#[cfg(not(target_family = "unix"))] +fn create_path_hierarchy_impl(p: &Path) -> io::Result<()> { + std::fs::DirBuilder::new().recursive(true).create(p) +} + pub mod ffi { - use super::{absolute, in_paths}; + use super::{absolute, create_path_hierarchy, in_paths, path_from_cstr, remove_path_hierarchy, user_gnustep_path}; use crate::memory::alloc_bytes; use std::{ env, - ffi::{CStr, OsStr, c_char, c_int}, + ffi::{c_char, c_int, CStr, OsStr}, iter, os::unix::ffi::OsStrExt, path::{Path, PathBuf}, @@ -218,4 +334,64 @@ pub mod ffi { return -1; } } + + /// Delegates to [`create_path_hierarchy`]. + /// + /// ## Rust rewrite notes + /// + /// The original C implementation of this function stripped the last path + /// component if it did not end in a `'/'` (i.e., it looked like a regular + /// file instead of a directory). This behavior does not appear to be relied + /// upon by any existing callers except for `WMWritePropListToFile`, which + /// has been rewritten, and it is not super portable (and annoying because + /// it adds weird edge cases). So each component of `path` is treated as a + /// directory to be made. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wmkdirhier(path: *const c_char) -> c_int { + if path.is_null() { + return 0; + } + let path = unsafe { CStr::from_ptr(path) }; + let Some(path) = path_from_cstr(path) else { + return 0; + }; + if create_path_hierarchy(path).is_ok() { + return 1; + } else { + return 0; + } + } + + /// Delegates to [`remove_path_hierarchy`]. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wrmdirhier(path: *const c_char) -> c_int { + if path.is_null() { + return 0; + } + let path = unsafe { CStr::from_ptr(path) }; + let Some(path) = path_from_cstr(path) else { + return 0; + }; + if remove_path_hierarchy(path).is_ok() { + return 1; + } else { + return 0; + } + } + + /// Delegates to [`user_gnustep_path`]. Returns the path, which must be The + /// returned value must be freed with [`crate::memory::free_bytes`] or + /// [`crate::memory::ffi::wfree`], path cannot be determined. + /// + /// ## Rust rewrite notes + /// + /// This was originally in `WINGs/userdefaults.c`. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn wusergnusteppath() -> *mut c_char { + if let Some(path) = user_gnustep_path() { + to_c_str(&path) + } else { + ptr::null_mut() + } + } } diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs index 081e4412..47577b48 100644 --- a/wutil-rs/src/lib.rs +++ b/wutil-rs/src/lib.rs @@ -3,3 +3,4 @@ pub mod data; pub mod defines; pub mod find_file; pub mod memory; +pub mod prop_list; diff --git a/wutil-rs/src/prop_list.rs b/wutil-rs/src/prop_list.rs new file mode 100644 index 00000000..4a2376f0 --- /dev/null +++ b/wutil-rs/src/prop_list.rs @@ -0,0 +1,871 @@ +//! Property lists: shared, tree-structured data. +//! +//! ## Rust rewrite notes +//! +//! This implementation should be good enough to facilitate migrating from C to +//! Rust and trasitioning away from using property lists everywhere instead of +//! proper structs. `PropList`s are a really cool general-purpose tool for +//! attaching data to objects and persisting it to disk. But in Rust, it is +//! easier to use proper structs with typed fields and appropriate `#[derive]` +//! declarations to generate code for serialization and deserialization +//! (presumably using Serde). +//! +//! As code that uses `PropList`s is rewritte in Rust, we should work on +//! migrating away from use of `PropList`s. Objects whose fields that can be +//! statically typed should be represented as structs. They may still be +//! persisted by cramming them into `PropList`s and writing those to disk, but +//! it would be better still to implement Serde-based serialization to and from +//! the property list format. +//! +//! The `PropList` implementation itself can also be improved substantially. See +//! [`PropList`] for thoughts on this. + +use atomic_write_file::unix::OpenOptionsExt; + +use std::{ + cell::RefCell, + collections::{hash_map, HashMap}, + ffi::{CString, OsStr, OsString}, + fmt, hash, + io::{self, BufWriter, Write}, + path::Path, + process::Command, + ptr, + rc::Rc, +}; + +use crate::find_file; + +pub mod parser; +pub mod writer; + +/// Payload of a [`PropList`]. +#[derive(Eq, PartialEq)] +pub enum Node { + /// Text data. This is UTF-8 encoded and null-safe. + /// + /// ## Rust rewrite notes + /// + /// It would be better for this to be a `String`, but the C interface + /// requires borrows of C-style strings. + String(CString), + /// Binary data. + Data(Vec), + /// Array of child `PropList`s. + Array(Vec), + /// `PropList`-keyed table of child `PropList`s. Keys should only have + /// `Node::String` or `Node::Data` payloads, although there is almost no + /// enforcement of this. + Dictionary(HashMap), +} + +impl hash::Hash for Node { + fn hash(&self, h: &mut H) { + match self { + Node::String(s) => s.hash(h), + Node::Data(d) => d.hash(h), + Node::Array(a) => { + for p in a { + p.hash(h); + } + } + Node::Dictionary(d) => { + for (k, v) in d { + k.hash(h); + v.hash(h); + } + } + } + } +} + +impl fmt::Debug for Node { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}", + writer::Display { + inline: writer::Inline::Soft, + clear_left: false, + indentation: 0, + increment: 2, + node: self, + } + ) + } +} + +fn merge_shallow(dest: PropList, source: PropList) { + if ptr::eq(dest.0.as_ref(), source.0.as_ref()) { + return; + } + let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else { + return; + }; + let Node::Dictionary(ref source) = *source.0.borrow() else { + return; + }; + for (k, v) in source { + if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) { + // Don't borrow k if it is already borrowed as dest. + continue; + } + dest_items.insert(k.clone(), v.clone()); + } +} + +fn merge_deep(dest: PropList, source: PropList) { + if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) { + return; + } + let Node::Dictionary(dest_items) = &mut *dest.0.borrow_mut() else { + return; + }; + let Node::Dictionary(source_items) = &*source.0.borrow() else { + return; + }; + + for (key, value) in source_items { + if key.0.try_borrow().is_err() || value.0.try_borrow().is_err() { + // Something has already borrowed key or value. This may happen if + // source contains pointers that are also in dest, or if dest is + // cyclic. This is bad, but we just bail out. + continue; + } + match dest_items.entry(key.clone()) { + hash_map::Entry::Vacant(v) => { + // Dest has nothing at key. Insert value from source. + v.insert(value.clone()); + } + hash_map::Entry::Occupied(mut o) => { + let recur = match *o.get().0.borrow() { + Node::Dictionary(_) => true, + _ => false, + }; + if recur { + // dest[key] is a dictionary. Recur on dest[key] and value from source. + merge_deep(o.get().clone(), value.clone()); + } else { + // dest[key] is not a dictionary. Overwrite with value from source. + o.insert(value.clone()); + } + } + } + } +} + +fn subtract_shallow(dest: PropList, source: PropList) { + if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) { + if let Node::Dictionary(ref mut items) = *dest.0.borrow_mut() { + items.clear(); + } + return; + } + + let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else { + return; + }; + let Node::Dictionary(ref source_items) = *source.0.borrow() else { + return; + }; + for (k, v) in source_items.iter() { + if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) { + continue; + } + if let hash_map::Entry::Occupied(o) = dest_items.entry(k.clone()) { + if o.get() == v { + o.remove(); + } + } + } +} + +fn subtract_deep(dest: PropList, source: PropList) { + if ptr::eq(dest.0.as_ptr(), source.0.as_ptr()) { + if let Node::Dictionary(ref mut items) = *dest.0.borrow_mut() { + items.clear(); + } + return; + } + + let Node::Dictionary(ref mut dest_items) = *dest.0.borrow_mut() else { + return; + }; + let Node::Dictionary(ref source_items) = *source.0.borrow() else { + return; + }; + for (k, v) in source_items.iter() { + if ptr::eq(dest.0.as_ptr(), k.0.as_ptr()) { + continue; + } + if let hash_map::Entry::Occupied(o) = dest_items.entry(k.clone()) { + if o.get() == v { + o.remove(); + continue; + } + let recur = match (&*o.get().0.borrow(), &*v.0.borrow()) { + (Node::Dictionary(_), Node::Dictionary(_)) => true, + _ => false, + }; + if recur { + subtract_deep(o.get().clone(), v.clone()); + } + } + } +} + +/// Data graph with convenient (de)serialization to/from the [property +/// list](https://en.wikipedia.org/wiki/Property_list) format. +/// +/// ## Rust rewrite notes +/// +/// The original WUtils `PropList` was a reference-counted pointer, so it +/// supported shallow copy and shared-memory semantics that we have continued to +/// try to support in the Rust implementation. As a result, `PropList` is a thin +/// wrapper around an `Rc>`. There are several reasons why this is +/// probably unnecessary and something we should migrate away from: +/// +/// * It allows for the creation of non-tree structures, which was probably +/// never intended. (A degenerate `PropList` could even have itself as a child.) +/// * It complicates recursive operations on `PropList`s (equality checks, +/// merging, or taking differences) because a given `PropList` may occur +/// multiple times when traversing two `PropList`s, but `Rc` only allows it to be +/// mutably borrowed once. +/// * Allowing subtrees to be shared between two different `PropList`s +/// may lead to spooky action at a distance and may not actually be taken +/// advantage of by any client code. +/// +/// As client code is migrated into Rust, it would be great to move away from +/// this implementation to a simpler one. As discussed in the module-level +/// rewrite notes, we may even be able to do away with `PropList` itself +/// (perhaps in favor of using Serde to write to and from property list files on +/// disk). +#[derive(Clone)] +pub struct PropList(Rc>); + +impl PropList { + pub fn new(node: Node) -> Self { + PropList(Rc::new(RefCell::new(node))) + } + + /// Reads `r` to the end and tries to parse it into a `PropList`. + pub fn from_file>(path: P) -> Result { + let path = path.as_ref().to_path_buf(); + let buf = std::fs::read_to_string(&path).map_err(|e| format!("{}", e))?; + parser::from_str(buf.as_str()) + } + + // Runs `command` and tries to parse a PropList from its standard output. + pub fn from_command>(command: S) -> Result { + let command: OsString = command.as_ref().to_os_string(); + let output = Command::new("/bin/sh") + .arg("-c") + .arg(command.clone()) + .output() + .map_err(|e| format!("{}", e))?; + let output = str::from_utf8(&output.stdout).map_err(|e| format!("{}", e))?; + parser::from_str(&output) + } + + pub fn display_indented<'s>(&'s self) -> impl fmt::Display + 's { + writer::Display { + inline: writer::Inline::Soft, + clear_left: false, + indentation: 0, + increment: 2, + node: self.0.borrow(), + } + } + + pub fn display_unindented<'s>(&'s self) -> impl fmt::Display + 's { + writer::Display { + inline: writer::Inline::Hard, + clear_left: false, + indentation: 0, + increment: 0, + node: self.0.borrow(), + } + } +} + +impl Eq for PropList {} + +impl PartialEq for PropList { + fn eq(&self, other: &Self) -> bool { + *self.0.borrow() == *other.0.borrow() + } +} + +impl hash::Hash for PropList { + fn hash(&self, h: &mut H) { + self.0.borrow().hash(h) + } +} + +impl fmt::Debug for PropList { + fn fmt(&self, out: &mut fmt::Formatter) -> fmt::Result { + write!(out, "{:?}", self.0.borrow())?; + Ok(()) + } +} + +impl PropList { + pub fn deep_clone(&self) -> Self { + match &*self.0.borrow() { + Node::String(s) => PropList::new(Node::String(s.clone())), + Node::Data(d) => PropList::new(Node::Data(d.clone())), + Node::Array(items) => { + PropList::new(Node::Array(items.iter().map(|x| x.deep_clone()).collect())) + } + Node::Dictionary(items) => PropList::new(Node::Dictionary( + items + .iter() + .map(|(k, v)| (k.deep_clone(), v.deep_clone())) + .collect(), + )), + } + } + + /// Atomically serialize this `PropList` to `path`, creating any necessary + /// parent directories. + /// + /// `path` is written to atomically: either the serialized `PropList` will + /// be completely written to `path`, or the operation will fail and any + /// existing file at `path` will not be modified. + /// + /// ## Rust rewrite notes + /// + /// As originally noted in `proplist.c`, a Coverity security bug report + /// flagged the need to preserve the permissions on the file being written + /// to. This should be respected in the rewritten code under Unix-like + /// operataing systems. + pub fn write_to_file>(&self, path: &P) -> io::Result<()> { + self.write_to_file_impl(path.as_ref()) + } + + fn write_to_file_impl(&self, path: &Path) -> io::Result<()> { + if let Some(parent) = path.parent() { + find_file::create_path_hierarchy(parent)?; + } + let file = atomic_write_file::AtomicWriteFile::options() + .preserve_mode(true) + .open(&path)?; + let mut out = BufWriter::new(file); + writeln!(&mut out, "{}", self.display_indented())?; + out.into_inner()?.commit() + } +} + +pub mod ffi { + use crate::{data::Data, find_file::path_from_cstr}; + + use super::{ + merge_deep, merge_shallow, parser, subtract_deep, subtract_shallow, Node, PropList, + }; + + use std::{ + collections::HashMap, ffi::{c_char, c_int, c_uchar, c_uint, CStr, CString, OsString}, ptr, str::FromStr + }; + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreatePLString(s: *const c_char) -> *mut PropList { + if s.is_null() { + return ptr::null_mut(); + } + let s = unsafe { CStr::from_ptr(s) }; + Box::leak(Box::new(PropList::new(Node::String(s.into())))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreatePLData(data: *mut Data) -> *mut PropList { + if data.is_null() { + return ptr::null_mut(); + } + let data = unsafe { &*data }; + data.with_bytes(|b| Box::leak(Box::new(PropList::new(Node::Data(Vec::from(b)))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreatePLDataWithBytes( + bytes: *const c_uchar, + length: c_uint, + ) -> *mut PropList { + if bytes.is_null() { + return ptr::null_mut(); + } + let bytes = unsafe { &*ptr::slice_from_raw_parts(bytes.cast::(), length as usize) }; + Box::leak(Box::new(PropList::new(Node::Data(Vec::from(bytes))))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreatePLArrayFromSlice( + elems: *mut PropList, + length: c_uint, + ) -> *mut PropList { + if elems.is_null() { + return ptr::null_mut(); + } + let elems = unsafe { &*ptr::slice_from_raw_parts(elems, length as usize) }; + Box::leak(Box::new(PropList::new(Node::Array( + elems.iter().cloned().collect(), + )))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateEmptyPLArray() -> *mut PropList { + Box::leak(Box::new(PropList::new(Node::Array(Vec::new())))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreatePLDictionary( + key: *mut PropList, + value: *mut PropList, + ) -> *mut PropList { + if key.is_null() || value.is_null() { + return Box::leak(Box::new(PropList::new(Node::Dictionary(HashMap::new())))); + } + let key = unsafe { (*key).clone() }; + let value = unsafe { (*value).clone() }; + Box::leak(Box::new(PropList::new(Node::Dictionary( + [(key, value)].into(), + )))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreateEmptyPLDictionary() -> *mut PropList { + Box::leak(Box::new(PropList::new(Node::Dictionary(HashMap::new())))) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRetainPropList(plist: *mut PropList) -> *mut PropList { + if plist.is_null() { + return ptr::null_mut(); + } + unsafe { Box::leak(Box::new((*plist).clone())) } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMReleasePropList(plist: *mut PropList) { + if plist.is_null() { + return; + } + let _ = unsafe { ptr::read(plist) }; + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMInsertInPLArray( + plist: *mut PropList, + index: c_int, + item: *mut PropList, + ) { + if plist.is_null() || index < 0 || item.is_null() { + return; + } + let plist = unsafe { &mut *plist }; + if let Node::Array(ref mut items) = *plist.0.borrow_mut() { + let item = unsafe { (*item).clone() }; + items.insert(index as usize, item); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMAddToPLArray(plist: *mut PropList, item: *mut PropList) { + if plist.is_null() || item.is_null() { + return; + } + let plist = unsafe { &mut *plist }; + if let Node::Array(ref mut items) = *plist.0.borrow_mut() { + let item = unsafe { (*item).clone() }; + items.push(item); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMDeleteFromPLArray(plist: *mut PropList, index: c_int) { + if plist.is_null() || index < 0 { + return; + } + let plist = unsafe { &mut *plist }; + if let Node::Array(ref mut items) = *plist.0.borrow_mut() { + items.remove(index as usize); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRemoveFromPLArray(plist: *mut PropList, item: *mut PropList) { + if plist.is_null() || item.is_null() { + return; + } + let plist = unsafe { &mut *plist }; + let item = unsafe { &*item }; + if let Node::Array(ref mut items) = *plist.0.borrow_mut() { + if let Some((i, _)) = items.iter().enumerate().find(|(_, x)| *x == item) { + items.remove(i); + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMPutInPLDictionary( + plist: *mut PropList, + key: *mut PropList, + value: *mut PropList, + ) { + if plist.is_null() || key.is_null() || value.is_null() { + return; + } + let plist = unsafe { &mut *plist }; + if let Node::Dictionary(ref mut items) = *plist.0.borrow_mut() { + let key = unsafe { (*key).clone() }; + let value = unsafe { (*value).clone() }; + items.insert(key, value); + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMRemoveFromPLDictionary(plist: *mut PropList, key: *mut PropList) { + if plist.is_null() || key.is_null() { + return; + } + let plist = unsafe { &mut *plist }; + let key = unsafe { &*key }; + if let Node::Dictionary(ref mut items) = *plist.0.borrow_mut() { + items.remove(key); + } + } + + /// If `dest` and `source` are both dictionaries, overwrites entries in + /// `dest` with corresponding entries in `source`. + /// + /// If `recursive` is non-zero, this is done recursively for values in + /// `dest` and `source` that are both dictionaries. + /// + /// ## Rust rewrite notes + /// + /// This operation is used a few times. It may be worth keeping around + /// longer-term, although it might be hard to express if we do transition + /// away from `PropList`s to statically typed struct trees. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMMergePLDictionaries( + dest: *mut PropList, + source: *mut PropList, + recursive: c_int, + ) -> *mut PropList { + if dest.is_null() || ptr::eq(dest, source) || source.is_null() { + return dest; + } + + let dest = unsafe { (*dest).clone() }; + let source = unsafe { (*source).clone() }; + + if recursive == 0 { + merge_shallow(dest.clone(), source); + } else { + merge_deep(dest.clone(), source); + } + + return Box::leak(Box::new(dest)); + } + + /// If `dest` and `source` are both dictionaries, removes from `dest` any + /// `(k, v)` pairs where `dest[k] == source[k]`. + /// + /// If `recursive` is non-zero, this is done recursively over subtrees of + /// `dest` and `source` when both `dest` and `source` are dictionaries for + /// keys of `source` that are also keys of `dest`. + /// + /// ## Rust rewrite notes + /// + /// This operation is only used in one place. It may be better to implement + /// this behavior as a one-off closer to where it is used, or with a + /// different API differently (e.g., as a function of a more general + /// proplist diff). + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMSubtractPLDictionaries( + dest: *mut PropList, + source: *mut PropList, + recursive: c_int, + ) -> *mut PropList { + if dest.is_null() { + return ptr::null_mut(); + } + if source.is_null() { + return dest; + } + + let dest = unsafe { (*dest).clone() }; + let source = unsafe { (*source).clone() }; + + if recursive == 0 { + subtract_shallow(dest.clone(), source); + } else { + subtract_deep(dest.clone(), source); + } + + Box::leak(Box::new(dest)) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetPropListItemCount(plist: *mut PropList) -> c_int { + if plist.is_null() { + return 0; + } + let plist = unsafe { &*plist }; + match &*plist.0.borrow() { + Node::Array(xs) => xs.len() as c_int, + Node::Dictionary(xs) => xs.len() as c_int, + _ => 0, + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMIsPLString(plist: *mut PropList) -> c_int { + if plist.is_null() { + return 0; + } + let plist = unsafe { &*plist }; + match &*plist.0.borrow() { + Node::String(_) => 1, + _ => 0, + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMIsPLData(plist: *mut PropList) -> c_int { + if plist.is_null() { + return 0; + } + let plist = unsafe { &*plist }; + match &*plist.0.borrow() { + Node::Data(_) => 1, + _ => 0, + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMIsPLArray(plist: *mut PropList) -> c_int { + if plist.is_null() { + return 0; + } + let plist = unsafe { &*plist }; + match &*plist.0.borrow() { + Node::Array(_) => 1, + _ => 0, + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMIsPLDictionary(plist: *mut PropList) -> c_int { + if plist.is_null() { + return 0; + } + let plist = unsafe { &*plist }; + match &*plist.0.borrow() { + Node::Dictionary(_) => 1, + _ => 0, + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMIsPropListEqualTo(a: *mut PropList, b: *mut PropList) -> c_int { + if ptr::eq(a, b) { + return 1; + } + if a.is_null() { + return 0; + } + let a = unsafe { &*a }; + let b = unsafe { &*b }; + if a == b { + 1 + } else { + 0 + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFromPLString(plist: *mut PropList) -> *const c_char { + if plist.is_null() { + return ptr::null_mut(); + } + let plist = unsafe { &*plist }; + if let Node::String(ref s) = *plist.0.borrow() { + s.as_ref().as_ptr().cast::() + } else { + ptr::null() + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFromPLArray(plist: *mut PropList, index: c_int) -> *mut PropList { + if plist.is_null() || index < 0 { + return ptr::null_mut(); + } + let plist = unsafe { &*plist }; + if let Node::Array(ref items) = *plist.0.borrow() { + Box::leak(Box::new(items[index as usize].clone())) + } else { + ptr::null_mut() + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetFromPLDictionary(plist: *mut PropList, key: *mut PropList) -> *mut PropList { + if plist.is_null() || key.is_null() { + return ptr::null_mut(); + } + let plist = unsafe { &*plist }; + let key = unsafe { &*key }; + if let Node::Dictionary(ref items) = *plist.0.borrow() { + if let Some(item) = items.get(key) { + return Box::leak(Box::new(item.clone())); + } + } + ptr::null_mut() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetPLDictionaryKeys(plist: *mut PropList) -> *mut PropList { + if plist.is_null() { + return ptr::null_mut(); + } + let plist = unsafe { &*plist }; + + if let Node::Dictionary(ref items) = *plist.0.borrow() { + return Box::leak(Box::new(PropList::new(Node::Array(items.keys().cloned().collect())))); + } + ptr::null_mut() + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMDeepCopyPropList(plist: *mut PropList) -> *mut PropList { + if plist.is_null() { + return ptr::null_mut(); + } + let plist = unsafe { &*plist }; + Box::leak(Box::new(plist.deep_clone())) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMCreatePropListFromDescription(desc: *const c_char) -> *mut PropList { + if desc.is_null() { + return ptr::null_mut(); + } + let desc = unsafe { CStr::from_ptr(desc) }; + let Ok(desc) = desc.to_str() else { + return ptr::null_mut(); + }; + + match parser::from_str(desc) { + Ok(plist) => Box::leak(Box::new(plist)), + Err(_) => ptr::null_mut(), + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMGetPropListDescription( + plist: *mut PropList, + indented: c_int, + ) -> *mut c_char { + use std::io::Write; + + if plist.is_null() { + return ptr::null_mut(); + } + let plist = unsafe { &*plist }; + let mut buf = Vec::new(); + if indented != 0 { + if let Err(_) = write!(&mut buf, "{}", plist.display_indented()) { + return ptr::null_mut(); + } + } else { + if let Err(_) = write!(&mut buf, "{}", plist.display_unindented()) { + return ptr::null_mut(); + } + } + match CString::new(buf) { + Ok(s) => s.into_raw(), + Err(_) => ptr::null_mut(), + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMReadPropListFromFile(path: *const c_char) -> *mut PropList { + if path.is_null() { + return ptr::null_mut(); + } + let path = unsafe { CStr::from_ptr(path) }; + let Ok(path) = path.to_str() else { + return ptr::null_mut(); + }; + match PropList::from_file(path) { + Ok(plist) => Box::leak(Box::new(plist)), + Err(_) => { + // TODO: print error message. + ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMReadPropListFromPipe(command: *const c_char) -> *mut PropList { + if command.is_null() { + return ptr::null_mut(); + } + + let command = unsafe { CStr::from_ptr(command) }; + let Ok(command) = command.to_str() else { + return ptr::null_mut(); + }; + let command = OsString::from_str(command).unwrap(); + + let Ok(output) = std::process::Command::new("/bin/sh") + .arg("-c") + .arg(command) + .output() + else { + // TODO: print error message. + return ptr::null_mut(); + }; + let Ok(output) = String::from_utf8(output.stdout) else { + // TODO: print error message. + return ptr::null_mut(); + }; + match parser::from_str(&output) { + Ok(plist) => Box::leak(Box::new(plist)), + Err(_) => { + // TODO: print error message. + ptr::null_mut() + } + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMWritePropListToFile( + plist: *mut PropList, + path: *const c_char, + ) -> c_int { + if plist.is_null() || path.is_null() { + return 0; + } + let plist = unsafe { + &*plist + }; + + let path = unsafe { + CStr::from_ptr(path) + }; + let Some(path) = path_from_cstr(path) else { + // TODO: complain. + return 0; + }; + + match plist.write_to_file(&path) { + Ok(_) => return 1, + Err(_) => { + // TODO: complain. + return 0; + } + } + } +} diff --git a/wutil-rs/src/prop_list/parser.rs b/wutil-rs/src/prop_list/parser.rs new file mode 100644 index 00000000..92d37998 --- /dev/null +++ b/wutil-rs/src/prop_list/parser.rs @@ -0,0 +1,1463 @@ +//! Support for reading NeXTSTEP/OPENSTEP/Window Maker property list files. +//! +//! Files are parsed into the WINGs-internal [`PropList`] structure, which does +//! not support GNUstep extensions for special data types (`NSValue`, `NSDate`, +//! etc.) or comments. +//! +//! This is not exactly the same as the original WINGs parser, but it should +//! handle Window Maker configurations just fine. Notable differences include: +//! +//! * A single trailing backslash in quoted strings is not accepted (and a +//! properly escaped backslash must be used). +//! * Strings are UTF-8 (and input must be in UTF-8). Files with strings that +//! contain a zero byte will be rejected because we still use C-style strings +//! internally to support FFI with code that has not yet been ported to Rust. + +use std::{collections::HashMap, ffi::CString}; + +use nom::{ + branch::alt, + character::complete::{char, multispace0, none_of, satisfy}, + combinator::{cut, eof, fail, map, map_res, opt}, + error::context, + multi::{fold_many0, many0, many1, many_m_n, separated_list0}, + sequence::{delimited, preceded, terminated}, + AsChar, Finish, IResult, Input, Parser, +}; +use nom_language::error::{convert_error, VerboseError}; + +use super::{Node, PropList}; + +fn read_dictionary_key>(input: I) -> IResult> { + preceded( + multispace0, + terminated( + alt((read_data, read_string)), + cut(context( + "looking for '=' after dictionary key", + (multispace0, char('='), multispace0), + )), + ), + ) + .parse(input) +} + +fn read_dictionary_value>( + input: I, +) -> IResult> { + terminated( + delimited(multispace0, read_prop_list, multispace0), + cut(context( + "looking for ';' after dictionary value", + (char(';'), multispace0), + )), + ) + .parse(input) +} + +fn read_dictionary>(input: I) -> IResult> { + delimited( + context( + "looking for dictionary to start with '{'", + (multispace0, char('{'), multispace0), + ), + map( + fold_many0( + (read_dictionary_key, read_dictionary_value), + HashMap::new, + |mut items: HashMap, (k, v): (PropList, PropList)| { + items.insert(k, v); + items + }, + ), + |items: HashMap| PropList::new(Node::Dictionary(items)), + ), + cut(context( + "looking for dictionary to end with '}'", + (multispace0, char('}'), multispace0), + )), + ) + .parse(input) +} + +fn read_array_elements>( + input: I, +) -> IResult, VerboseError> { + delimited( + multispace0, + separated_list0( + context("looking for comma between array elements", char(',')), + delimited(multispace0, read_prop_list, multispace0), + ), + multispace0, + ) + .parse(input) +} + +fn read_array>(input: I) -> IResult> { + map( + preceded( + (multispace0, char('(')), + terminated( + read_array_elements, + ( + multispace0, + opt(char(',')), + multispace0, + cut(context("looking for array that ends with ')'", char(')'))), + ), + ), + ), + |items: Vec| PropList::new(Node::Array(items)), + ) + .parse(input) +} + +fn read_data_byte>(input: I) -> IResult> { + map( + delimited( + multispace0, + ( + context( + "looking for first hex value", + satisfy(|c: char| c.is_ascii_hexdigit()), + ), + cut(context( + "looking for second hex value", + satisfy(|c: char| c.is_ascii_hexdigit()), + )), + ), + multispace0, + ), + |(upper, lower)| { + let s = &[upper as u8, lower as u8]; + // Safety: upper and lower are ASCII hexdigits, so they + // should fit into a str without validation and provide + // exactly 8 bits of value. + u8::from_str_radix(unsafe { str::from_utf8_unchecked(s) }, 16).unwrap() + }, + ) + .parse(input) +} + +fn read_data>(input: I) -> IResult> { + map( + delimited( + (multispace0, char('<')), + many0(read_data_byte), + cut(context( + "looking for data to end with '>'", + (multispace0, char('>')), + )), + ), + |bytes: Vec| PropList::new(Node::Data(bytes)), + ) + .parse(input) +} + +fn unescape_character(c: char) -> char { + match c { + '\\' => '\\', + '"' => '"', + 'a' => '\x07', + 'b' => '\x08', + 't' => '\t', + 'n' => '\n', + 'v' => '\x0B', + 'f' => '\x0D', + x @ _ => x, + } +} + +fn unescape_octal(bytes: Vec) -> Result { + let mut result = 0u32; + for b in &bytes { + result <<= 3; + result |= (*b as u32) & 0o7; + } + char::from_u32(result).ok_or_else(|| { + format!( + "unable to convert octal escape sequence '{:?}' to character", + &bytes + ) + }) +} + +fn read_quoted_char>(input: I) -> IResult> { + alt(( + preceded( + char('\\'), + cut(alt(( + map( + context( + "looking for an escaped character", + satisfy(|c: char| !c.is_oct_digit()), + ), + unescape_character, + ), + map_res( + context( + "looking for an octal sequence (0-7, up to 3 times)", + many_m_n(1, 3, satisfy(|c| c.is_oct_digit())), + ), + unescape_octal, + ), + ))), + ), + // Characters that do not need escaping. + none_of(r#""\"#), + )) + .parse(input) +} + +fn read_nullbyte_char>( + input: I, +) -> IResult> { + map_res(read_quoted_char, |c: char| { + let mut bytes = [0u8; 4]; + let s = c.encode_utf8(&mut bytes).as_bytes(); + for b in s { + if *b == 0 { + let len = s.len(); + return Ok((bytes, len)); + } + } + Err(()) + }) + .parse(input) +} + +fn read_char_bytes>( + input: I, +) -> IResult> { + map(read_quoted_char, |c: char| { + let mut bytes = [0u8; 4]; + let s = c.encode_utf8(&mut bytes).as_bytes(); + let len = s.len(); + (bytes, len) + }) + .parse(input) +} + +fn read_quoted_string>(input: I) -> IResult> { + map( + fold_many0( + alt(( + terminated( + // First we try to read a char with a null byte. If we do, + // unconditionally fail. + read_nullbyte_char, + cut(context( + "null bytes are not allowed in strings", + fail::>(), + )), + ), + // Character is valid, so proceed. + read_char_bytes, + )), + Vec::new, + |mut buf: Vec, (bytes, len): ([u8; 4], usize)| { + buf.extend_from_slice(&bytes[0..len]); + buf + }, + ), + |buf: Vec| unsafe { CString::from_vec_unchecked(buf) }, + ) + .parse(input) +} + +pub(crate) fn is_unquoted_char(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '.' || c == '_' || c == '/' || c == '+' +} + +fn read_unquoted_string>(input: I) -> IResult> { + map( + many1(map(satisfy(is_unquoted_char), |c: char| c as u8)), + |bytes: Vec| unsafe { CString::from_vec_unchecked(bytes) }, + ) + .parse(input) +} + +fn read_string>(input: I) -> IResult> { + map( + delimited( + multispace0, + alt(( + delimited( + char('"'), + read_quoted_string, + cut(context("looking for closing \" for string", char('"'))), + ), + read_unquoted_string, + )), + multispace0, + ), + |s: CString| PropList::new(Node::String(s)), + ) + .parse(input) +} + +fn read_prop_list>(input: I) -> IResult> { + delimited( + multispace0, + alt((read_array, read_data, read_dictionary, read_string)), + multispace0, + ) + .parse(input) +} + +fn is_context_error(e: &nom_language::error::VerboseErrorKind) -> bool { + if let nom_language::error::VerboseErrorKind::Context(_) = e { + true + } else { + false + } +} + +/// Reads a `PropList` from `s`. Returns a human-readable error message if +/// there's an error. +pub fn from_str(s: &str) -> Result { + terminated( + read_prop_list, + ( + multispace0, + cut(context( + "looking for complete property list at start of input, with no trailing garbage", + eof, + )), + ), + ) + .parse(s) + .finish() + .map(|(_, plist)| plist) + .map_err(|e| { + if let Some(e) = e.errors.iter().filter(|(_, e)| is_context_error(e)).last() { + // Drop all but the deepest context error from the VerboseError trace. + convert_error( + s, + VerboseError { + errors: vec![e.clone()], + }, + ) + } else { + convert_error(s, e) + } + }) +} + +#[cfg(test)] +mod test { + use std::ffi::CString; + + use crate::prop_list::{parser::from_str, Node, PropList}; + + fn pl_array(xs: Vec) -> PropList { + PropList::new(Node::Array(xs)) + } + + fn pl_dict(kvs: Vec<(PropList, PropList)>) -> PropList { + PropList::new(Node::Dictionary(kvs.into_iter().collect())) + } + + fn pl_string(s: &str) -> PropList { + PropList::new(Node::String(CString::new(s.as_bytes()).unwrap())) + } + + #[test] + fn parse_data() { + let plist = from_str("").unwrap(); + assert_eq!( + plist, + PropList::new(Node::Data(vec![0xde, 0xad, 0xbe, 0xef])) + ); + } + + #[test] + fn error_unclosed_data() { + let e = from_str("': +': +, +^ + +"# + ); + } + + #[test] + fn error_incomplete_data_byte() { + let e = from_str("> { + /// Line-breaking strategy. + pub(crate) inline: Inline, + /// Whether to render `node` as if it is the first item on a new line. + pub(crate) clear_left: bool, + /// Indentation level to render `node` at. + pub(crate) indentation: u32, + /// Amount by which `indentation` should increase when rendering children of + /// `node`. + pub(crate) increment: u32, + /// The `Node` to render. + pub(crate) node: N, +} + +/// Quick and dirty single-branch lookup mapping `b` to a hex character. Only +/// valid for `b` in `[0, 15]`. +fn byte_char(b: u8) -> char { + match b { + 0 => '0', + 1 => '1', + 2 => '2', + 3 => '3', + 4 => '4', + 5 => '5', + 6 => '6', + 7 => '7', + 8 => '8', + 9 => '9', + 10 => 'a', + 11 => 'b', + 12 => 'c', + 13 => 'd', + 14 => 'e', + 15 => 'f', + _ => unreachable!(), + } +} + +impl> Display { + /// Writes whitespace to `f` for the current level of indentation. + fn write_indent(&self, f: &mut fmt::Formatter) -> fmt::Result { + for _ in 0..self.indentation { + f.write_char(' ')?; + } + Ok(()) + } +} + +/// Writes a hexadecimal representation of `bytes` to `f`, splitting `bytes` up +/// into space-delimited 32-bit quartets for readability. +fn write_data_bytes(bytes: &[u8], f: &mut fmt::Formatter) -> fmt::Result { + fn write(b: u8, f: &mut fmt::Formatter) -> fmt::Result { + let upper = (b >> 3) as u8; + let lower = (b & 0x0F) as u8; + write!(f, "{}", byte_char(upper))?; + write!(f, "{}", byte_char(lower)) + } + + let mut chunks = bytes.chunks(4); + if let Some(first) = chunks.next() { + for b in first { + write(*b, f)?; + } + } + for seg in chunks { + f.write_char(' ')?; + for b in seg { + write(*b, f)?; + } + } + + Ok(()) +} + +/// Writes `s` to `f`, backslash-escaping special characters as appropriate for +/// a quoted property list string. +fn write_escaped_string(s: &str, f: &mut fmt::Formatter) -> fmt::Result { + for c in s.chars() { + match c { + '\x07' => f.write_str(r#"\a"#)?, + '\x08' => f.write_str(r#"\b"#)?, + '\t' => f.write_str(r#"\t"#)?, + '\n' => f.write_str(r#"\n"#)?, + '\x0B' => f.write_str(r#"\v"#)?, + '\x0D' => f.write_str(r#"\f"#)?, + '\\' => f.write_str(r#"\\"#)?, + '"' => f.write_str(r#"\\""#)?, + c => f.write_char(c)?, + } + } + Ok(()) +} + +impl> fmt::Display for Display { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.clear_left { + writeln!(f, "")?; + self.write_indent(f)?; + } + match &*self.node { + Node::Array(items) if self.inline != Inline::No => { + // Try to fit everything in one line. + let mut buf = String::new(); + buf.push('('); + let mut items = items.iter(); + if let Some(first) = items.next() { + write!( + &mut buf, + "{}", + Display { + inline: self.inline, + clear_left: false, + indentation: self.indentation, + increment: self.increment, + node: first.0.borrow(), + } + )?; + } + for next in items { + write!( + &mut buf, + ", {}", + Display { + inline: self.inline, + clear_left: false, + indentation: self.indentation, + increment: self.increment, + node: next.0.borrow(), + } + )?; + } + buf.push(')'); + if self.inline == Inline::Soft && buf.chars().count() > SOFT_LINEBREAK_WIDTH { + write!( + f, + "{}", + Display { + inline: Inline::No, + clear_left: false, + indentation: self.indentation, + increment: self.increment, + node: &*self.node, + } + )?; + } else { + f.write_str(&buf)?; + } + } + Node::Array(items) if items.is_empty() => f.write_str("()")?, + Node::Array(items) => { + f.write_char('(')?; + let mut items = items.iter(); + if let Some(first) = items.next() { + write!( + f, + "{}", + Display { + inline: Inline::Soft, + clear_left: true, + indentation: self.indentation + self.increment, + increment: self.increment, + node: first.0.borrow(), + } + )?; + } + for next in items { + f.write_char(',')?; + write!( + f, + "{}", + Display { + inline: Inline::Soft, + clear_left: true, + indentation: self.indentation + self.increment, + increment: self.increment, + node: next.0.borrow(), + } + )?; + } + writeln!(f, "")?; + self.write_indent(f)?; + f.write_char(')')?; + } + Node::Data(bytes) => { + write!(f, "<")?; + write_data_bytes(bytes, f)?; + write!(f, ">")?; + } + Node::Dictionary(items) if items.is_empty() => write!(f, "{{}}")?, + Node::Dictionary(items) if self.inline != Inline::No => { + // Try to fit everything in one line. + let mut buf = String::new(); + buf.push('{'); + for (k, v) in items { + write!( + &mut buf, + " {} = {};", + Display { + inline: self.inline, + clear_left: false, + indentation: self.indentation, + increment: self.increment, + node: k.0.borrow(), + }, + Display { + inline: self.inline, + clear_left: false, + indentation: self.indentation, + increment: self.increment, + node: v.0.borrow(), + } + )?; + } + write!(&mut buf, " }}")?; + if self.inline == Inline::Soft && buf.chars().count() > SOFT_LINEBREAK_WIDTH { + write!( + f, + "{}", + Display { + inline: Inline::No, + clear_left: false, + indentation: self.indentation, + increment: self.increment, + node: &*self.node, + } + )?; + } else { + f.write_str(&buf)?; + } + } + Node::Dictionary(items) => { + write!(f, "{{")?; + for (k, v) in items { + write!( + f, + "{} = {};", + Display { + inline: Inline::Soft, + clear_left: true, + indentation: self.indentation + self.increment, + increment: self.increment, + node: k.0.borrow(), + }, + Display { + inline: Inline::Soft, + clear_left: false, + indentation: self.indentation + self.increment, + increment: self.increment, + node: v.0.borrow(), + } + )?; + } + writeln!(f, "")?; + self.write_indent(f)?; + f.write_char('}')?; + } + Node::String(s) => { + let mut unquoted = !s.is_empty(); + for c in s.as_bytes() { + if !parser::is_unquoted_char(*c as char) { + unquoted = false; + break; + } + } + if unquoted { + write!(f, "{}", s.to_string_lossy())?; + } else { + f.write_char('"')?; + write_escaped_string(&s.to_string_lossy(), f)?; + f.write_char('"')?; + } + } + } + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::prop_list::{Node, PropList}; + + use std::ffi::CString; + + fn pl_array(xs: Vec) -> PropList { + PropList::new(Node::Array(xs)) + } + + fn pl_string(s: &str) -> PropList { + PropList::new(Node::String(CString::new(s.as_bytes()).unwrap())) + } + + #[test] + fn write_empty_string() { + let serialized = format!("{:?}", pl_string("")); + assert_eq!(serialized, r#""""#); + } + + #[test] + fn write_unquoted_string() { + let serialized = format!("{:?}", pl_string("hello")); + assert_eq!(serialized, r#"hello"#); + } + + #[test] + fn write_quoted_string() { + let serialized = format!("{:?}", pl_string("hello, world!")); + assert_eq!(serialized, r#""hello, world!""#); + } + + #[test] + fn write_flat_array() { + let serialized = format!( + "{:?}", + pl_array(vec![pl_string("hello"), pl_string("world")]) + ); + assert_eq!(serialized, r#"(hello, world)"#); + } + + #[test] + fn write_empty_array() { + let serialized = format!("{:?}", pl_array(vec![])); + assert_eq!(serialized, r#"()"#); + } + + #[test] + fn write_nested_array() { + let serialized = format!( + "{:?}", + pl_array(vec![ + pl_array(vec![pl_string("hello"), pl_string("world")]), + pl_string("baz"), + pl_string("quux plugh") + ]) + ); + assert_eq!(serialized, r#"((hello, world), baz, "quux plugh")"#); + } + + #[test] + fn write_long_nested_array() { + let serialized = format!( + "{:?}", + pl_array(vec![ + pl_array(vec![pl_string("hello"), pl_string("world")]), + pl_string("baz"), + pl_string("quux plugh"), + pl_string("etc"), + pl_array(vec![ + pl_string("lots"), + pl_string("of"), + pl_string("words"), + pl_string("go"), + pl_array(vec![ + pl_string("here"), + pl_string("and"), + pl_string("stuff"), + pl_string("blah"), + pl_string("blah"), + pl_string("blah"), + ]) + ]) + ]) + ); + assert_eq!( + serialized, + r#"( + (hello, world), + baz, + "quux plugh", + etc, + (lots, of, words, go, (here, and, stuff, blah, blah, blah)) +)"# + ); + } + + #[test] + fn roundtrip_wm_state() { + let original = r#"{ + Dock = { + AutoRaiseLower = No; + Applications = ( + { + Forced = No; + Name = Logo.WMDock; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,0"; + Lock = Yes; + Command = "/usr/bin/WPrefs"; + }, + { + Forced = No; + Name = wmweather.wmweather; + DropCommand = "wmweather -s KBOS %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,4"; + Lock = Yes; + PasteCommand = "wmweather -s KBOS %s"; + Command = "wmweather -s KBOS"; + }, + { + Forced = No; + Name = wmclock.WMClock; + DropCommand = "wmclock %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,3"; + Lock = Yes; + PasteCommand = "wmclock %s"; + Command = wmclock; + }, + { + Forced = No; + Name = emacs.Emacs; + DropCommand = "emacsclient -c %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,2"; + Lock = Yes; + PasteCommand = "emacsclient -c %s"; + Command = "emacsclient -"; + }, + { + Forced = No; + Name = wmfire.wmfire; + DropCommand = "wmfire -m %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,5"; + Lock = Yes; + PasteCommand = "wmfire -m %s"; + Command = "wmfire -m"; + }, + { + Forced = No; + Name = wmnet.WMNET; + DropCommand = "wmnet -W enp0s31f6 %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,7"; + Lock = Yes; + PasteCommand = "wmnet -W enp0s31f6 %s"; + Command = "wmnet -W enp0s31f6"; + }, + { + Forced = No; + Name = wmtemp.DockApp; + DropCommand = "wmtemp -f %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,6"; + Lock = Yes; + PasteCommand = "wmtemp -f %s"; + Command = "wmtemp -f"; + }, + { + Forced = No; + Name = firefox.Firefox; + DropCommand = "firefox %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,1"; + Lock = Yes; + PasteCommand = "firefox %s"; + Command = firefox; + }, + { + Forced = No; + Name = "org\\.gnome\\.Weather.org\\.gnome\\.Weather"; + DropCommand = "org.gnome.Weather %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,8"; + Lock = Yes; + PasteCommand = "org.gnome.Weather %s"; + Command = "/usr/bin/gnome-weather"; + }, + { + Forced = No; + Name = Zotero.Zotero; + DropCommand = "/home/stu/bin/zotero %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,9"; + Lock = Yes; + PasteCommand = "/home/stu/bin/zotero %s"; + Command = "/home/stu/bin/zotero"; + } + ); + Lowered = Yes; + Position = "2496,0"; + Applications1440 = ( + { + Forced = No; + Name = Logo.WMDock; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,0"; + Lock = Yes; + Command = "/usr/bin/WPrefs"; + }, + { + Forced = No; + Name = wmweather.wmweather; + DropCommand = "wmweather -s KBOS %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,4"; + Lock = Yes; + PasteCommand = "wmweather -s KBOS %s"; + Command = "wmweather -s KBOS"; + }, + { + Forced = No; + Name = wmclock.WMClock; + DropCommand = "wmclock %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,3"; + Lock = Yes; + PasteCommand = "wmclock %s"; + Command = wmclock; + }, + { + Forced = No; + Name = emacs.Emacs; + DropCommand = "emacsclient -c %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,2"; + Lock = Yes; + PasteCommand = "emacsclient -c %s"; + Command = "emacsclient -"; + }, + { + Forced = No; + Name = wmfire.wmfire; + DropCommand = "wmfire -m %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,5"; + Lock = Yes; + PasteCommand = "wmfire -m %s"; + Command = "wmfire -m"; + }, + { + Forced = No; + Name = wmnet.WMNET; + DropCommand = "wmnet -W enp0s31f6 %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,7"; + Lock = Yes; + PasteCommand = "wmnet -W enp0s31f6 %s"; + Command = "wmnet -W enp0s31f6"; + }, + { + Forced = No; + Name = wmtemp.DockApp; + DropCommand = "wmtemp -f %d"; + BuggyApplication = No; + AutoLaunch = Yes; + Position = "0,6"; + Lock = Yes; + PasteCommand = "wmtemp -f %s"; + Command = "wmtemp -f"; + }, + { + Forced = No; + Name = firefox.Firefox; + DropCommand = "firefox %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,1"; + Lock = Yes; + PasteCommand = "firefox %s"; + Command = firefox; + }, + { + Forced = No; + Name = "org\\.gnome\\.Weather.org\\.gnome\\.Weather"; + DropCommand = "org.gnome.Weather %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,8"; + Lock = Yes; + PasteCommand = "org.gnome.Weather %s"; + Command = "/usr/bin/gnome-weather"; + }, + { + Forced = No; + Name = Zotero.Zotero; + DropCommand = "/home/stu/bin/zotero %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "0,9"; + Lock = Yes; + PasteCommand = "/home/stu/bin/zotero %s"; + Command = "/home/stu/bin/zotero"; + } + ); + }; + Clip = { + Forced = No; + Name = Logo.WMClip; + DropCommand = "wmsetbg -u -t %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "2496,1376"; + Lock = No; + Command = "-"; + }; + Drawers = (); + Workspaces = ( + { + Clip = { + AutoRaiseLower = No; + AutoCollapse = No; + Applications = ( + { + Forced = No; + Name = xsnow.Xsnow; + DropCommand = "xsnow %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "-2,0"; + Lock = No; + PasteCommand = "xsnow %s"; + Command = xsnow; + Omnipresent = No; + }, + { + Forced = No; + Name = "thunderbird.Thunderbird-default"; + DropCommand = "thunderbird %d"; + BuggyApplication = No; + AutoLaunch = No; + Position = "-1,0"; + Lock = No; + PasteCommand = "thunderbird %s"; + Command = thunderbird; + Omnipresent = No; + } + ); + Collapsed = No; + AutoAttractIcons = No; + Lowered = Yes; + }; + Name = Mail; + }, + { + Clip = { + AutoRaiseLower = No; + AutoCollapse = No; + Applications = (); + Collapsed = No; + AutoAttractIcons = No; + Lowered = Yes; + }; + Name = Plans; + }, + { + Clip = { + AutoRaiseLower = No; + AutoCollapse = No; + Applications = (); + Collapsed = No; + AutoAttractIcons = No; + Lowered = Yes; + }; + Name = Misc; + }, + ); +}"#; + + // We do (original serialized) -> plist -> (serialized) -> plist and + // compare the two plists because Rust's hashtable ordering (which is + // reflected in the serialized output) does not match the original + // WUtils hashtable ordering. + let original_plist = super::parser::from_str(original).unwrap(); + let serialized = format!("{}", original_plist.display_indented()); + let deserialized = super::parser::from_str(&serialized).unwrap(); + assert_eq!(original_plist, deserialized); + } +} -- 2.39.5 From cf588d6e278a213732b0edb5c8f19415eca784cf Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 17 Oct 2025 11:00:48 -0400 Subject: [PATCH 18/39] Simplify `path_from_cstr` substantially (h/t cross). --- wutil-rs/src/find_file.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wutil-rs/src/find_file.rs b/wutil-rs/src/find_file.rs index bcc4447d..9e86d0f7 100644 --- a/wutil-rs/src/find_file.rs +++ b/wutil-rs/src/find_file.rs @@ -35,9 +35,7 @@ use std::{ /// Tries to interpret `s` as a UTF-8 path. Returns `None` if decoding `s` /// fails. pub fn path_from_cstr(s: &CStr) -> Option { - String::from_utf8(s.to_bytes().iter().copied().collect()) - .ok() - .map(|p| PathBuf::from(p)) + s.to_str().ok().map(PathBuf::from) } /// If `file` is an absolute path can be opened, returns that path. Paths -- 2.39.5 From 12930739ec993709e5e06c1ecc236e44e6c0f0a4 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 17 Oct 2025 11:47:22 -0400 Subject: [PATCH 19/39] Allocate PropList description with `memory::alloc_bytes`. All memory allocations passed back from FFI functions should be allocated with `memory::alloc_bytes`, so that C code can call `memory::free_bytes` when it's done with them. --- wutil-rs/src/memory.rs | 22 +++++++++++++++++++--- wutil-rs/src/prop_list.rs | 23 +++++++++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs index 7ced619b..8f06450a 100644 --- a/wutil-rs/src/memory.rs +++ b/wutil-rs/src/memory.rs @@ -18,7 +18,7 @@ //! memory than the baseline Window Maker code, it isn't really necessary in //! this day and age. -use std::{alloc, mem, ptr::{self, NonNull}}; +use std::{alloc, ffi::{c_char, CStr}, mem, ptr::{self, NonNull}}; /// Tracks the layout and reference count of an allocated chunk of memory. #[derive(Clone, Copy)] @@ -84,6 +84,14 @@ pub fn alloc_bytes(size: usize) -> *mut u8 { result } +/// Allocates a segment with [`alloc_bytes`] and fills it with the contents of +/// `s`. The resulting string should be free'd by passing it to [`free_bytes`]. +pub fn alloc_string(s: &CStr) -> *mut c_char { + let result = alloc_bytes(s.count_bytes() + 1).cast::(); + unsafe { ptr::copy(s.as_ptr().cast::(), result, s.count_bytes() + 1); } + result.cast::() +} + /// Frees the bytes pointed to by `b`. /// /// ## Safety @@ -170,9 +178,9 @@ pub mod ffi { #[cfg(test)] mod test { - use super::{alloc_bytes, free_bytes, ffi::wrealloc, Header}; + use super::{alloc_bytes, alloc_string, ffi::wrealloc, free_bytes, Header}; - use std::{mem, os::raw::c_void, ptr}; + use std::{ffi::CStr, mem, os::raw::c_void, ptr}; #[test] fn recover_header() { @@ -219,4 +227,12 @@ mod test { assert_eq!(unsafe { *y }, 17); unsafe { free_bytes(y.cast::()); } } + + #[test] + fn alloc_free_string() { + let s = alloc_string(c"hello"); + assert!(!s.is_null()); + assert_eq!(unsafe { CStr::from_ptr(s) }, c"hello"); + unsafe { free_bytes(s.cast::()); } + } } diff --git a/wutil-rs/src/prop_list.rs b/wutil-rs/src/prop_list.rs index 4a2376f0..3b98f022 100644 --- a/wutil-rs/src/prop_list.rs +++ b/wutil-rs/src/prop_list.rs @@ -357,7 +357,7 @@ impl PropList { } pub mod ffi { - use crate::{data::Data, find_file::path_from_cstr}; + use crate::{data::Data, find_file::path_from_cstr, memory}; use super::{ merge_deep, merge_shallow, parser, subtract_deep, subtract_shallow, Node, PropList, @@ -784,7 +784,7 @@ pub mod ffi { } } match CString::new(buf) { - Ok(s) => s.into_raw(), + Ok(s) => memory::alloc_string(s.as_c_str()), Err(_) => ptr::null_mut(), } } @@ -869,3 +869,22 @@ pub mod ffi { } } } + +#[cfg(test)] +mod test { + use std::ffi::CString; + + use crate::memory; + + use super::{Node, PropList, ffi}; + + #[test] + fn free_proplist_description() { + let mut plist = PropList::new(Node::Array(vec![PropList::new(Node::String(CString::from(c"hello"))), + PropList::new(Node::String(CString::from(c"world!")))])); + let desc = unsafe { ffi::WMGetPropListDescription(&mut plist, 1) }; + assert!(!desc.is_null()); + + unsafe { memory::ffi::wfree(desc.cast()); } + } +} -- 2.39.5 From e6fd7e49f8e4486f5d1eeac7b989d14a47e7ac35 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 12:12:27 -0400 Subject: [PATCH 20/39] Clean things up a little bit with refutable let. --- wutil-rs/src/memory.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs index 8f06450a..b1def97b 100644 --- a/wutil-rs/src/memory.rs +++ b/wutil-rs/src/memory.rs @@ -49,18 +49,15 @@ pub fn alloc_bytes(size: usize) -> *mut u8 { if size == 0 { return ptr::null_mut(); } - let header_layout = match alloc::Layout::from_size_align(mem::size_of::
(), 8) { - Ok(x) => x, - Err(_) => return ptr::null_mut(), + let Ok(header_layout) = alloc::Layout::from_size_align(mem::size_of::
(), 8) else { + return ptr::null_mut(); }; - let layout = match alloc::Layout::from_size_align(size, 8) { - Ok(x) => x, - Err(_) => return ptr::null_mut(), + let Ok(layout) = alloc::Layout::from_size_align(size, 8) else { + return ptr::null_mut(); }; - let (layout, result_offset) = match header_layout.extend(layout) { - Ok(x) => x, - Err(_) => return ptr::null_mut(), + let Ok((full_layout, result_offset)) = header_layout.extend(layout) else { + return ptr::null_mut(); }; let full_segment = unsafe { alloc::alloc_zeroed(layout) }; -- 2.39.5 From dfdaa67b4d7cfe41006ddd4e0d975b5f274f91d3 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 12:14:18 -0400 Subject: [PATCH 21/39] Chasing memory bugs in memory.rs: allocate the right layout. --- wutil-rs/src/memory.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs index b1def97b..2804a228 100644 --- a/wutil-rs/src/memory.rs +++ b/wutil-rs/src/memory.rs @@ -60,7 +60,7 @@ pub fn alloc_bytes(size: usize) -> *mut u8 { return ptr::null_mut(); }; - let full_segment = unsafe { alloc::alloc_zeroed(layout) }; + let full_segment = unsafe { alloc::alloc_zeroed(full_layout) }; if full_segment.is_null() { return ptr::null_mut(); } @@ -73,7 +73,7 @@ pub fn alloc_bytes(size: usize) -> *mut u8 { let header = result.sub(mem::size_of::
()).cast::
(); header.write_unaligned(Header { ptr: NonNull::new_unchecked(full_segment), - layout: header_layout, + layout: full_layout, refcount: 0, }); } -- 2.39.5 From 6a98614d137a90654e1a82e9f837cecb864f2fbe Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 12:21:29 -0400 Subject: [PATCH 22/39] Clarify wmalloc contract - it's safe to read before writing. --- wutil-rs/src/memory.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs index 2804a228..37df9644 100644 --- a/wutil-rs/src/memory.rs +++ b/wutil-rs/src/memory.rs @@ -110,7 +110,8 @@ pub mod ffi { use std::{ffi::c_void, ptr}; - /// Allocates `size` bytes. Returns null if `sizes is 0. + /// Allocates `size` bytes. Returns null if `size` is 0. Data will be + /// initialized but have an arbitrary value. #[unsafe(no_mangle)] pub unsafe extern "C" fn wmalloc(size: usize) -> *mut c_void { alloc_bytes(size).cast::() -- 2.39.5 From 14c316615e532beb9ef9b7ef59530584a14e949d Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 12:23:19 -0400 Subject: [PATCH 23/39] Chasing memory bugs in memory.rs: copy only the payload length in wrealloc. --- wutil-rs/src/memory.rs | 56 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs index 37df9644..54128304 100644 --- a/wutil-rs/src/memory.rs +++ b/wutil-rs/src/memory.rs @@ -24,6 +24,7 @@ use std::{alloc, ffi::{c_char, CStr}, mem, ptr::{self, NonNull}}; #[derive(Clone, Copy)] struct Header { ptr: NonNull, + payload_size: usize, layout: alloc::Layout, refcount: u16, } @@ -73,6 +74,7 @@ pub fn alloc_bytes(size: usize) -> *mut u8 { let header = result.sub(mem::size_of::
()).cast::
(); header.write_unaligned(Header { ptr: NonNull::new_unchecked(full_segment), + payload_size: size, layout: full_layout, refcount: 0, }); @@ -124,16 +126,31 @@ pub mod ffi { } /// Resizes `ptr` to be at least `newsize` bytes in size, returning the - /// start of the new segment. + /// start of the new segment. If `newsize` is larger than `ptr`'s segment, + /// data in the new space will be initialized but have an arbitrary value. /// /// ## Safety /// - /// Callers must ensure that `ptr` is a live allocation from [`wmalloc`] or [`wrealloc`]. + /// If `ptr` is non-null, callers must ensure that it came from from + /// [`wmalloc`] or [`wrealloc`]. #[unsafe(no_mangle)] pub unsafe extern "C" fn wrealloc(ptr: *mut c_void, newsize: usize) -> *mut c_void { + if ptr.is_null() { + unsafe { + return wmalloc(newsize); + } + } unsafe { + let result = wmalloc(newsize); + let result_header = ptr::read_unaligned(Header::for_alloc_bytes(result.cast())); + let ptr_header = ptr::read_unaligned(Header::for_alloc_bytes(ptr.cast())); + let copy_size = usize::min( + ptr_header.payload_size, + result_header.payload_size, + ); + ptr::copy_nonoverlapping(ptr, result, copy_size); wfree(ptr); - wmalloc(newsize).cast::() + result } } @@ -176,9 +193,9 @@ pub mod ffi { #[cfg(test)] mod test { - use super::{alloc_bytes, alloc_string, ffi::wrealloc, free_bytes, Header}; + use super::{alloc_bytes, alloc_string, ffi::{wfree, wmalloc, wrealloc}, free_bytes, Header}; - use std::{ffi::CStr, mem, os::raw::c_void, ptr}; + use std::{ffi::CStr, mem, os::raw::c_void, ptr, slice}; #[test] fn recover_header() { @@ -215,6 +232,21 @@ mod test { unsafe { free_bytes(x.cast::()); } } + #[test] + fn multiple_allocs() { + unsafe { + let x = wmalloc(mem::size_of::()); + *x.cast::() = 30; + let y = wmalloc(mem::size_of::()); + *y.cast::() = 5; + let z = wmalloc(48); + *z.cast::() = 1.0; + wfree(x); + wfree(y); + wfree(z); + } + } + #[test] fn realloc_nonzero() { let x = alloc_bytes(mem::size_of::()).cast::(); @@ -226,6 +258,20 @@ mod test { unsafe { free_bytes(y.cast::()); } } + #[test] + fn realloc_retains_data() { + let x: *mut u8 = unsafe { wmalloc(10).cast() }; + unsafe { + let xs = slice::from_raw_parts_mut(&mut *x, 10); + // We know that xs should be zeroed. + assert_eq!(xs, &[0u8; 10]); + for i in 0u8..10 { + xs[i as usize] = i; + } + assert_eq!(xs, (0..10).collect::>()); + } + } + #[test] fn alloc_free_string() { let s = alloc_string(c"hello"); -- 2.39.5 From 2d2dd9febe1612e5be05dc89dc41271fdba76214 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 12:24:24 -0400 Subject: [PATCH 24/39] Chasing memory bugs: be consistent about wmalloc/wfree. --- src/appmenu.c | 4 ++-- src/misc.c | 12 ++++++------ src/properties.c | 4 ++-- wrlib/tests/testgrad.c | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/appmenu.c b/src/appmenu.c index bc708964..11191641 100644 --- a/src/appmenu.c +++ b/src/appmenu.c @@ -139,7 +139,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun } wstrlcpy(title, &slist[*index][pos], sizeof(title)); } - data = malloc(sizeof(WAppMenuData)); + data = wmalloc(sizeof(WAppMenuData)); if (data == NULL) { wwarning(_("appmenu: out of memory creating menu for window %lx"), win); wMenuDestroy(menu, True); @@ -152,7 +152,7 @@ static WMenu *parseMenuCommand(WScreen * scr, Window win, char **slist, int coun if (!entry) { wMenuDestroy(menu, True); wwarning(_("appmenu: out of memory creating menu for window %lx"), win); - free(data); + wfree(data); return NULL; } if (rtext[0] != 0) diff --git a/src/misc.c b/src/misc.c index 4cab3584..123def2b 100644 --- a/src/misc.c +++ b/src/misc.c @@ -520,7 +520,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) len = strlen(cmdline); olen = len + 1; - out = malloc(olen); + out = wmalloc(olen); if (!out) { wwarning(_("out of memory during expansion of \"%s\""), cmdline); return NULL; @@ -573,7 +573,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) (unsigned int)scr->focused_window->client_win); slen = strlen(tmpbuf); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%w", cmdline); goto error; @@ -590,7 +590,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) snprintf(tmpbuf, sizeof(tmpbuf), "0x%x", (unsigned int)scr->current_workspace + 1); slen = strlen(tmpbuf); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%W", cmdline); goto error; @@ -607,7 +607,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) if (user_input) { slen = strlen(user_input); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%a", cmdline); goto error; @@ -630,7 +630,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) } slen = strlen(scr->xdestring); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%d", cmdline); goto error; @@ -651,7 +651,7 @@ char *ExpandOptions(WScreen *scr, const char *cmdline) } slen = strlen(selection); olen += slen; - nout = realloc(out, olen); + nout = wrealloc(out, olen); if (!nout) { wwarning(_("out of memory during expansion of '%s' for command \"%s\""), "%s", cmdline); goto error; diff --git a/src/properties.c b/src/properties.c index 6751306c..d9ce4249 100644 --- a/src/properties.c +++ b/src/properties.c @@ -133,7 +133,7 @@ int PropGetGNUstepWMAttr(Window window, GNUstepWMAttributes ** attr) if (!data) return False; - *attr = malloc(sizeof(GNUstepWMAttributes)); + *attr = wmalloc(sizeof(GNUstepWMAttributes)); if (!*attr) { XFree(data); return False; @@ -183,7 +183,7 @@ void PropSetIconTileHint(WScreen * scr, RImage * image) imageAtom = XInternAtom(dpy, "_RGBA_IMAGE", False); } - tmp = malloc(image->width * image->height * 4 + 4); + tmp = wmalloc(image->width * image->height * 4 + 4); if (!tmp) { wwarning("could not allocate memory to set _WINDOWMAKER_ICON_TILE hint"); return; diff --git a/wrlib/tests/testgrad.c b/wrlib/tests/testgrad.c index 0b5a7994..4b555aa4 100644 --- a/wrlib/tests/testgrad.c +++ b/wrlib/tests/testgrad.c @@ -38,7 +38,7 @@ int main(int argc, char **argv) else ProgName++; - color_name = (char **)malloc(sizeof(char *) * argc); + color_name = (char **)wmalloc(sizeof(char *) * argc); if (color_name == NULL) { fprintf(stderr, "Cannot allocate memory!\n"); exit(1); @@ -106,13 +106,13 @@ int main(int argc, char **argv) exit(1); } - colors = malloc(sizeof(RColor *) * (ncolors + 1)); + colors = wmalloc(sizeof(RColor *) * (ncolors + 1)); for (i = 0; i < ncolors; i++) { if (!XParseColor(dpy, ctx->cmap, color_name[i], &color)) { printf("could not parse color \"%s\"\n", color_name[i]); exit(1); } else { - colors[i] = malloc(sizeof(RColor)); + colors[i] = wmalloc(sizeof(RColor)); colors[i]->red = color.red >> 8; colors[i]->green = color.green >> 8; colors[i]->blue = color.blue >> 8; -- 2.39.5 From bc163d13f69ed9c6d55cfe00f75d229e799e3686 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 12:24:58 -0400 Subject: [PATCH 25/39] Provide alloc_string impl. --- wutil-rs/src/memory.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wutil-rs/src/memory.rs b/wutil-rs/src/memory.rs index 54128304..753f0246 100644 --- a/wutil-rs/src/memory.rs +++ b/wutil-rs/src/memory.rs @@ -86,9 +86,10 @@ pub fn alloc_bytes(size: usize) -> *mut u8 { /// Allocates a segment with [`alloc_bytes`] and fills it with the contents of /// `s`. The resulting string should be free'd by passing it to [`free_bytes`]. pub fn alloc_string(s: &CStr) -> *mut c_char { - let result = alloc_bytes(s.count_bytes() + 1).cast::(); - unsafe { ptr::copy(s.as_ptr().cast::(), result, s.count_bytes() + 1); } - result.cast::() + let len = s.count_bytes() + 1; + let result = alloc_bytes(len).cast::(); + unsafe { ptr::copy_nonoverlapping(s.as_ptr().cast::(), result, len); } + result } /// Frees the bytes pointed to by `b`. -- 2.39.5 From f69227ce19baeae227dc32464d600eec63c2f358 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 16 Oct 2025 22:00:18 -0400 Subject: [PATCH 26/39] Remove support for PropList data nodes. This type of PropList entry appears to be completely unused, so let's be rid of it. This can be reversed in the future if we do want more complete support for property lists, but for now it's code that we don't need. --- WINGs/WINGs/WUtil.h | 6 -- wutil-rs/src/prop_list.rs | 41 +------------ wutil-rs/src/prop_list/parser.rs | 102 +------------------------------ wutil-rs/src/prop_list/writer.rs | 55 ----------------- 4 files changed, 5 insertions(+), 199 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 9475e2b4..070d5c4a 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -732,10 +732,6 @@ WMPropList* WMCreatePLArray(WMPropList *elem, ...); WMPropList* WMCreatePLString(const char *str); -WMPropList* WMCreatePLData(WMData *data); - -WMPropList* WMCreatePLDataWithBytes(const unsigned char *bytes, unsigned int length); - WMPropList* WMCreatePLArrayFromSlice(WMPropList *elems, unsigned int length); WMPropList* WMCreateEmptyPLArray(); @@ -780,8 +776,6 @@ int WMGetPropListItemCount(WMPropList *plist); Bool WMIsPLString(WMPropList *plist); -Bool WMIsPLData(WMPropList *plist); - Bool WMIsPLArray(WMPropList *plist); Bool WMIsPLDictionary(WMPropList *plist); diff --git a/wutil-rs/src/prop_list.rs b/wutil-rs/src/prop_list.rs index 3b98f022..0c303cca 100644 --- a/wutil-rs/src/prop_list.rs +++ b/wutil-rs/src/prop_list.rs @@ -49,8 +49,6 @@ pub enum Node { /// It would be better for this to be a `String`, but the C interface /// requires borrows of C-style strings. String(CString), - /// Binary data. - Data(Vec), /// Array of child `PropList`s. Array(Vec), /// `PropList`-keyed table of child `PropList`s. Keys should only have @@ -63,7 +61,6 @@ impl hash::Hash for Node { fn hash(&self, h: &mut H) { match self { Node::String(s) => s.hash(h), - Node::Data(d) => d.hash(h), Node::Array(a) => { for p in a { p.hash(h); @@ -313,7 +310,6 @@ impl PropList { pub fn deep_clone(&self) -> Self { match &*self.0.borrow() { Node::String(s) => PropList::new(Node::String(s.clone())), - Node::Data(d) => PropList::new(Node::Data(d.clone())), Node::Array(items) => { PropList::new(Node::Array(items.iter().map(|x| x.deep_clone()).collect())) } @@ -357,14 +353,14 @@ impl PropList { } pub mod ffi { - use crate::{data::Data, find_file::path_from_cstr, memory}; + use crate::{find_file::path_from_cstr, memory}; use super::{ merge_deep, merge_shallow, parser, subtract_deep, subtract_shallow, Node, PropList, }; use std::{ - collections::HashMap, ffi::{c_char, c_int, c_uchar, c_uint, CStr, CString, OsString}, ptr, str::FromStr + collections::HashMap, ffi::{c_char, c_int, c_uint, CStr, CString, OsString}, ptr, str::FromStr }; #[unsafe(no_mangle)] @@ -376,27 +372,6 @@ pub mod ffi { Box::leak(Box::new(PropList::new(Node::String(s.into())))) } - #[unsafe(no_mangle)] - pub unsafe extern "C" fn WMCreatePLData(data: *mut Data) -> *mut PropList { - if data.is_null() { - return ptr::null_mut(); - } - let data = unsafe { &*data }; - data.with_bytes(|b| Box::leak(Box::new(PropList::new(Node::Data(Vec::from(b)))))) - } - - #[unsafe(no_mangle)] - pub unsafe extern "C" fn WMCreatePLDataWithBytes( - bytes: *const c_uchar, - length: c_uint, - ) -> *mut PropList { - if bytes.is_null() { - return ptr::null_mut(); - } - let bytes = unsafe { &*ptr::slice_from_raw_parts(bytes.cast::(), length as usize) }; - Box::leak(Box::new(PropList::new(Node::Data(Vec::from(bytes))))) - } - #[unsafe(no_mangle)] pub unsafe extern "C" fn WMCreatePLArrayFromSlice( elems: *mut PropList, @@ -630,18 +605,6 @@ pub mod ffi { } } - #[unsafe(no_mangle)] - pub unsafe extern "C" fn WMIsPLData(plist: *mut PropList) -> c_int { - if plist.is_null() { - return 0; - } - let plist = unsafe { &*plist }; - match &*plist.0.borrow() { - Node::Data(_) => 1, - _ => 0, - } - } - #[unsafe(no_mangle)] pub unsafe extern "C" fn WMIsPLArray(plist: *mut PropList) -> c_int { if plist.is_null() { diff --git a/wutil-rs/src/prop_list/parser.rs b/wutil-rs/src/prop_list/parser.rs index 92d37998..de542047 100644 --- a/wutil-rs/src/prop_list/parser.rs +++ b/wutil-rs/src/prop_list/parser.rs @@ -20,7 +20,7 @@ use nom::{ character::complete::{char, multispace0, none_of, satisfy}, combinator::{cut, eof, fail, map, map_res, opt}, error::context, - multi::{fold_many0, many0, many1, many_m_n, separated_list0}, + multi::{fold_many0, many1, many_m_n, separated_list0}, sequence::{delimited, preceded, terminated}, AsChar, Finish, IResult, Input, Parser, }; @@ -32,7 +32,7 @@ fn read_dictionary_key>(input: I) -> IResult>(input: I) -> IResult>(input: I) -> IResult> { - map( - delimited( - multispace0, - ( - context( - "looking for first hex value", - satisfy(|c: char| c.is_ascii_hexdigit()), - ), - cut(context( - "looking for second hex value", - satisfy(|c: char| c.is_ascii_hexdigit()), - )), - ), - multispace0, - ), - |(upper, lower)| { - let s = &[upper as u8, lower as u8]; - // Safety: upper and lower are ASCII hexdigits, so they - // should fit into a str without validation and provide - // exactly 8 bits of value. - u8::from_str_radix(unsafe { str::from_utf8_unchecked(s) }, 16).unwrap() - }, - ) - .parse(input) -} - -fn read_data>(input: I) -> IResult> { - map( - delimited( - (multispace0, char('<')), - many0(read_data_byte), - cut(context( - "looking for data to end with '>'", - (multispace0, char('>')), - )), - ), - |bytes: Vec| PropList::new(Node::Data(bytes)), - ) - .parse(input) -} - fn unescape_character(c: char) -> char { match c { '\\' => '\\', @@ -300,7 +258,7 @@ fn read_string>(input: I) -> IResult>(input: I) -> IResult> { delimited( multispace0, - alt((read_array, read_data, read_dictionary, read_string)), + alt((read_array, read_dictionary, read_string)), multispace0, ) .parse(input) @@ -363,60 +321,6 @@ mod test { PropList::new(Node::String(CString::new(s.as_bytes()).unwrap())) } - #[test] - fn parse_data() { - let plist = from_str("").unwrap(); - assert_eq!( - plist, - PropList::new(Node::Data(vec![0xde, 0xad, 0xbe, 0xef])) - ); - } - - #[test] - fn error_unclosed_data() { - let e = from_str("': -': -, -^ - -"# - ); - } - - #[test] - fn error_incomplete_data_byte() { - let e = from_str("> { pub(crate) node: N, } -/// Quick and dirty single-branch lookup mapping `b` to a hex character. Only -/// valid for `b` in `[0, 15]`. -fn byte_char(b: u8) -> char { - match b { - 0 => '0', - 1 => '1', - 2 => '2', - 3 => '3', - 4 => '4', - 5 => '5', - 6 => '6', - 7 => '7', - 8 => '8', - 9 => '9', - 10 => 'a', - 11 => 'b', - 12 => 'c', - 13 => 'd', - 14 => 'e', - 15 => 'f', - _ => unreachable!(), - } -} - impl> Display { /// Writes whitespace to `f` for the current level of indentation. fn write_indent(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -83,32 +59,6 @@ impl> Display { } } -/// Writes a hexadecimal representation of `bytes` to `f`, splitting `bytes` up -/// into space-delimited 32-bit quartets for readability. -fn write_data_bytes(bytes: &[u8], f: &mut fmt::Formatter) -> fmt::Result { - fn write(b: u8, f: &mut fmt::Formatter) -> fmt::Result { - let upper = (b >> 3) as u8; - let lower = (b & 0x0F) as u8; - write!(f, "{}", byte_char(upper))?; - write!(f, "{}", byte_char(lower)) - } - - let mut chunks = bytes.chunks(4); - if let Some(first) = chunks.next() { - for b in first { - write(*b, f)?; - } - } - for seg in chunks { - f.write_char(' ')?; - for b in seg { - write(*b, f)?; - } - } - - Ok(()) -} - /// Writes `s` to `f`, backslash-escaping special characters as appropriate for /// a quoted property list string. fn write_escaped_string(s: &str, f: &mut fmt::Formatter) -> fmt::Result { @@ -218,11 +168,6 @@ impl> fmt::Display for Display { self.write_indent(f)?; f.write_char(')')?; } - Node::Data(bytes) => { - write!(f, "<")?; - write_data_bytes(bytes, f)?; - write!(f, ">")?; - } Node::Dictionary(items) if items.is_empty() => write!(f, "{{}}")?, Node::Dictionary(items) if self.inline != Inline::No => { // Try to fit everything in one line. -- 2.39.5 From 252362545e4d6187c6049eb51739a7343de3e8e2 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 11 Sep 2025 21:02:32 -0400 Subject: [PATCH 27/39] Drop WMStringHashCallbacks, which is unused. --- WINGs/WINGs/WUtil.h | 4 ---- WINGs/hashtable.c | 7 ------- 2 files changed, 11 deletions(-) diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 070d5c4a..62965326 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -391,10 +391,6 @@ Bool WMNextHashEnumeratorItemAndKey(WMHashEnumerator *enumerator, extern const WMHashTableCallbacks WMIntHashCallbacks; /* sizeof(keys) are <= sizeof(void*) */ -extern const WMHashTableCallbacks WMStringHashCallbacks; -/* keys are strings. Strings will be copied with wstrdup() - * and freed with wfree() */ - extern const WMHashTableCallbacks WMStringPointerHashCallbacks; /* keys are strings, but they are not copied */ diff --git a/WINGs/hashtable.c b/WINGs/hashtable.c index 2620c784..58648084 100644 --- a/WINGs/hashtable.c +++ b/WINGs/hashtable.c @@ -407,13 +407,6 @@ const WMHashTableCallbacks WMIntHashCallbacks = { NULL }; -const WMHashTableCallbacks WMStringHashCallbacks = { - hashString, - compareStrings, - (retainFunc) wstrdup, - (releaseFunc) wfree -}; - const WMHashTableCallbacks WMStringPointerHashCallbacks = { hashString, compareStrings, -- 2.39.5 From 157a8e0d5ad9b1fa177120e3774c2841d233bda4 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 11 Sep 2025 21:26:08 -0400 Subject: [PATCH 28/39] Eliminate the retainKey and releaseKey hashtable callbacks. These fields are only ever NULL, so there's no reason to keep them. --- WINGs/WINGs/WUtil.h | 4 ---- WINGs/hashtable.c | 18 ++---------------- WINGs/proplist.c | 30 ------------------------------ 3 files changed, 2 insertions(+), 50 deletions(-) delete mode 100644 WINGs/proplist.c diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 62965326..226d5b88 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -169,10 +169,6 @@ typedef struct { unsigned (*hash)(const void *); /* NULL is pointer compare */ Bool (*keyIsEqual)(const void *, const void *); - /* NULL does nothing */ - void* (*retainKey)(const void *); - /* NULL does nothing */ - void (*releaseKey)(const void *); } WMHashTableCallbacks; diff --git a/WINGs/hashtable.c b/WINGs/hashtable.c index 58648084..3f8edd18 100644 --- a/WINGs/hashtable.c +++ b/WINGs/hashtable.c @@ -29,12 +29,6 @@ typedef struct W_HashTable { #define HASH(table, key) (((table)->callbacks.hash ? \ (*(table)->callbacks.hash)(key) : hashPtr(key)) % (table)->size) -#define DUPKEY(table, key) ((table)->callbacks.retainKey ? \ - (*(table)->callbacks.retainKey)(key) : (key)) - -#define RELKEY(table, key) if ((table)->callbacks.releaseKey) \ - (*(table)->callbacks.releaseKey)(key) - static inline unsigned hashString(const void *param) { const char *key = param; @@ -114,7 +108,6 @@ void WMResetHashTable(WMHashTable * table) item = table->table[i]; while (item) { tmp = item->next; - RELKEY(table, item->key); wfree(item); item = tmp; } @@ -140,7 +133,6 @@ void WMFreeHashTable(WMHashTable * table) item = table->table[i]; while (item) { tmp = item->next; - RELKEY(table, item->key); wfree(item); item = tmp; } @@ -237,15 +229,14 @@ void *WMHashInsert(WMHashTable * table, const void *key, const void *data) old = item->data; item->data = data; - RELKEY(table, item->key); - item->key = DUPKEY(table, key); + item->key = key; return (void *)old; } else { HashItem *nitem; nitem = wmalloc(sizeof(HashItem)); - nitem->key = DUPKEY(table, key); + nitem->key = key; nitem->data = data; nitem->next = table->table[h]; table->table[h] = nitem; @@ -278,7 +269,6 @@ static HashItem *deleteFromList(HashTable * table, HashItem * item, const void * || (!table->callbacks.keyIsEqual && key == item->key)) { next = item->next; - RELKEY(table, item->key); wfree(item); table->itemCount--; @@ -403,13 +393,9 @@ typedef void (*releaseFunc) (const void *); const WMHashTableCallbacks WMIntHashCallbacks = { NULL, NULL, - NULL, - NULL }; const WMHashTableCallbacks WMStringPointerHashCallbacks = { hashString, compareStrings, - NULL, - NULL }; diff --git a/WINGs/proplist.c b/WINGs/proplist.c deleted file mode 100644 index 4a574cd6..00000000 --- a/WINGs/proplist.c +++ /dev/null @@ -1,30 +0,0 @@ -#include - -#include "WUtil.h" - -/* - * This should be written in Rust whenever va_args support improves. - */ -WMPropList *WMCreatePLArray(WMPropList * elem, ...) -{ - WMPropList *plist, *nelem; - va_list ap; - - plist = WMCreateEmptyPLArray(); - - if (!elem) - return plist; - - WMAddToPLArray(plist, elem); - - va_start(ap, elem); - - while (1) { - nelem = va_arg(ap, WMPropList *); - if (!nelem) { - va_end(ap); - return plist; - } - WMAddToPLArray(plist, elem); - } -} -- 2.39.5 From 32c40643c24c6f9fba1b288085220dade9e30385 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sun, 14 Sep 2025 18:51:06 -0400 Subject: [PATCH 29/39] Replace WUtil hashtable with a Rust impl. This tweaks the hashtable API, and it is incomplete because the WUtil proplist impl depends heavily on a feature of the old API that is being discontinued. Moving the proplist code into Rust is our next objective. --- WINGs/Makefile.am | 2 +- WINGs/WINGs/WUtil.h | 3 +- WINGs/hashtable.c | 401 ------------------------------------- WINGs/notification.c | 6 +- WINGs/wballoon.c | 2 +- WINGs/wfontpanel.c | 2 +- WINGs/widgets.c | 2 +- WPrefs.app/Makefile.am | 1 + src/Makefile.am | 3 +- wutil-rs/Cargo.toml | 3 + wutil-rs/Makefile.am | 2 + wutil-rs/src/hash_table.rs | 328 ++++++++++++++++++++++++++++++ wutil-rs/src/lib.rs | 1 + 13 files changed, 346 insertions(+), 410 deletions(-) delete mode 100644 WINGs/hashtable.c create mode 100644 wutil-rs/src/hash_table.rs diff --git a/WINGs/Makefile.am b/WINGs/Makefile.am index 88fcad31..82ea4514 100644 --- a/WINGs/Makefile.am +++ b/WINGs/Makefile.am @@ -20,6 +20,7 @@ libWUtil_la_LIBADD = @LIBBSD@ $(wutilrs) EXTRA_DIST = BUGS make-rgb Examples Extras Tests + # wbutton.c libWINGs_la_SOURCES = \ configuration.c \ @@ -69,7 +70,6 @@ libWUtil_la_SOURCES = \ error.h \ findfile.c \ handlers.c \ - hashtable.c \ menuparser.c \ menuparser.h \ menuparser_macros.c \ diff --git a/WINGs/WINGs/WUtil.h b/WINGs/WINGs/WUtil.h index 226d5b88..18c8b52b 100644 --- a/WINGs/WINGs/WUtil.h +++ b/WINGs/WINGs/WUtil.h @@ -337,7 +337,8 @@ void WHandleEvents(void); /* ---[ WINGs/hashtable.c ]----------------------------------------------- */ -WMHashTable* WMCreateHashTable(const WMHashTableCallbacks callbacks); +WMHashTable* WMCreateIdentityHashTable(); +WMHashTable* WMCreateStringHashTable(); void WMFreeHashTable(WMHashTable *table); diff --git a/WINGs/hashtable.c b/WINGs/hashtable.c deleted file mode 100644 index 3f8edd18..00000000 --- a/WINGs/hashtable.c +++ /dev/null @@ -1,401 +0,0 @@ -#include - -#include -#include -#include -#include - -#include "WUtil.h" - -#define INITIAL_CAPACITY 23 - - -typedef struct HashItem { - const void *key; - const void *data; - - struct HashItem *next; /* collided item list */ -} HashItem; - -typedef struct W_HashTable { - WMHashTableCallbacks callbacks; - - unsigned itemCount; - unsigned size; /* table size */ - - HashItem **table; -} HashTable; - -#define HASH(table, key) (((table)->callbacks.hash ? \ - (*(table)->callbacks.hash)(key) : hashPtr(key)) % (table)->size) - -static inline unsigned hashString(const void *param) -{ - const char *key = param; - unsigned ret = 0; - unsigned ctr = 0; - - while (*key) { - ret ^= *key++ << ctr; - ctr = (ctr + 1) % sizeof(char *); - } - - return ret; -} - -static inline unsigned hashPtr(const void *key) -{ - return ((size_t) key / sizeof(char *)); -} - -static void rellocateItem(WMHashTable * table, HashItem * item) -{ - unsigned h; - - h = HASH(table, item->key); - - item->next = table->table[h]; - table->table[h] = item; -} - -static void rebuildTable(WMHashTable * table) -{ - HashItem *next; - HashItem **oldArray; - int i; - int oldSize; - int newSize; - - oldArray = table->table; - oldSize = table->size; - - newSize = table->size * 2; - - table->table = wmalloc(sizeof(char *) * newSize); - table->size = newSize; - - for (i = 0; i < oldSize; i++) { - while (oldArray[i] != NULL) { - next = oldArray[i]->next; - rellocateItem(table, oldArray[i]); - oldArray[i] = next; - } - } - wfree(oldArray); -} - -WMHashTable *WMCreateHashTable(const WMHashTableCallbacks callbacks) -{ - HashTable *table; - - table = wmalloc(sizeof(HashTable)); - - table->callbacks = callbacks; - - table->size = INITIAL_CAPACITY; - - table->table = wmalloc(sizeof(HashItem *) * table->size); - - return table; -} - -void WMResetHashTable(WMHashTable * table) -{ - HashItem *item, *tmp; - int i; - - for (i = 0; i < table->size; i++) { - item = table->table[i]; - while (item) { - tmp = item->next; - wfree(item); - item = tmp; - } - } - - table->itemCount = 0; - - if (table->size > INITIAL_CAPACITY) { - wfree(table->table); - table->size = INITIAL_CAPACITY; - table->table = wmalloc(sizeof(HashItem *) * table->size); - } else { - memset(table->table, 0, sizeof(HashItem *) * table->size); - } -} - -void WMFreeHashTable(WMHashTable * table) -{ - HashItem *item, *tmp; - int i; - - for (i = 0; i < table->size; i++) { - item = table->table[i]; - while (item) { - tmp = item->next; - wfree(item); - item = tmp; - } - } - wfree(table->table); - wfree(table); -} - -unsigned WMCountHashTable(WMHashTable * table) -{ - return table->itemCount; -} - -static HashItem *hashGetItem(WMHashTable *table, const void *key) -{ - unsigned h; - HashItem *item; - - h = HASH(table, key); - item = table->table[h]; - - if (table->callbacks.keyIsEqual) { - while (item) { - if ((*table->callbacks.keyIsEqual) (key, item->key)) { - break; - } - item = item->next; - } - } else { - while (item) { - if (key == item->key) { - break; - } - item = item->next; - } - } - return item; -} - -void *WMHashGet(WMHashTable * table, const void *key) -{ - HashItem *item; - - item = hashGetItem(table, key); - if (!item) - return NULL; - return (void *)item->data; -} - -Bool WMHashGetItemAndKey(WMHashTable * table, const void *key, void **retItem, void **retKey) -{ - HashItem *item; - - item = hashGetItem(table, key); - if (!item) - return False; - - if (retKey) - *retKey = (void *)item->key; - if (retItem) - *retItem = (void *)item->data; - return True; -} - -void *WMHashInsert(WMHashTable * table, const void *key, const void *data) -{ - unsigned h; - HashItem *item; - int replacing = 0; - - h = HASH(table, key); - /* look for the entry */ - item = table->table[h]; - if (table->callbacks.keyIsEqual) { - while (item) { - if ((*table->callbacks.keyIsEqual) (key, item->key)) { - replacing = 1; - break; - } - item = item->next; - } - } else { - while (item) { - if (key == item->key) { - replacing = 1; - break; - } - item = item->next; - } - } - - if (replacing) { - const void *old; - - old = item->data; - item->data = data; - item->key = key; - - return (void *)old; - } else { - HashItem *nitem; - - nitem = wmalloc(sizeof(HashItem)); - nitem->key = key; - nitem->data = data; - nitem->next = table->table[h]; - table->table[h] = nitem; - - table->itemCount++; - } - - /* OPTIMIZE: put this in an idle handler. */ - if (table->itemCount > table->size) { -#ifdef DEBUG0 - printf("rebuilding hash table...\n"); -#endif - rebuildTable(table); -#ifdef DEBUG0 - printf("finished rebuild.\n"); -#endif - } - - return NULL; -} - -static HashItem *deleteFromList(HashTable * table, HashItem * item, const void *key) -{ - HashItem *next; - - if (item == NULL) - return NULL; - - if ((table->callbacks.keyIsEqual && (*table->callbacks.keyIsEqual) (key, item->key)) - || (!table->callbacks.keyIsEqual && key == item->key)) { - - next = item->next; - wfree(item); - - table->itemCount--; - - return next; - } - - item->next = deleteFromList(table, item->next, key); - - return item; -} - -void WMHashRemove(WMHashTable * table, const void *key) -{ - unsigned h; - - h = HASH(table, key); - - table->table[h] = deleteFromList(table, table->table[h], key); -} - -WMHashEnumerator WMEnumerateHashTable(WMHashTable * table) -{ - WMHashEnumerator enumerator; - - enumerator.table = table; - enumerator.index = 0; - enumerator.nextItem = table->table[0]; - - return enumerator; -} - -void *WMNextHashEnumeratorItem(WMHashEnumerator * enumerator) -{ - const void *data = NULL; - - /* this assumes the table doesn't change between - * WMEnumerateHashTable() and WMNextHashEnumeratorItem() calls */ - - if (enumerator->nextItem == NULL) { - HashTable *table = enumerator->table; - while (++enumerator->index < table->size) { - if (table->table[enumerator->index] != NULL) { - enumerator->nextItem = table->table[enumerator->index]; - break; - } - } - } - - if (enumerator->nextItem) { - data = ((HashItem *) enumerator->nextItem)->data; - enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next; - } - - return (void *)data; -} - -void *WMNextHashEnumeratorKey(WMHashEnumerator * enumerator) -{ - const void *key = NULL; - - /* this assumes the table doesn't change between - * WMEnumerateHashTable() and WMNextHashEnumeratorKey() calls */ - - if (enumerator->nextItem == NULL) { - HashTable *table = enumerator->table; - while (++enumerator->index < table->size) { - if (table->table[enumerator->index] != NULL) { - enumerator->nextItem = table->table[enumerator->index]; - break; - } - } - } - - if (enumerator->nextItem) { - key = ((HashItem *) enumerator->nextItem)->key; - enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next; - } - - return (void *)key; -} - -Bool WMNextHashEnumeratorItemAndKey(WMHashEnumerator * enumerator, void **item, void **key) -{ - /* this assumes the table doesn't change between - * WMEnumerateHashTable() and WMNextHashEnumeratorItemAndKey() calls */ - - if (enumerator->nextItem == NULL) { - HashTable *table = enumerator->table; - while (++enumerator->index < table->size) { - if (table->table[enumerator->index] != NULL) { - enumerator->nextItem = table->table[enumerator->index]; - break; - } - } - } - - if (enumerator->nextItem) { - if (item) - *item = (void *)((HashItem *) enumerator->nextItem)->data; - if (key) - *key = (void *)((HashItem *) enumerator->nextItem)->key; - enumerator->nextItem = ((HashItem *) enumerator->nextItem)->next; - - return True; - } - - return False; -} - -static Bool compareStrings(const void *param1, const void *param2) -{ - const char *key1 = param1; - const char *key2 = param2; - - return strcmp(key1, key2) == 0; -} - -typedef void *(*retainFunc) (const void *); -typedef void (*releaseFunc) (const void *); - -const WMHashTableCallbacks WMIntHashCallbacks = { - NULL, - NULL, -}; - -const WMHashTableCallbacks WMStringPointerHashCallbacks = { - hashString, - compareStrings, -}; diff --git a/WINGs/notification.c b/WINGs/notification.c index b1b37128..d43fef54 100644 --- a/WINGs/notification.c +++ b/WINGs/notification.c @@ -88,10 +88,10 @@ static NotificationCenter *notificationCenter = NULL; void W_InitNotificationCenter(void) { notificationCenter = wmalloc(sizeof(NotificationCenter)); - notificationCenter->nameTable = WMCreateHashTable(WMStringPointerHashCallbacks); - notificationCenter->objectTable = WMCreateHashTable(WMIntHashCallbacks); + notificationCenter->nameTable = WMCreateStringHashTable(); + notificationCenter->objectTable = WMCreateIdentityHashTable(); notificationCenter->nilList = NULL; - notificationCenter->observerTable = WMCreateHashTable(WMIntHashCallbacks); + notificationCenter->observerTable = WMCreateIdentityHashTable(); } void W_ReleaseNotificationCenter(void) diff --git a/WINGs/wballoon.c b/WINGs/wballoon.c index c87b6c9e..8a9c8a84 100644 --- a/WINGs/wballoon.c +++ b/WINGs/wballoon.c @@ -65,7 +65,7 @@ struct W_Balloon *W_CreateBalloon(WMScreen * scr) W_ResizeView(bPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT); bPtr->flags.alignment = DEFAULT_ALIGNMENT; - bPtr->table = WMCreateHashTable(WMIntHashCallbacks); + bPtr->table = WMCreateIdentityHashTable(); bPtr->delay = DEFAULT_DELAY; diff --git a/WINGs/wfontpanel.c b/WINGs/wfontpanel.c index df01ed65..c57678ab 100644 --- a/WINGs/wfontpanel.c +++ b/WINGs/wfontpanel.c @@ -535,7 +535,7 @@ static void listFamilies(WMScreen * scr, WMFontPanel * panel) if (pat) FcPatternDestroy(pat); - families = WMCreateHashTable(WMStringPointerHashCallbacks); + families = WMCreateStringHashTable(); if (fs) { for (i = 0; i < fs->nfont; i++) { diff --git a/WINGs/widgets.c b/WINGs/widgets.c index 95e4fd77..c9a48245 100644 --- a/WINGs/widgets.c +++ b/WINGs/widgets.c @@ -630,7 +630,7 @@ WMScreen *WMCreateScreenWithRContext(Display * display, int screen, RContext * c scrPtr->rootWin = RootWindow(display, screen); - scrPtr->fontCache = WMCreateHashTable(WMStringPointerHashCallbacks); + scrPtr->fontCache = WMCreateStringHashTable(); scrPtr->xftdraw = XftDrawCreate(scrPtr->display, W_DRAWABLE(scrPtr), scrPtr->visual, scrPtr->colormap); diff --git a/WPrefs.app/Makefile.am b/WPrefs.app/Makefile.am index 50c258eb..7b23e0a0 100644 --- a/WPrefs.app/Makefile.am +++ b/WPrefs.app/Makefile.am @@ -67,6 +67,7 @@ WPrefs_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la WPrefs_LDADD = \ $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\ + $(top_builddir)/wings-rs/target/debug/libwings_rs.la\ $(top_builddir)/WINGs/libWINGs.la\ $(top_builddir)/WINGs/libWUtil.la\ $(top_builddir)/wrlib/libwraster.la \ diff --git a/src/Makefile.am b/src/Makefile.am index f704516a..8ba6f2af 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -134,7 +134,7 @@ else nodist_wmaker_SOURCES = misc.hack_nf.c \ xmodifier.hack_nf.c -CLEANFILES = $(nodist_wmaker_SOURCES) ../wmaker-rs/target +CLEANFILES = $(nodist_wmaker_SOURCES) misc.hack_nf.c: misc.c $(top_srcdir)/script/nested-func-to-macro.sh $(AM_V_GEN)$(top_srcdir)/script/nested-func-to-macro.sh \ @@ -160,6 +160,7 @@ wmaker_LDADD = \ $(top_builddir)/WINGs/libWINGs.la\ $(top_builddir)/WINGs/libWUtil.la\ $(top_builddir)/wrlib/libwraster.la\ + $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\ $(top_builddir)/wmaker-rs/target/debug/libwmaker_rs.a\ @XLFLAGS@ \ @LIBXRANDR@ \ diff --git a/wutil-rs/Cargo.toml b/wutil-rs/Cargo.toml index 66e739ea..34de6e65 100644 --- a/wutil-rs/Cargo.toml +++ b/wutil-rs/Cargo.toml @@ -11,5 +11,8 @@ cc = "1.0" [dependencies] atomic-write-file = "0.3" +hashbrown = "0.16.0" +libc = "0.2.175" nom = "8.0" nom-language = "0.1" +x11 = "2.21.0" diff --git a/wutil-rs/Makefile.am b/wutil-rs/Makefile.am index 7955beb3..00067adb 100644 --- a/wutil-rs/Makefile.am +++ b/wutil-rs/Makefile.am @@ -2,9 +2,11 @@ AUTOMAKE_OPTIONS = RUST_SOURCES = \ src/array.rs \ + src/data.rs \ src/defines.c \ src/defines.rs \ src/find_file.rs \ + src/hash_table.rs \ src/lib.rs \ src/memory.rs \ src/prop_list.rs diff --git a/wutil-rs/src/hash_table.rs b/wutil-rs/src/hash_table.rs new file mode 100644 index 00000000..569f4ae2 --- /dev/null +++ b/wutil-rs/src/hash_table.rs @@ -0,0 +1,328 @@ +use hashbrown::hash_map::{self, HashMap}; + +use std::{ + borrow::Borrow, + ffi::{CStr, c_void}, + hash::{Hash, Hasher}, + mem, +}; + +pub enum HashTable { + PointerKeyed(HashMap), + StringKeyed(HashMap), +} + +impl HashTable { + pub fn new_pointer_keyed() -> Self { + HashTable::PointerKeyed(HashMap::new()) + } + + pub fn new_string_keyed() -> Self { + HashTable::StringKeyed(HashMap::new()) + } + + pub fn clear(&mut self) { + match self { + HashTable::PointerKeyed(m) => m.clear(), + HashTable::StringKeyed(m) => m.clear(), + } + } + + pub fn len(&self) -> usize { + match self { + HashTable::PointerKeyed(m) => m.len(), + HashTable::StringKeyed(m) => m.len(), + } + } + + pub unsafe fn get(&self, key: *const i8) -> Option<*mut i8> { + match self { + HashTable::PointerKeyed(m) => { + let key = key.cast_mut(); + m.get(&key).map(|x| x.0) + } + HashTable::StringKeyed(m) => { + let key = StringPointer(key.cast_mut()); + let v = m.get(&key).map(|x| x.0); + mem::forget(key); + v + } + } + } + + pub unsafe fn insert(&mut self, key: *mut i8, data: VoidPointer) -> Option { + match self { + HashTable::PointerKeyed(m) => m.insert(VoidPointer(key), data), + HashTable::StringKeyed(m) => m.insert(StringPointer(key), data), + } + } + + pub unsafe fn remove(&mut self, key: *const i8) { + match self { + HashTable::PointerKeyed(m) => { + let key = key.cast_mut(); + m.remove(&key); + } + HashTable::StringKeyed(m) => { + let key = StringPointer(key.cast_mut()); + m.remove(&key); + mem::forget(key); + } + } + } +} + +#[derive(Debug, Eq, PartialEq, Hash)] +#[repr(transparent)] +pub struct VoidPointer(*mut i8); + +impl Drop for VoidPointer { + fn drop(&mut self) { + unsafe { libc::free(self.0.cast::()) } + } +} + +impl Borrow<*mut i8> for VoidPointer { + fn borrow(&self) -> &*mut i8 { + &self.0 + } +} + +#[derive(Debug)] +#[repr(transparent)] +pub struct StringPointer(*mut i8); + +impl PartialEq for StringPointer { + fn eq(&self, other: &Self) -> bool { + match (self.0.is_null(), other.0.is_null()) { + (true, true) => true, + (true, false) => false, + (false, true) => false, + (false, false) => unsafe { CStr::from_ptr(self.0) == CStr::from_ptr(other.0) }, + } + } +} + +impl Eq for StringPointer {} + +impl Hash for StringPointer { + fn hash(&self, h: &mut H) { + if self.0.is_null() { + h.write_usize(0) + } else { + unsafe { CStr::from_ptr(self.0).hash(h) } + } + } +} + +impl Drop for StringPointer { + fn drop(&mut self) { + unsafe { + libc::free(self.0.cast::()); + } + } +} + +pub enum Enumerator<'a> { + PointerKeyed(hash_map::IterMut<'a, VoidPointer, VoidPointer>), + StringKeyed(hash_map::IterMut<'a, StringPointer, VoidPointer>), +} + +pub mod ffi { + use std::{ + ffi::{c_int, c_uint, c_void}, + mem, ptr, + }; + + use super::{Enumerator, HashTable, StringPointer, VoidPointer}; + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn WMCreateIdentityHashTable() -> *mut HashTable { + Box::leak(Box::new(HashTable::new_pointer_keyed())) + } + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn WMCreateStringHashTable() -> *mut HashTable { + Box::leak(Box::new(HashTable::new_string_keyed())) + } + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn WMFreeHashTable(table: *mut HashTable) { + if !table.is_null() { + let _ = unsafe { Box::from_raw(table) }; + } + } + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn WMResetHashTable(table: *mut HashTable) { + if !table.is_null() { + unsafe { + (*table).clear(); + } + } + } + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn WMCountHashTable(table: *mut HashTable) -> c_uint { + if table.is_null() { + 0 + } else { + (unsafe { (*table).len() }) as c_uint + } + } + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn WMHashGet(table: *mut HashTable, key: *const c_void) -> *mut c_void { + if table.is_null() { + return ptr::null_mut(); + } + let key = key.cast::(); + (unsafe { (*table).get(key) }) + .map(|v| v.cast::()) + .unwrap_or(ptr::null_mut()) + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMHashGetItemAndKey( + table: *mut HashTable, + key: *const c_void, + value_dest: *mut *mut c_void, + key_dest: *mut *const c_void, + ) -> c_int { + if table.is_null() { + return 0; + } + let table = unsafe { &mut *table }; + match table { + HashTable::PointerKeyed(m) => { + let key = VoidPointer(key.cast::().cast_mut()); + let result = match m.get_key_value_mut(&key) { + Some((k, v)) => { + unsafe { + *key_dest = k.0.cast::(); + *value_dest = v.0.cast::(); + } + 1 + } + None => 0, + }; + mem::forget(key); + result + } + HashTable::StringKeyed(m) => { + let key = StringPointer(key.cast::().cast_mut()); + let result = match m.get_key_value_mut(&key) { + Some((k, v)) => { + unsafe { + *key_dest = k.0.cast::(); + *value_dest = v.0.cast::(); + } + 1 + } + None => 0, + }; + mem::forget(key); + return result; + } + } + } + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn WMHashInsert( + table: *mut HashTable, + key: *mut c_void, + data: *mut c_void, + ) -> *mut c_void { + if table.is_null() { + return ptr::null_mut(); + } + match unsafe { (*table).insert(key.cast::(), VoidPointer(data.cast::())) } { + Some(v) => { + let raw = v.0; + mem::forget(v); + raw.cast::() + } + None => ptr::null_mut(), + } + } + + #[unsafe(no_mangle)] + #[allow(non_snake_case)] + pub unsafe extern "C" fn WMHashRemove(table: *mut HashTable, key: *mut c_void) { + if table.is_null() { + return; + } + unsafe { + (*table).remove(key.cast::()); + } + } + + /// Important note: this may leak memory if you don't pass the enumerator + /// back to [`WMFreeHashEnumerator`]. This is a breaking change from the + /// original C implementation, which did not require any resource cleanup. + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMEnumerateHashTable( + table: *mut HashTable, + ) -> *mut Enumerator<'static> { + if table.is_null() { + return ptr::null_mut(); + } + let table = unsafe { &mut *table }; + match table { + HashTable::PointerKeyed(m) => { + Box::leak(Box::new(Enumerator::PointerKeyed(m.iter_mut()))) + } + HashTable::StringKeyed(m) => Box::leak(Box::new(Enumerator::StringKeyed(m.iter_mut()))), + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMFreeHashEnumerator(e: *mut Enumerator<'static>) { + if !e.is_null() { + let _ = unsafe { Box::from_raw(e) }; + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMNextHashEnumeratorItem(e: *mut Enumerator<'static>) -> *mut c_void { + if e.is_null() { + return ptr::null_mut(); + } + let e = unsafe { &mut *e }; + match e { + Enumerator::PointerKeyed(i) => match i.next() { + Some((_, v)) => v.0.cast::(), + None => ptr::null_mut(), + }, + Enumerator::StringKeyed(i) => match i.next() { + Some((_, v)) => v.0.cast::(), + None => ptr::null_mut(), + }, + } + } + + #[unsafe(no_mangle)] + pub unsafe extern "C" fn WMNextHashEnumeratorKey(e: *mut Enumerator<'static>) -> *mut c_void { + if e.is_null() { + return ptr::null_mut(); + } + let e = unsafe { &mut *e }; + match e { + Enumerator::PointerKeyed(i) => match i.next() { + Some((k, _)) => k.0.cast::(), + None => ptr::null_mut(), + }, + Enumerator::StringKeyed(i) => match i.next() { + Some((k, _)) => k.0.cast::(), + None => ptr::null_mut(), + }, + } + } +} diff --git a/wutil-rs/src/lib.rs b/wutil-rs/src/lib.rs index 47577b48..68a17ca2 100644 --- a/wutil-rs/src/lib.rs +++ b/wutil-rs/src/lib.rs @@ -2,5 +2,6 @@ pub mod array; pub mod data; pub mod defines; pub mod find_file; +pub mod hash_table; pub mod memory; pub mod prop_list; -- 2.39.5 From 7bded0055f1a6887bdf7fe40650418adb2fd8c06 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Fri, 19 Sep 2025 18:25:48 -0400 Subject: [PATCH 30/39] Drop unused wAbort function from WPrefs.app. --- WPrefs.app/main.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/WPrefs.app/main.c b/WPrefs.app/main.c index ede35fda..1312115c 100644 --- a/WPrefs.app/main.c +++ b/WPrefs.app/main.c @@ -47,14 +47,6 @@ struct { static pid_t DeadChildren[MAX_DEATHS]; static int DeadChildrenCount = 0; -static noreturn void wAbort(Bool foo) -{ - /* Parameter not used, but tell the compiler that it is ok */ - (void) foo; - - exit(1); -} - static void print_help(const char *progname) { printf(_("usage: %s [options]\n"), progname); -- 2.39.5 From 1a8d99b2c0ac721bc9e2f1a30cb576a394fe8d5c Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 14:20:28 -0400 Subject: [PATCH 31/39] PropList array items should return NULL on OOB index access. --- wutil-rs/src/prop_list.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/wutil-rs/src/prop_list.rs b/wutil-rs/src/prop_list.rs index 0c303cca..c5b488b2 100644 --- a/wutil-rs/src/prop_list.rs +++ b/wutil-rs/src/prop_list.rs @@ -666,10 +666,11 @@ pub mod ffi { } let plist = unsafe { &*plist }; if let Node::Array(ref items) = *plist.0.borrow() { - Box::leak(Box::new(items[index as usize].clone())) - } else { - ptr::null_mut() + if let Some(x) = items.get(index as usize) { + return Box::leak(Box::new(x.clone())); + } } + ptr::null_mut() } #[unsafe(no_mangle)] @@ -850,4 +851,13 @@ mod test { unsafe { memory::ffi::wfree(desc.cast()); } } + + #[test] + fn oob_array_access_returns_null() { + // This is the original WMArray behavior. I don't like it, but a bunch + // of existing code relies on it. + let mut list = PropList::new(Node::Array(vec![PropList::new(Node::String(CString::from(c"hello"))), + PropList::new(Node::String(CString::from(c"world!")))])); + assert!(unsafe { ffi::WMGetFromPLArray(&mut list, 3) }.is_null()); + } } -- 2.39.5 From bd61e588214994698af311594a2de6f0dc8cd8ef Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 14:23:56 -0400 Subject: [PATCH 32/39] Don't pass wmalloc'd memory to XFree. This is enough to get the wmaker into a minimally running state. We should complete this change by reviewing https://git.sdf.org/vitrine/wmaker/pulls/1. --- src/client.c | 4 ++-- src/window.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client.c b/src/client.c index e452f2df..7d64d78b 100644 --- a/src/client.c +++ b/src/client.c @@ -307,7 +307,7 @@ void wClientCheckProperty(WWindow * wwin, XPropertyEvent * event) wWindowUpdateName(wwin, tmp); } if (tmp) - XFree(tmp); + wfree(tmp); } break; @@ -616,7 +616,7 @@ void wClientCheckProperty(WWindow * wwin, XPropertyEvent * event) wWindowUpdateGNUstepAttr(wwin, attr); - XFree(attr); + wfree(attr); } else { wNETWMCheckClientHintChange(wwin, event); } diff --git a/src/window.c b/src/window.c index 9e0622df..508fa1b2 100644 --- a/src/window.c +++ b/src/window.c @@ -1413,7 +1413,7 @@ WWindow *wManageWindow(WScreen *scr, Window window) /* Update name must come after WApplication stuff is done */ wWindowUpdateName(wwin, title); if (title) - XFree(title); + wfree(title); XUngrabServer(dpy); -- 2.39.5 From 4f4dcf551bb78fcecc89b68f1d95588f8bd718cf Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 15:40:50 -0400 Subject: [PATCH 33/39] Restore proplist.c, which was clobbered by mistake during a rebase. Lessons learned: don't rebase so freely, review commits properly. --- WINGs/proplist.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 WINGs/proplist.c diff --git a/WINGs/proplist.c b/WINGs/proplist.c new file mode 100644 index 00000000..4a574cd6 --- /dev/null +++ b/WINGs/proplist.c @@ -0,0 +1,30 @@ +#include + +#include "WUtil.h" + +/* + * This should be written in Rust whenever va_args support improves. + */ +WMPropList *WMCreatePLArray(WMPropList * elem, ...) +{ + WMPropList *plist, *nelem; + va_list ap; + + plist = WMCreateEmptyPLArray(); + + if (!elem) + return plist; + + WMAddToPLArray(plist, elem); + + va_start(ap, elem); + + while (1) { + nelem = va_arg(ap, WMPropList *); + if (!nelem) { + va_end(ap); + return plist; + } + WMAddToPLArray(plist, elem); + } +} -- 2.39.5 From fbd6400186b8428e898c47a2a3fd82c1e98dee67 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 16:25:01 -0400 Subject: [PATCH 34/39] Remove stale reference to libwings_rs (which should have been deleted in a rebase). --- WPrefs.app/Makefile.am | 1 - 1 file changed, 1 deletion(-) diff --git a/WPrefs.app/Makefile.am b/WPrefs.app/Makefile.am index 7b23e0a0..50c258eb 100644 --- a/WPrefs.app/Makefile.am +++ b/WPrefs.app/Makefile.am @@ -67,7 +67,6 @@ WPrefs_DEPENDENCIES = $(top_builddir)/WINGs/libWINGs.la WPrefs_LDADD = \ $(top_builddir)/wutil-rs/target/debug/libwutil_rs.a\ - $(top_builddir)/wings-rs/target/debug/libwings_rs.la\ $(top_builddir)/WINGs/libWINGs.la\ $(top_builddir)/WINGs/libWUtil.la\ $(top_builddir)/wrlib/libwraster.la \ -- 2.39.5 From 65726a1e6a7bb13d6759aa68135dea362672e58b Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 16:25:37 -0400 Subject: [PATCH 35/39] WMHashTable ownership: it doesn't own keys or values. The WMHashTable rewrite was prematurely merged into refactor/wutil-rs, so we're picking up the pieces now. --- wutil-rs/Cargo.toml | 1 - wutil-rs/src/hash_table.rs | 114 ++++++++++++++++--------------------- 2 files changed, 48 insertions(+), 67 deletions(-) diff --git a/wutil-rs/Cargo.toml b/wutil-rs/Cargo.toml index 34de6e65..0d3e85dd 100644 --- a/wutil-rs/Cargo.toml +++ b/wutil-rs/Cargo.toml @@ -12,7 +12,6 @@ cc = "1.0" [dependencies] atomic-write-file = "0.3" hashbrown = "0.16.0" -libc = "0.2.175" nom = "8.0" nom-language = "0.1" x11 = "2.21.0" diff --git a/wutil-rs/src/hash_table.rs b/wutil-rs/src/hash_table.rs index 569f4ae2..62424714 100644 --- a/wutil-rs/src/hash_table.rs +++ b/wutil-rs/src/hash_table.rs @@ -1,15 +1,14 @@ use hashbrown::hash_map::{self, HashMap}; use std::{ - borrow::Borrow, - ffi::{CStr, c_void}, + ffi::CStr, hash::{Hash, Hasher}, mem, }; pub enum HashTable { - PointerKeyed(HashMap), - StringKeyed(HashMap), + PointerKeyed(HashMap<*mut u8, *mut u8>), + StringKeyed(HashMap), } impl HashTable { @@ -35,36 +34,34 @@ impl HashTable { } } - pub unsafe fn get(&self, key: *const i8) -> Option<*mut i8> { + pub unsafe fn get(&self, key: *const u8) -> Option<*mut u8> { match self { HashTable::PointerKeyed(m) => { let key = key.cast_mut(); - m.get(&key).map(|x| x.0) + m.get(&key).copied() } HashTable::StringKeyed(m) => { - let key = StringPointer(key.cast_mut()); - let v = m.get(&key).map(|x| x.0); - mem::forget(key); - v + let key = StringKey(key.cast_mut()); + m.get(&key).copied() } } } - pub unsafe fn insert(&mut self, key: *mut i8, data: VoidPointer) -> Option { + pub unsafe fn insert(&mut self, key: *mut u8, data: *mut u8) -> Option<*mut u8> { match self { - HashTable::PointerKeyed(m) => m.insert(VoidPointer(key), data), - HashTable::StringKeyed(m) => m.insert(StringPointer(key), data), + HashTable::PointerKeyed(m) => m.insert(key, data), + HashTable::StringKeyed(m) => m.insert(StringKey(key), data), } } - pub unsafe fn remove(&mut self, key: *const i8) { + pub unsafe fn remove(&mut self, key: *const u8) { match self { HashTable::PointerKeyed(m) => { let key = key.cast_mut(); m.remove(&key); } HashTable::StringKeyed(m) => { - let key = StringPointer(key.cast_mut()); + let key = StringKey(key.cast_mut()); m.remove(&key); mem::forget(key); } @@ -72,60 +69,51 @@ impl HashTable { } } -#[derive(Debug, Eq, PartialEq, Hash)] -#[repr(transparent)] -pub struct VoidPointer(*mut i8); +// #[derive(Debug, Eq, PartialEq, Hash)] +// #[repr(transparent)] +// pub struct VoidPointer(*mut u8); -impl Drop for VoidPointer { - fn drop(&mut self) { - unsafe { libc::free(self.0.cast::()) } - } -} +// impl Drop for VoidPointer { +// fn drop(&mut self) { +// unsafe { wfree(self.0.cast::()) } +// } +// } -impl Borrow<*mut i8> for VoidPointer { - fn borrow(&self) -> &*mut i8 { - &self.0 - } -} +// impl Borrow<*mut u8> for VoidPointer { +// fn borrow(&self) -> &*mut u8 { +// &self.0 +// } +// } #[derive(Debug)] #[repr(transparent)] -pub struct StringPointer(*mut i8); +pub struct StringKey(*const u8); -impl PartialEq for StringPointer { +impl PartialEq for StringKey { fn eq(&self, other: &Self) -> bool { match (self.0.is_null(), other.0.is_null()) { (true, true) => true, - (true, false) => false, - (false, true) => false, - (false, false) => unsafe { CStr::from_ptr(self.0) == CStr::from_ptr(other.0) }, + (false, false) => unsafe { CStr::from_ptr(self.0.cast()) == CStr::from_ptr(other.0.cast()) }, + _ => false, } } } -impl Eq for StringPointer {} +impl Eq for StringKey {} -impl Hash for StringPointer { +impl Hash for StringKey { fn hash(&self, h: &mut H) { if self.0.is_null() { h.write_usize(0) } else { - unsafe { CStr::from_ptr(self.0).hash(h) } - } - } -} - -impl Drop for StringPointer { - fn drop(&mut self) { - unsafe { - libc::free(self.0.cast::()); + unsafe { CStr::from_ptr(self.0.cast()).hash(h) } } } } pub enum Enumerator<'a> { - PointerKeyed(hash_map::IterMut<'a, VoidPointer, VoidPointer>), - StringKeyed(hash_map::IterMut<'a, StringPointer, VoidPointer>), + PointerKeyed(hash_map::IterMut<'a, *mut u8, *mut u8>), + StringKeyed(hash_map::IterMut<'a, StringKey, *mut u8>), } pub mod ffi { @@ -134,7 +122,7 @@ pub mod ffi { mem, ptr, }; - use super::{Enumerator, HashTable, StringPointer, VoidPointer}; + use super::{Enumerator, HashTable, StringKey}; #[unsafe(no_mangle)] #[allow(non_snake_case)] @@ -182,7 +170,7 @@ pub mod ffi { if table.is_null() { return ptr::null_mut(); } - let key = key.cast::(); + let key = key.cast::(); (unsafe { (*table).get(key) }) .map(|v| v.cast::()) .unwrap_or(ptr::null_mut()) @@ -201,27 +189,25 @@ pub mod ffi { let table = unsafe { &mut *table }; match table { HashTable::PointerKeyed(m) => { - let key = VoidPointer(key.cast::().cast_mut()); - let result = match m.get_key_value_mut(&key) { + let result = match m.get_key_value_mut(&key.cast::().cast_mut()) { Some((k, v)) => { unsafe { - *key_dest = k.0.cast::(); - *value_dest = v.0.cast::(); + *key_dest = k.cast::(); + *value_dest = v.cast::(); } 1 } None => 0, }; - mem::forget(key); result } HashTable::StringKeyed(m) => { - let key = StringPointer(key.cast::().cast_mut()); + let key = StringKey(key.cast::().cast_mut()); let result = match m.get_key_value_mut(&key) { Some((k, v)) => { unsafe { *key_dest = k.0.cast::(); - *value_dest = v.0.cast::(); + *value_dest = v.cast::(); } 1 } @@ -243,12 +229,8 @@ pub mod ffi { if table.is_null() { return ptr::null_mut(); } - match unsafe { (*table).insert(key.cast::(), VoidPointer(data.cast::())) } { - Some(v) => { - let raw = v.0; - mem::forget(v); - raw.cast::() - } + match unsafe { (*table).insert(key.cast::(), data.cast::()) } { + Some(v) => v.cast::(), None => ptr::null_mut(), } } @@ -260,7 +242,7 @@ pub mod ffi { return; } unsafe { - (*table).remove(key.cast::()); + (*table).remove(key.cast::()); } } @@ -298,25 +280,25 @@ pub mod ffi { let e = unsafe { &mut *e }; match e { Enumerator::PointerKeyed(i) => match i.next() { - Some((_, v)) => v.0.cast::(), + Some((_, v)) => v.cast::(), None => ptr::null_mut(), }, Enumerator::StringKeyed(i) => match i.next() { - Some((_, v)) => v.0.cast::(), + Some((_, v)) => v.cast::(), None => ptr::null_mut(), }, } } #[unsafe(no_mangle)] - pub unsafe extern "C" fn WMNextHashEnumeratorKey(e: *mut Enumerator<'static>) -> *mut c_void { + pub unsafe extern "C" fn WMNextHashEnumeratorKey(e: *mut Enumerator<'static>) -> *const c_void { if e.is_null() { return ptr::null_mut(); } let e = unsafe { &mut *e }; match e { Enumerator::PointerKeyed(i) => match i.next() { - Some((k, _)) => k.0.cast::(), + Some((k, _)) => k.cast::(), None => ptr::null_mut(), }, Enumerator::StringKeyed(i) => match i.next() { -- 2.39.5 From d66eb34f166543b48909847adc6d275737f3fa8d Mon Sep 17 00:00:00 2001 From: Stu Black Date: Thu, 23 Oct 2025 16:26:47 -0400 Subject: [PATCH 36/39] Satisfy the dangerous_implicit_autorefs lint. --- wutil-rs/src/array.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wutil-rs/src/array.rs b/wutil-rs/src/array.rs index a0efe1bd..42a0d45b 100644 --- a/wutil-rs/src/array.rs +++ b/wutil-rs/src/array.rs @@ -204,7 +204,7 @@ pub mod ffi { return ptr::null_mut(); } unsafe { - (*array) + (&(*array)) .items .get(index as usize) .map(|p| p.as_ptr()) -- 2.39.5 From 564501953f2d65913a4974974def628618ba4133 Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sat, 25 Oct 2025 01:53:23 -0400 Subject: [PATCH 37/39] Use wfree instead of XFree in a few more places. This fixes some crashes found in cursory smoke tests. You can now open and close some windows without crashing immediately. --- src/window.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/window.c b/src/window.c index 0f311437..6c2a01be 100644 --- a/src/window.c +++ b/src/window.c @@ -283,10 +283,10 @@ void wWindowDestroy(WWindow *wwin) XFree(wwin->wm_hints); if (wwin->wm_instance) - XFree(wwin->wm_instance); + wfree(wwin->wm_instance); if (wwin->wm_class) - XFree(wwin->wm_class); + wfree(wwin->wm_class); if (wwin->wm_gnustep_attr) wfree(wwin->wm_gnustep_attr); -- 2.39.5 From 026426e6c3c3ad3bb1b8f13e1ca4abd0d6b0075b Mon Sep 17 00:00:00 2001 From: Stu Black Date: Sat, 25 Oct 2025 12:41:52 -0400 Subject: [PATCH 38/39] Remove VoidPointer impls that were commented out in previous commit. --- wutil-rs/src/hash_table.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/wutil-rs/src/hash_table.rs b/wutil-rs/src/hash_table.rs index 62424714..77c20823 100644 --- a/wutil-rs/src/hash_table.rs +++ b/wutil-rs/src/hash_table.rs @@ -69,22 +69,6 @@ impl HashTable { } } -// #[derive(Debug, Eq, PartialEq, Hash)] -// #[repr(transparent)] -// pub struct VoidPointer(*mut u8); - -// impl Drop for VoidPointer { -// fn drop(&mut self) { -// unsafe { wfree(self.0.cast::()) } -// } -// } - -// impl Borrow<*mut u8> for VoidPointer { -// fn borrow(&self) -> &*mut u8 { -// &self.0 -// } -// } - #[derive(Debug)] #[repr(transparent)] pub struct StringKey(*const u8); -- 2.39.5 From 46af2c27ee153f957b79a840293aae07074efd5d Mon Sep 17 00:00:00 2001 From: Stu Black Date: Tue, 28 Oct 2025 21:30:34 -0400 Subject: [PATCH 39/39] Tweak start-captive-wmaker.sh to behave a little more nicely. * Select $DISPLAY dynamically because X11 likes :0 and Wayland likes :1 and who knows what else might like some other value. * Kill Xephyr after wmaker exits. * 640x480 should be big enough for anyone. (And the window shouldn't get in the way so much.) --- start-captive-wmaker.sh | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/start-captive-wmaker.sh b/start-captive-wmaker.sh index 32a5f402..2afb4c33 100755 --- a/start-captive-wmaker.sh +++ b/start-captive-wmaker.sh @@ -77,10 +77,18 @@ if [ -n "$1" -a -x "$WindowMaker$1" ] ; then shift fi -Xephyr -screen 1080x760 :1 & +for i in $(seq 5 10) ; do + if [ "x$DISPLAY" != "x:$i" ] ; then + xephyr_display=":$i" + break + fi +done +echo "Running Xephyr on display $xephyr_display" + +Xephyr -screen 640x480 "$xephyr_display" & xephyr_pid=$! -DISPLAY=:1 gdb \ +DISPLAY="$xephyr_display" gdb \ --directory "$project_base" \ --quiet \ - --args "$WindowMaker" -display :1 --for-real "$@" + --args "$WindowMaker" -display "$xephyr_display" --for-real "$@" kill $xephyr_pid -- 2.39.5