[PATCH] add: SetEnvironmentStringsW

Zebediah Figura zfigura at codeweavers.com
Wed Mar 11 21:24:33 CDT 2020


On 3/11/20 4:50 PM, Frank Uhlig wrote:
> Sorry for the late reply, but have finally gotten back to this patch.
> 
> On Thu, Feb 20, 2020 at 10:35 PM Zebediah Figura <z.figura12 at gmail.com> wrote:
>>
>> On 2/20/20 3:12 PM, Frank Uhlig wrote:
>>> Hi Zebediah, thanks for looking into this. Please find my responses
>>> inline below. I've already incorporated many things, but still have
>>> some remaining questions and comments.
>>>
>>> I would re-submit it soon, and include a "versioning" in the mail tag
>>> as I've seen on other submissions (i.e., [PATCH v2]).
>>>
>>> On Mon, Feb 10, 2020 at 4:38 AM Zebediah Figura <z.figura12 at gmail.com> wrote:
>>>>
>>>> Hello Frank, thanks for the patch.
>>>>
>>>> With regard to the title, convention is to put the name of the component
>>>> first, followed by a colon, then a verb describing what the patch does,
>>>> so e.g. "kernelbase: Add SetEnvironmentStringsW()."
>>>>
>>>
>>> done.
>>>
>>>> I have a few more comments inlined below which might help improve the patch:
>>>>
>>>> On 1/22/20 3:33 PM, Frank Uhlig wrote:
>>>>> From: Frank Uhlig <fuulish at users.noreply.github.com>
>>>>>
>>>>> Signed-off-by: Frank Uhlig <uhlig.frank at gmail.com>
>>>>> ---
>>>>>   ...ms-win-core-processenvironment-l1-1-0.spec |  2 +-
>>>>>   ...ms-win-core-processenvironment-l1-2-0.spec |  2 +-
>>>>>   dlls/kernel32/kernel32.spec                   |  2 +-
>>>>>   dlls/kernel32/tests/environ.c                 | 61 +++++++++++++++++++
>>>>>   dlls/kernelbase/kernelbase.spec               |  2 +-
>>>>>   dlls/kernelbase/process.c                     | 29 +++++++++
>>>>>   include/winbase.h                             |  1 +
>>>>>   7 files changed, 95 insertions(+), 4 deletions(-)
>>>>>
>>>>> diff --git a/dlls/api-ms-win-core-processenvironment-l1-1-0/api-ms-win-core-processenvironment-l1-1-0.spec b/dlls/api-ms-win-core-processenvironment-l1-1-0/api-ms-win-core-processenvironment-l1-1-0.spec
>>>>> index e3698d6efd..7a62b74390 100644
>>>>> --- a/dlls/api-ms-win-core-processenvironment-l1-1-0/api-ms-win-core-processenvironment-l1-1-0.spec
>>>>> +++ b/dlls/api-ms-win-core-processenvironment-l1-1-0/api-ms-win-core-processenvironment-l1-1-0.spec
>>>>> @@ -15,7 +15,7 @@
>>>>>   @ stdcall SearchPathW(wstr wstr wstr long ptr ptr) kernel32.SearchPathW
>>>>>   @ stdcall SetCurrentDirectoryA(str) kernel32.SetCurrentDirectoryA
>>>>>   @ stdcall SetCurrentDirectoryW(wstr) kernel32.SetCurrentDirectoryW
>>>>> -@ stub SetEnvironmentStringsW
>>>>> +@ stdcall SetEnvironmentStringsW(ptr) kernel32.SetEnvironmentStringsW
>>>>>   @ stdcall SetEnvironmentVariableA(str str) kernel32.SetEnvironmentVariableA
>>>>>   @ stdcall SetEnvironmentVariableW(wstr wstr) kernel32.SetEnvironmentVariableW
>>>>>   @ stdcall SetStdHandle(long long) kernel32.SetStdHandle
>>>>> diff --git a/dlls/api-ms-win-core-processenvironment-l1-2-0/api-ms-win-core-processenvironment-l1-2-0.spec b/dlls/api-ms-win-core-processenvironment-l1-2-0/api-ms-win-core-processenvironment-l1-2-0.spec
>>>>> index 2c25ee1a07..c93d221c5e 100644
>>>>> --- a/dlls/api-ms-win-core-processenvironment-l1-2-0/api-ms-win-core-processenvironment-l1-2-0.spec
>>>>> +++ b/dlls/api-ms-win-core-processenvironment-l1-2-0/api-ms-win-core-processenvironment-l1-2-0.spec
>>>>> @@ -17,7 +17,7 @@
>>>>>   @ stdcall SearchPathW(wstr wstr wstr long ptr ptr) kernel32.SearchPathW
>>>>>   @ stdcall SetCurrentDirectoryA(str) kernel32.SetCurrentDirectoryA
>>>>>   @ stdcall SetCurrentDirectoryW(wstr) kernel32.SetCurrentDirectoryW
>>>>> -@ stub SetEnvironmentStringsW
>>>>> +@ stdcall SetEnvironmentStringsW(ptr) kernel32.SetEnvironmentStringsW
>>>>>   @ stdcall SetEnvironmentVariableA(str str) kernel32.SetEnvironmentVariableA
>>>>>   @ stdcall SetEnvironmentVariableW(wstr wstr) kernel32.SetEnvironmentVariableW
>>>>>   @ stdcall SetStdHandle(long long) kernel32.SetStdHandle
>>>>> diff --git a/dlls/kernel32/kernel32.spec b/dlls/kernel32/kernel32.spec
>>>>> index be48ef1694..2b74a4182e 100644
>>>>> --- a/dlls/kernel32/kernel32.spec
>>>>> +++ b/dlls/kernel32/kernel32.spec
>>>>> @@ -1387,7 +1387,7 @@
>>>>>   # @ stub SetDynamicTimeZoneInformation
>>>>>   @ stdcall -import SetEndOfFile(long)
>>>>>   # @ stub SetEnvironmentStringsA
>>>>> -# @ stub SetEnvironmentStringsW
>>>>> +@ stdcall -import SetEnvironmentStringsW (ptr)
>>>>>   @ stdcall -import SetEnvironmentVariableA(str str)
>>>>>   @ stdcall -import SetEnvironmentVariableW(wstr wstr)
>>>>>   @ stdcall -import SetErrorMode(long)
>>>>> diff --git a/dlls/kernel32/tests/environ.c b/dlls/kernel32/tests/environ.c
>>>>> index 44a6a0cff0..53b2803e77 100644
>>>>> --- a/dlls/kernel32/tests/environ.c
>>>>> +++ b/dlls/kernel32/tests/environ.c
>>>>> @@ -579,6 +579,66 @@ static void test_GetEnvironmentStringsW(void)
>>>>>       FreeEnvironmentStringsW(env2);
>>>>>   }
>>>>>
>>>>> +static void test_SetEnvironmentStringsW(void)
>>>>> +{
>>>>> +    DWORD buf_len;
>>>>> +    BOOL ret;
>>>>> +    DWORD ret_size;
>>>>> +
>>>>> +    static WCHAR buf[256];
>>>>> +
>>>>> +    static WCHAR name[] = {'N','a','m','e',0};
>>>>> +    static WCHAR value[] = {'V','a','l','u','e',0};
>>>>> +    static WCHAR env[] = {'N','a','m','e','=','V','a','l','u','e',0};
>>>>> +
>>>>> +    static WCHAR eman[] = {'e','m','a','N',0};
>>>>> +    static WCHAR eulav[] = {'e','u','l','a','V',0};
>>>>> +    static WCHAR vne[] = {'e','m','a','N','=','e','u','l','a','V',0};
>>>>> +
>>>>> +    static WCHAR var[] = {'V','a','r',0};
>>>>> +    static WCHAR val[] = {'V','a','l',0};
>>>>> +    static WCHAR rav[] = {'r','a','V',0};
>>>>> +    static WCHAR lav[] = {'l','a','V',0};
>>>>> +    static WCHAR mul[] = {'V','a','r','=','V','a','l',' ','r','a','V','=','l','a','V',0};
>>>>> +
>>>>> +    static WCHAR empty[] = {'V','a','r','=',0};
>>>>
>>>> You can use wide character string literals in tests (which should also
>>>> allow you to get rid of some of these declarations).
>>>>
>>>
>>> Didn't change this, b/c I was trying to keep the same style as already
>>> there and described on the website
>>> (https://wiki.winehq.org/Developer_Hints#Using_only_C89-compliant_code).
>>
>> The wiki is out of date on this particular topic. Someone™ should
>> probably update it.
>>
>>>
>>>> I suspect that you also want either local variables or (when applicable)
>>>> "static const". There's no point declaring non-constant variables as static.
>>>>
>>>
>>> Agreed, but kept as is to be consistent; and my test code is now not
>>> changing the input anymore.
>>
>> To be consistent with what? There may be existing code that uses bad
>> style, but that's not really a good reason to emulate it.
>>
> 
> consistent style is less prone to errors. but further, I prefer the
> array initialization b/c of the visible double-0 termination of the
> strings.
> and yes, the static was a remnant of a previous static const, and got removed.
> 
>>>
>>>>> +
>>>>> +    buf_len = sizeof(buf) / 2;
>>>>
>>>> I personally think using an extra variable is redundant here. I also
>>>> think "ARRAY_SIZE(buf)" would be more expressive.
>>>>
>>>
>>> Changed to ARRAY_SIZE(buf)
>>>
>>>>> +
>>>>> +    ret = SetEnvironmentStringsW(env);
>>>>> +    ok( ret, "Setting environment strings failed\n" );
>>>>> +
>>>>> +    ret_size = GetEnvironmentVariableW(name, buf, buf_len);
>>>>> +    ok( ((0 != ret_size) && (0 == wcscmp(buf, value))),
>>>>> +       "Environment String settings resulted in different value\n");
>>>>
>>>> With all of these tests I think it would be helpful to print the actual
>>>> value of "ret_size" and contents of "buf" in the case the test fails. I
>>>> would also recommend testing "ret_size" against the exact expected
>>>> length, and perhaps splitting the two tests into two separate ok() calls.
>>>>
>>>
>>> Made the checks more specific, and included more info in the output
>>>
>>>>> +
>>>>> +    ret = SetEnvironmentStringsW(vne);
>>>>> +    ok( ret, "Setting environment strings failed\n" );
>>>>> +
>>>>> +    ret_size = GetEnvironmentVariableW(eman, buf, buf_len);
>>>>> +    ok( ((0 != ret_size) && (0 == wcscmp(buf, eulav))),
>>>>> +       "Environment String settings resulted in different value\n");
>>>>> +
>>>>> +    ret = SetEnvironmentStringsW(mul);
>>>>> +    ok( ret, "Setting environment strings failed\n" );
>>>>> +
>>>>> +    ret_size = GetEnvironmentVariableW(var, buf, buf_len);
>>>>> +    ok( ((0 != ret_size) && (0 == wcscmp(buf, val))),
>>>>> +       "Environment String settings resulted in different value\n");
>>>>> +
>>>>> +    ret_size = GetEnvironmentVariableW(rav, buf, buf_len);
>>>>> +    ok( ((0 != ret_size) && (0 == wcscmp(buf, lav))),
>>>>> +       "Environment String settings resulted in different value\n");
>>>>> +
>>>>> +    ret = SetEnvironmentStringsW(empty);
>>>>> +    ok( ret, "Setting environment strings failed\n" );
>>>>> +
>>>>> +    ret_size = GetEnvironmentVariableW(var, buf, buf_len);
>>>>> +    ok( (0 == ret_size),
>>>>> +       "Environment String settings resulted in different value\n");
>>>>> +
>>>>> +}
>>>>> +
>>>>>   START_TEST(environ)
>>>>>   {
>>>>>       init_functionpointers();
>>>>> @@ -591,4 +651,5 @@ START_TEST(environ)
>>>>>       test_GetComputerNameExA();
>>>>>       test_GetComputerNameExW();
>>>>>       test_GetEnvironmentStringsW();
>>>>> +    test_SetEnvironmentStringsW();
>>>>>   }
>>>>> diff --git a/dlls/kernelbase/kernelbase.spec b/dlls/kernelbase/kernelbase.spec
>>>>> index f36d4d525c..a5e30b938f 100644
>>>>> --- a/dlls/kernelbase/kernelbase.spec
>>>>> +++ b/dlls/kernelbase/kernelbase.spec
>>>>> @@ -1422,7 +1422,7 @@
>>>>>   @ stdcall SetDefaultDllDirectories(long)
>>>>>   # @ stub SetDynamicTimeZoneInformation
>>>>>   @ stdcall SetEndOfFile(long)
>>>>> -@ stub SetEnvironmentStringsW
>>>>> +@ stdcall SetEnvironmentStringsW(ptr)
>>>>>   @ stdcall SetEnvironmentVariableA(str str)
>>>>>   @ stdcall SetEnvironmentVariableW(wstr wstr)
>>>>>   @ stdcall SetErrorMode(long)
>>>>> diff --git a/dlls/kernelbase/process.c b/dlls/kernelbase/process.c
>>>>> index a07dddb1fc..97b59b9548 100644
>>>>> --- a/dlls/kernelbase/process.c
>>>>> +++ b/dlls/kernelbase/process.c
>>>>> @@ -1345,6 +1345,35 @@ BOOL WINAPI DECLSPEC_HOTPATCH SetEnvironmentVariableW( LPCWSTR name, LPCWSTR val
>>>>>   }
>>>>>
>>>>>
>>>>> +/***********************************************************************
>>>>> + *           SetEnvironmentStringsW   (kernelbase.@)
>>>>> + */
>>>>> +BOOL WINAPI DECLSPEC_HOTPATCH SetEnvironmentStringsW( LPWCH NewEnvironment )
>>>>
>>>> "WCHAR *" would probably be preferred.
>>>>
>>>
>>> Any particular reason for this? That's exactly what LPWCH is defined as.
>>
>> Broadly, use of pointer typedefs tends to be dispreferred in new code. I
>> also suspect that "env" would be nicer than "NewEnvironment".
>>
> 
> OK, fine by me.
> 
> 
>>>>> +{
>>>>> +
>>>>
>>>> Stray newline.
>>>>
>>>
>>> Done.
>>>
>>>>> +    BOOL rc = FALSE;
>>>>> +    WCHAR *var, *val;
>>>>> +    const WCHAR delim[] = {' ','=','\n',0};
>>>>> +
>>>>> +    TRACE( "(%s)\n", debugstr_w(NewEnvironment));
>>>>> +
>>>>> +    if (NULL == NewEnvironment)
>>>>> +        return rc;
>>>>
>>>> This probably deserves a test.
>>>>
>>>
>>> I tried including a simple test by just calling
>>> SetEnvironmentStrings(NULL) and that lead to an unhandled exception on
>>> my windows machine (cross-compiled from 64 bit Linux to 64 bit Windows
>>> using mingw). No clue how and why, but would welcome any suggestions
>>> on why this simple thing might fail.
>>
>> It's possible that Windows doesn't check the pointer against NULL
>> either, and in that case there's not much point in doing it in Wine.
>>
> 
> You're right, but this is such an elementary check that I would not remove it.
> 
>>>
>>>>> +
>>>>> +    var = wcstok(NewEnvironment, delim);
>>>>> +    val = wcstok(NULL, delim);
>>>>
>>>> This isn't thread-safe. [It's not stated anywhere that
>>>> SetEnvironmentStrings() itself is thread-safe, but it costs little to
>>>> assume it is, and moreover this will race with anything else that calls
>>>> wcstok().]
>>>
>>> True, however I had trouble using the reentrant wcstok_s on my system
>>> (i.e., 'twas unavailable :). Is  there an alternative, specific
>>> include? (I tride the expected #include <wchar.h> but that didn't
>>> work).
>>
>> It should probably be added to the headers. I think it goes in string.h
>> and wchar.h, but Jacek might correct me on this.
>>
> 
> Switched to wcschr which is available and working.
> Was trying to include wcstok_s from msvcrt, but without success. What
> would be the proper way to go about this?

I think that Jacek added it in the interim ;-)

That said, you probably shouldn't use it, if tests show that
SetEnvironmentStrings() doesn't modify the argument. (If it does, I
guess that gives you free license.)

