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); + } +}