> 
>>>
>>>>
>>>>> +
>>>>> +    while (var != NULL) {
>>>>> +        if (FALSE == (rc = SetEnvironmentVariableW(var, val)))
>>>>> +            break;
>>>>> +        var = wcstok(NULL, delim);
>>>>> +        val = wcstok(NULL, delim);
>>>>> +    }
>>>>> +
>>>>> +    return rc;
>>>>> +}
>>>>
>>>> Broadly, this seems surprising; is this really how the function is
>>>> supposed to work?
>>>>
>>>> I think your patch could use more interesting tests. For example:
>>>>
>>>> * Immediately I'd expect GetEnvironmentStrings() to return the exact
>>>> same pointer that SetEnvironmentStrings() set. Is this in fact the case?
>>>> If so, how do A and W variants (of both Get/Set) interact?
>>>>
>>>
>>> Confusing. GetEnvironmentStringsW doesn't return a pointer. Haven't
>>> seen any info on the GetEnvironmentStringsA variant.
>>
>> Er, what? GetEnvironmentStringsA() and GetEnvironmentStringsW() return
>> pointers to char and WCHAR respectively.
>>
> 
> Sorry, misread. However, GetEnvironmentStringsW returns a pointer to a
> new copy of the environment strings with each call. Probably from the
> same source that gets modified by SetEnvironmentStringsW.
> Not really checkable from the outside, I gather.

Okay, so part of that comment was based on the mistaken impression that
GetEnvironmentStrings() returned a pointer to a constant string instead
of allocating a new set every time. Apologies for the confusion that may
have caused.

> 
>>>
>>>> * Are values not present in the call to SetEnvironmentStrings() deleted
>>>> from the environment?
>>>
>>> doc doesn't specify anything about this. I'd expect no.
>>
>> The documentation is often less specific than desired, and also often
>> incorrect; that's why we write tests, and code our implementations
>> against them. It's not obvious whether SetEnvironmentStrings() means
>> "add these several strings to the environment" or "replace the entire
>> environment with these strings", hence the question.
>>
> 
> Yes, on a Windows machine a fresh environment is set up and all the
> previous variables are discarded. In the wine patch now as well.

One thing you'll need to take into consideration, then, is that there
are several environment variables Wine needs to function correctly. In
practice I think this means pretty much anything prefixed with WINE,
though you may want to maintain a manual list.

Are there any others I'm not thinking of?

(Host libraries would presumably need them—and if I'm using e.g.
GST_DEBUG I'd prefer not to have to jump through hoops to make that
work, although if SetEnvironmentStrings() is uncommon I can't complain
too much, and really all I'd need to do is hack
RtlSetCurrentEnvironment() to set that variable afterward. On the one
hand, host libraries would use getenv() anyway, but on the other hand,
if we move to PE files for some of them, this becomes a potential
problem again. All in all, I'm not sure this is a salient enough concern
to warrant doing anything about it...)

> 
>>>
>>>>
>>>> * The PSDK header suggests that SetEnvironmentStrings() should receive a
>>>> doubly null-terminated argument. I'd advise testing with an environment
>>>> containing a null separator (e.g. L"one=two\0three=four\0".)
>>>>
>>>
>>> Done.
>>>
>>>> * You treat '\n' as a separator, but have no tests for it. Also, if both
>>>> space and newline are a separator, what about other whitespace
>>>> characters (tab, carriage return)?
>>>
>>> I have reduced the number of possible separators to just include '='
>>> and conform with the doubly-null terminated string expectation. This
>>> also conforms with the few examples I've found.
>>>
>>>>
>>>> * What about strings that violate the assumptions this implementation
>>>> makes? For example, "one=", "one", "=two", "one=two=three", "one=two
>>>> three=four"...
>>>>
>>>
>>> Included a few more tests.
> 
> Just to note, "one=two=three" is valid and will assign 'two=three' to
> the variable 'one' (on windows).
> one= is also not illegal, AFAICT.
> 
>>>
>>>> * Is this function really supposed to modify its argument? (Yes, it's
>>>> not declared constant, but that doesn't always mean that it will.)
>>>>
>>>
>>> Done, included a buffer that's modified instead.
>>>
>>>
>>>>> +
>>>>> +
>>>>>   /***********************************************************************
>>>>>    * Process/thread attribute lists
>>>>>    ***********************************************************************/
>>>>> diff --git a/include/winbase.h b/include/winbase.h
>>>>> index 655eb48f0f..0c1aa19b42 100644
>>>>> --- a/include/winbase.h
>>>>> +++ b/include/winbase.h
>>>>> @@ -2621,6 +2621,7 @@ WINBASEAPI BOOL        WINAPI SetEndOfFile(HANDLE);
>>>>>   WINBASEAPI BOOL        WINAPI SetEnvironmentVariableA(LPCSTR,LPCSTR);
>>>>>   WINBASEAPI BOOL        WINAPI SetEnvironmentVariableW(LPCWSTR,LPCWSTR);
>>>>>   #define                       SetEnvironmentVariable WINELIB_NAME_AW(SetEnvironmentVariable)
>>>>> +WINBASEAPI BOOL        WINAPI SetEnvironmentStringsW(LPWCH);
>>>>>   WINBASEAPI UINT        WINAPI SetErrorMode(UINT);
>>>>>   WINBASEAPI BOOL        WINAPI SetEvent(HANDLE);
>>>>>   WINBASEAPI VOID        WINAPI SetFileApisToANSI(void);
>>>>>
>>>>
>>>
>>
>>
> 



More information about the wine-devel mailing list