<div dir="ltr"><br><div class="gmail_extra"><div class="gmail_quote">On 7 June 2018 at 11:57, Huw Davies <span dir="ltr"><<a href="mailto:huw@codeweavers.com" target="_blank">huw@codeweavers.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><span>On Wed, Jun 06, 2018 at 08:46:49AM +0100, Rob Walker wrote:<br>
> Fixes: <a href="https://bugs.winehq.org/show_bug.cgi?id=41668" rel="noreferrer" target="_blank">https://bugs.winehq.org/show_b<wbr>ug.cgi?id=41668</a> (1)<br>
>        <a href="https://bugs.winehq.org/show_bug.cgi?id=28216" rel="noreferrer" target="_blank">https://bugs.winehq.org/show_b<wbr>ug.cgi?id=28216</a> (2)<br>
> <br>
> (1) Introduces a "WINESYMLINK" env variable that allows a user to<br>
>     control what shell folder links are created to the HOME folder, when<br>
>     a WINEPREFIX is initially created.<br>
>     The Wineprefix Shell Folders, that Wine symlinks to the user's HOME directory,<br>
>     are only (re-)created on each Wine boot if they: do not pre-exist or are broken<br>
>     symbolic links.<br>
> <br>
> (2) The XDG Directory specification automatically falls back to the user HOME<br>
>     directory for any XDG_*_DIR that does not exist (xdg-user-dirs-update).<br>
>     The implemented solution is to block Wine from symlinking directly<br>
>     to HOME directory.<br>
<br>
</span>Hi Rob,<br>
<br>
There's an awful lot going on in this patch which makes it very<br>
difficult to review.  Please avoid re-naming and re-indenting<br>
existing functions so that we can actually see the real changes.<br>
<br>
Then please split the changes into separate patches.  A clue<br>
that you're not doing this is that you have a list of two<br>
items in the commit message.  If the patch is doing two<br>
things, it should be two patches.<br>
<span class="m_-1476018504731303187HOEnZb"><font color="#888888"><br>
Huw.<br>
</font></span><div class="m_-1476018504731303187HOEnZb"><div class="m_-1476018504731303187h5"><br></div></div></blockquote><div><br></div><div>Hi Huw,</div><div><br></div><div>Thanks for the valuable feedback! I think I spent quite a bit of time fiddling with this,</div><div>so I've probably "lost the wood for the trees" a bit... :-)<br></div><div><br></div><div>Just to clarify a style point, for the re-factored patches...</div><div><br></div><div>If I still want to split the _SHCreateSymbolicLinks() function into a re-factored</div><div>implementation that has an iterative wrapper function, is it OK to call them:</div><div><br></div><div>_SHCreateSymbolicLink()</div><div>_SHCreateSymbolicLinks()<br></div><div><br></div><div>??<br></div><div>This would introduce a new "Camel Case" style function name... But would obviously</div><div>be more consistent...</div><div><br></div><div>Thanks</div><div>Rob<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div class="m_-1476018504731303187HOEnZb"><div class="m_-1476018504731303187h5">
> <br>
> Test on Gentoo GNU/Linux.<br>
> <br>
> Signed-off-by: Rob Walker <<a href="mailto:bob.mt.wya@gmail.com" target="_blank">bob.mt.wya@gmail.com</a>><br>
> ---<br>
>  dlls/shell32/shellpath.c | 554 ++++++++++++++++++++++++------<wbr>---------<br>
>  1 file changed, 343 insertions(+), 211 deletions(-)<br>
> <br>
> diff --git a/dlls/shell32/shellpath.c b/dlls/shell32/shellpath.c<br>
> index a551e93aa8..b8c74273c6 100644<br>
> --- a/dlls/shell32/shellpath.c<br>
> +++ b/dlls/shell32/shellpath.c<br>
> @@ -4326,252 +4326,259 @@ static HRESULT _SHRegisterCommonShellFolders(<wbr>void)<br>
>  }<br>
>  <br>
>  /*****************************<wbr>******************************<wbr>*******************<br>
> - * _SHAppendToUnixPath  [Internal]<br>
> + * append_to_unix_path  [Internal]<br>
>   *<br>
> - * Helper function for _SHCreateSymbolicLinks. Appends pwszSubPath (or the <br>
> - * corresponding resource, if IS_INTRESOURCE) to the unix base path 'szBasePath' <br>
> - * and replaces backslashes with slashes.<br>
> + * Helper function for create_home_subdir_symbolic_li<wbr>nk.<br>
> + * Appends 'subpath', or the corresponding resource (if it is a<br>
> + * resource identifier), to the Unix base path 'base_dir'.<br>
> + * Replace backslashes with forward slashes.<br>
>   *<br>
>   * PARAMS<br>
> - *  szBasePath  [IO] The unix base path, which will be appended to (CP_UNXICP).<br>
> - *  pwszSubPath [I]  Sub-path or resource id (use MAKEINTRESOURCEW).<br>
> + *  base_dir   [IO] The Unix base path, which will be appended to (CP_UNXICP).<br>
> + *  subpath    [I]  Sub-path or resource id (use MAKEINTRESOURCEW, pre-call).<br>
>   *<br>
>   * RETURNS<br>
> - *  Success: TRUE,<br>
> + *  Success: TRUE<br>
>   *  Failure: FALSE<br>
>   */<br>
> -static inline BOOL _SHAppendToUnixPath(char *szBasePath, LPCWSTR pwszSubPath) {<br>
> -    WCHAR wszSubPath[MAX_PATH];<br>
> -    int cLen = strlen(szBasePath);<br>
> -    char *pBackslash;<br>
> -<br>
> -    if (IS_INTRESOURCE(pwszSubPath)) {<br>
> -        if (!LoadStringW(shell32_hInstanc<wbr>e, LOWORD(pwszSubPath), wszSubPath, MAX_PATH)) {<br>
> -            /* Fall back to hard coded defaults. */<br>
> -            switch (LOWORD(pwszSubPath)) {<br>
> -                case IDS_PERSONAL:<br>
> -                    lstrcpyW(wszSubPath, DocumentsW);<br>
> -                    break;<br>
> -                case IDS_MYMUSIC:<br>
> -                    lstrcpyW(wszSubPath, My_MusicW);<br>
> -                    break;<br>
> -                case IDS_MYPICTURES:<br>
> -                    lstrcpyW(wszSubPath, My_PicturesW);<br>
> -                    break;<br>
> -                case IDS_MYVIDEOS:<br>
> -                    lstrcpyW(wszSubPath, My_VideosW);<br>
> -                    break;<br>
> -                default:<br>
> -                    ERR("LoadString(%d) failed!\n", LOWORD(pwszSubPath));<br>
> -                    return FALSE;<br>
> -            }<br>
> +static inline BOOL append_to_unix_path(char *base_dir, LPCWSTR subpath)<br>
> +{<br>
> +    WCHAR ws_subpath[MAX_PATH];<br>
> +    int cLen = strlen(base_dir);<br>
> +    char *back_slash;<br>
> +<br>
> +    if (!IS_INTRESOURCE(subpath))<br>
> +    {<br>
> +        lstrcpyW(ws_subpath, subpath);<br>
> +    }<br>
> +    else if (!LoadStringW(shell32_hInstanc<wbr>e, LOWORD(subpath), ws_subpath, MAX_PATH))<br>
> +    {<br>
> +        /* Fall back to hard coded defaults. */<br>
> +        switch (LOWORD(subpath)) {<br>
> +            case IDS_PERSONAL:<br>
> +                lstrcpyW(ws_subpath, DocumentsW);<br>
> +                break;<br>
> +            case IDS_MYMUSIC:<br>
> +                lstrcpyW(ws_subpath, My_MusicW);<br>
> +                break;<br>
> +            case IDS_MYPICTURES:<br>
> +                lstrcpyW(ws_subpath, My_PicturesW);<br>
> +                break;<br>
> +            case IDS_MYVIDEOS:<br>
> +                lstrcpyW(ws_subpath, My_VideosW);<br>
> +                break;<br>
> +            case IDS_DESKTOP:<br>
> +                lstrcpyW(ws_subpath, DesktopW);<br>
> +                break;<br>
> +            default:<br>
> +                ERR("LoadString(%d) failed!\n", LOWORD(subpath));<br>
> +                return FALSE;<br>
>          }<br>
> -    } else {<br>
> -        lstrcpyW(wszSubPath, pwszSubPath);<br>
>      }<br>
> - <br>
> -    if (szBasePath[cLen-1] != '/') szBasePath[cLen++] = '/';<br>
> - <br>
> -    if (!WideCharToMultiByte(CP_UNIXC<wbr>P, 0, wszSubPath, -1, szBasePath + cLen,<br>
> +<br>
> +    if (!cLen || (base_dir[cLen-1] != '/')) base_dir[cLen++] = '/';<br>
> +<br>
> +    if (!WideCharToMultiByte(CP_UNIXC<wbr>P, 0, ws_subpath, -1, base_dir + cLen,<br>
>                               FILENAME_MAX - cLen, NULL, NULL))<br>
>      {<br>
>          return FALSE;<br>
>      }<br>
> - <br>
> -    pBackslash = szBasePath + cLen;<br>
> -    while ((pBackslash = strchr(pBackslash, '\\'))) *pBackslash = '/';<br>
> - <br>
> +<br>
> +    back_slash = base_dir + cLen;<br>
> +    while ((back_slash = strchr(back_slash, '\\'))) *back_slash = '/';<br>
> +<br>
>      return TRUE;<br>
>  }<br>
>  <br>
>  /*****************************<wbr>******************************<wbr>*******************<br>
> - * _SHCreateSymbolicLinks  [Internal]<br>
> + * compare_pathft_to_bootft  [Internal]<br>
>   * <br>
> - * Sets up symbol links for various shell folders to point into the users home<br>
> - * directory. We do an educated guess about what the user would probably want:<br>
> - * - If there is a 'My Documents' directory in $HOME, the user probably wants<br>
> - *   wine's 'My Documents' to point there. Furthermore, we imply that the user<br>
> - *   is a Windows lover and has no problem with wine creating 'My Pictures',<br>
> - *   'My Music' and 'My Videos' subfolders under '$HOME/My Documents', if those<br>
> - *   do not already exits. We put appropriate symbolic links in place for those,<br>
> - *   too.<br>
> - * - If there is no 'My Documents' directory in $HOME, we let 'My Documents'<br>
> - *   point directly to $HOME. We assume the user to be a unix hacker who does not<br>
> - *   want wine to create anything anywhere besides the .wine directory. So, if<br>
> - *   there already is a 'My Music' directory in $HOME, we symlink the 'My Music'<br>
> - *   shell folder to it. But if not, then we check XDG_MUSIC_DIR - "well known"<br>
> - *   directory, and try to link to that. If that fails, then we symlink to<br>
> - *   $HOME directly. The same holds fo 'My Pictures' and 'My Videos'.<br>
> - * - The Desktop shell folder is symlinked to XDG_DESKTOP_DIR. If that does not<br>
> - *   exist, then we try '$HOME/Desktop'. If that does not exist, then we leave<br>
> - *   it alone.<br>
> - * ('My Music',... above in fact means LoadString(IDS_MYMUSIC))<br>
> + * Function to compare the FILETIME of ws_path (a Windows path) to the (estimated)<br>
> + * Wine System boot time.<br>
> + *<br>
> + * PARAMS<br>
> + *  ws_path         [I]  The Windows path, for which provides the FS write time.<br>
> + *  time_difference [O]   -1 Windows path last written before time of Wine boot.<br>
> + *                         0 Windows path last written at time of Wine boot.<br>
> + *                        +1 Windows path last written after time of Wine boot.<br>
> + * RETURNS<br>
> + *  Success: S_OK<br>
> + *  Failure: E_FAIL<br>
> + * <br>
> + * NOTES<br>
> + *   Pre-allocate storage for time_difference pointer variable externally.<br>
>   */<br>
> -static void _SHCreateSymbolicLinks(void)<br>
> -{<br>
> -    UINT aidsMyStuff[] = { IDS_MYPICTURES, IDS_MYVIDEOS, IDS_MYMUSIC }, i;<br>
> -    const WCHAR* MyOSXStuffW[] = { PicturesW, MoviesW, MusicW };<br>
> -    int acsidlMyStuff[] = { CSIDL_MYPICTURES, CSIDL_MYVIDEO, CSIDL_MYMUSIC };<br>
> -    static const char * const xdg_dirs[] = { "PICTURES", "VIDEOS", "MUSIC", "DOCUMENTS", "DESKTOP" };<br>
> -    static const unsigned int num = ARRAY_SIZE(xdg_dirs);<br>
> -    WCHAR wszTempPath[MAX_PATH];<br>
> -    char szPersonalTarget[FILENAME_MAX]<wbr>, *pszPersonal;<br>
> -    char szMyStuffTarget[FILENAME_MAX], *pszMyStuff;<br>
> -    char szDesktopTarget[FILENAME_MAX], *pszDesktop;<br>
> -    struct stat statFolder;<br>
> -    const char *pszHome;<br>
> -    HRESULT hr;<br>
> -    char ** xdg_results;<br>
> -    char * xdg_desktop_dir;<br>
> -<br>
> -    /* Create all necessary profile sub-dirs up to 'My Documents' and get the unix path. */<br>
> -    hr = SHGetFolderPathW(NULL, CSIDL_PERSONAL|CSIDL_FLAG_CREA<wbr>TE, NULL,<br>
> -                          SHGFP_TYPE_DEFAULT, wszTempPath);<br>
> -    if (FAILED(hr)) return;<br>
> -    pszPersonal = wine_get_unix_file_name(wszTem<wbr>pPath);<br>
> -    if (!pszPersonal) return;<br>
> +static HRESULT compare_pathft_to_bootft(const WCHAR * ws_path, int * time_difference)<br>
> +{<br>
> +    static ULONG64 time_system_start = 0;<br>
> +    SYSTEMTIME system_time_st;<br>
> +    FILETIME dummy_ft, system_time_ft, last_write_ft;<br>
> +    ULONG64 tick_count, time_last_write;<br>
> +    HANDLE fhandle;<br>
> +    DWORD ft_errorc = 0;<br>
>  <br>
> -    hr = XDG_UserDirLookup(xdg_dirs, num, &xdg_results);<br>
> -    if (FAILED(hr)) xdg_results = NULL;<br>
> +    if (!time_difference) return E_FAIL;<br>
>  <br>
> -    pszHome = getenv("HOME");<br>
> -    if (pszHome && !stat(pszHome, &statFolder) && S_ISDIR(statFolder.st_mode))<br>
> +    if (!time_system_start)<br>
>      {<br>
> -        while (1)<br>
> +        tick_count = (ULONG64) GetTickCount64();<br>
> +        GetSystemTime(&system_time_st)<wbr>;<br>
> +        if (!SystemTimeToFileTime( &system_time_st, &system_time_ft))<br>
>          {<br>
> -            /* Check if there's already a Wine-specific 'My Documents' folder */<br>
> -            strcpy(szPersonalTarget, pszHome);<br>
> -            if (_SHAppendToUnixPath(szPersona<wbr>lTarget, MAKEINTRESOURCEW(IDS_PERSONAL)<wbr>) &&<br>
> -                !stat(szPersonalTarget, &statFolder) && S_ISDIR(statFolder.st_mode))<br>
> -            {<br>
> -                /* '$HOME/My Documents' exists. Create 'My Pictures',<br>
> -                 * 'My Videos' and 'My Music' subfolders or fail silently if<br>
> -                 * they already exist.<br>
> -                 */<br>
> -                for (i = 0; i < ARRAY_SIZE(aidsMyStuff); i++)<br>
> -                {<br>
> -                    strcpy(szMyStuffTarget, szPersonalTarget);<br>
> -                    if (_SHAppendToUnixPath(szMyStuff<wbr>Target, MAKEINTRESOURCEW(aidsMyStuff[i<wbr>])))<br>
> -                        mkdir(szMyStuffTarget, 0777);<br>
> -                }<br>
> -                break;<br>
> -            }<br>
> -<br>
> -            /* Try to point to the XDG Documents folder */<br>
> -            if (xdg_results && xdg_results[num-2] &&<br>
> -               !stat(xdg_results[num-2], &statFolder) &&<br>
> -               S_ISDIR(statFolder.st_mode))<br>
> -            {<br>
> -                strcpy(szPersonalTarget, xdg_results[num-2]);<br>
> -                break;<br>
> -            }<br>
> -<br>
> -            /* Or the hardcoded / OS X Documents folder */<br>
> -            strcpy(szPersonalTarget, pszHome);<br>
> -            if (_SHAppendToUnixPath(szPersona<wbr>lTarget, DocumentsW) &&<br>
> -               !stat(szPersonalTarget, &statFolder) &&<br>
> -               S_ISDIR(statFolder.st_mode))<br>
> -                break;<br>
> -<br>
> -            /* As a last resort point to $HOME. */<br>
> -            strcpy(szPersonalTarget, pszHome);<br>
> -            break;<br>
> +            ERR("SystemTimeToFileTime call failed (%d)\n", GetLastError());<br>
> +            return E_FAIL;<br>
>          }<br>
> -<br>
> -        /* Replace 'My Documents' directory with a symlink or fail silently if not empty. */<br>
> -        remove(pszPersonal);<br>
> -        symlink(szPersonalTarget, pszPersonal);<br>
> +        time_system_start = (((ULONG64)system_time_ft.dwHi<wbr>ghDateTime) << 32)<br>
> +                            + system_time_ft.dwLowDateTime<br>
> +                            - (tick_count * 10000);<br>
>      }<br>
> -    else<br>
> +    fhandle = CreateFileW( ws_path, 0, FILE_SHARE_READ, NULL,<br>
> +                        OPEN_EXISTING,  FILE_FLAG_BACKUP_SEMANTICS, NULL);<br>
> +    if (fhandle == INVALID_HANDLE_VALUE)<br>
>      {<br>
> -        /* '$HOME' doesn't exist. Create 'My Pictures', 'My Videos' and 'My Music' subdirs<br>
> -         * in '%USERPROFILE%\\My Documents' or fail silently if they already exist. */<br>
> -        pszHome = NULL;<br>
> -        strcpy(szPersonalTarget, pszPersonal);<br>
> -        for (i = 0; i < ARRAY_SIZE(aidsMyStuff); i++) {<br>
> -            strcpy(szMyStuffTarget, szPersonalTarget);<br>
> -            if (_SHAppendToUnixPath(szMyStuff<wbr>Target, MAKEINTRESOURCEW(aidsMyStuff[i<wbr>])))<br>
> -                mkdir(szMyStuffTarget, 0777);<br>
> -        }<br>
> +        ERR("Open file handle failed for path: %s (%d)\n",<br>
> +            debugstr_w(ws_path), GetLastError());<br>
> +        return E_FAIL;<br>
>      }<br>
> -<br>
> -    /* Create symbolic links for 'My Pictures', 'My Videos' and 'My Music'. */<br>
> -    for (i=0; i < ARRAY_SIZE(aidsMyStuff); i++)<br>
> +    if (!GetFileTime(fhandle, &dummy_ft, &dummy_ft, &last_write_ft))<br>
> +        ft_errorc = GetLastError();<br>
> +    if (!CloseHandle(fhandle))<br>
>      {<br>
> -        /* Create the current 'My Whatever' folder and get its unix path. */<br>
> -        hr = SHGetFolderPathW(NULL, acsidlMyStuff[i]|CSIDL_FLAG_CR<wbr>EATE, NULL,<br>
> -                              SHGFP_TYPE_DEFAULT, wszTempPath);<br>
> -        if (FAILED(hr)) continue;<br>
> -<br>
> -        pszMyStuff = wine_get_unix_file_name(wszTem<wbr>pPath);<br>
> -        if (!pszMyStuff) continue;<br>
> -        <br>
> -        while (1)<br>
> -        {<br>
> -            /* Check for the Wine-specific '$HOME/My Documents' subfolder */<br>
> -            strcpy(szMyStuffTarget, szPersonalTarget);<br>
> -            if (_SHAppendToUnixPath(szMyStuff<wbr>Target, MAKEINTRESOURCEW(aidsMyStuff[i<wbr>])) &&<br>
> -                !stat(szMyStuffTarget, &statFolder) && S_ISDIR(statFolder.st_mode))<br>
> -                break;<br>
> +        ERR("Close file handle failed: %p (%d)\n",<br>
> +            fhandle, GetLastError());<br>
> +        return E_FAIL;<br>
> +    }<br>
> +    if (ft_errorc)<br>
> +    {<br>
> +        ERR("Get file time failed: %p (%d)\n",<br>
> +            fhandle, ft_errorc);<br>
> +        return E_FAIL;<br>
> +    }<br>
> +    time_last_write = (((ULONG64)last_write_ft.dwHig<wbr>hDateTime) << 32)<br>
> +                      + last_write_ft.dwLowDateTime;<br>
> +    *time_difference = ((time_last_write > time_system_start) ?  1 :<br>
> +                       ((time_last_write < time_system_start) ? -1 :<br>
> +                                                                 0));<br>
>  <br>
> -            /* Try the XDG_XXX_DIR folder */<br>
> -            if (xdg_results && xdg_results[i])<br>
> -            {<br>
> -                strcpy(szMyStuffTarget, xdg_results[i]);<br>
> -                break;<br>
> -            }<br>
> +    return S_OK;<br>
> +}<br>
>  <br>
> -            /* Or the OS X folder (these are never localized) */<br>
> -            if (pszHome)<br>
> -            {<br>
> -                strcpy(szMyStuffTarget, pszHome);<br>
> -                if (_SHAppendToUnixPath(szMyStuff<wbr>Target, MyOSXStuffW[i]) &&<br>
> -                    !stat(szMyStuffTarget, &statFolder) &&<br>
> -                    S_ISDIR(statFolder.st_mode))<br>
> -                    break;<br>
> -            }<br>
> +/****************************<wbr>******************************<wbr>********************<br>
> + * create_homedir_symbolic_link  [Internal]<br>
> + *<br>
> + * Creates a symbolic link from the current Wineprefix to an appropriate<br>
> + * HOME subdirectory (if one is found).<br>
> + *<br>
> + *  Creates 'XXXX' directory in Wineprefix.<br>
> + *  Then create a 'My XXXX' symbolic link in Wineprefix:<br>
> + *  1) If '$HOME/XXXX' (IDS directory) exists then target this.<br>
> + *  2) If '$HOME/XXXX' (XDG_XXXX_DIR) exists then target this.<br>
> + *  3) If '$HOME/XXXX' (MacOS XXXX media directory) exists then target this.<br>
> + * <br>
> + *  PARAMS<br>
> + *  env_enabled [I]<br>
> + *         TRUE      The user has enabled this Shell Folder to be symlinked (default).<br>
> + *        FALSE      The user has chosen to disable symlinking this Shell Folder.<br>
> + *                   The Shell Folder will be created, in the current Wineprefix (if not pre-existing).<br>
> + *  ids_dir     [I]  Windows Resource Identifier code for current Shell Folder.<br>
> + *  csidl_dir   [I]  Constant Special Item ID List identifier for current Shell Folder.<br>
> + *  xdg_dir     [I]  Full path of external Unix XDG directory corresponding to current Shell Folder.<br>
> + *  ws_osx_dir  [I]  Fallback directory name to use, corresponding to current Shell Folder (OSX specific).<br>
> + * <br>
> + */<br>
> +void create_homedir_symbolic_link(B<wbr>OOL env_enabled,<br>
> +                                  UINT ids_dir,<br>
> +                                  int csidl_dir,<br>
> +                                  const char * xdg_dir,<br>
> +                                  const WCHAR * ws_osx_dir)<br>
> +{<br>
> +    static const char * env_homedir = NULL;<br>
> +    WCHAR ws_temp_path[MAX_PATH];<br>
> +    char home_target[FILENAME_MAX], * prefix_dir;<br>
> +    struct stat stat_folder, stat_home_folder;<br>
> +    HRESULT hr;<br>
> +    BOOL target_ok;<br>
> +    int time_difference;<br>
>  <br>
> -            /* As a last resort point to the same location as 'My Documents' */<br>
> -            strcpy(szMyStuffTarget, szPersonalTarget);<br>
> -            break;<br>
> +    hr = SHGetFolderPathW(NULL, csidl_dir, NULL,<br>
> +                SHGFP_TYPE_DEFAULT, ws_temp_path);<br>
> +    if (SUCCEEDED(hr))<br>
> +    {<br>
> +        if (ids_dir != IDS_DESKTOPDIRECTORY) return;<br>
> +<br>
> +        if ((compare_pathft_to_bootft(ws_<wbr>temp_path, &time_difference) == S_OK)<br>
> +            && (time_difference < 0))<br>
> +        {<br>
> +            TRACE("%s directory created before wineboot\n",<br>
> +                  debugstr_w(ws_temp_path));<br>
> +            return;<br>
>          }<br>
> -        remove(pszMyStuff);<br>
> -        symlink(szMyStuffTarget, pszMyStuff);<br>
> -        heap_free(pszMyStuff);<br>
>      }<br>
> -<br>
> -    /* Last but not least, the Desktop folder */<br>
> -    if (pszHome)<br>
> -        strcpy(szDesktopTarget, pszHome);<br>
> +    else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_<wbr>NOT_FOUND))<br>
> +    {<br>
> +        hr = SHGetFolderPathW(NULL, csidl_dir|CSIDL_FLAG_CREATE, NULL,<br>
> +                            SHGFP_TYPE_DEFAULT, ws_temp_path);<br>
> +        if (FAILED(hr)) return;<br>
> +    }<br>
>      else<br>
> -        strcpy(szDesktopTarget, pszPersonal);<br>
> -    heap_free(pszPersonal);<br>
> +    {<br>
> +        ERR("Failed to get Wineprefix path corresponding to %d CSIDL\n",<br>
> +            csidl_dir);<br>
> +        return;<br>
> +    }<br>
> +    if (!env_enabled) return;<br>
> +    prefix_dir = wine_get_unix_file_name(ws_tem<wbr>p_path);<br>
> +    if (!prefix_dir)<br>
> +    {<br>
> +        ERR("Failed to get Unix Wineprefix directory for: %s\n",<br>
> +            debugstr_w(ws_temp_path));<br>
> +        return;<br>
> +    }<br>
> +    if (!env_homedir) env_homedir = getenv("HOME");<br>
> +    if (!(env_homedir<br>
> +          && (stat(env_homedir, &stat_home_folder) != 1)<br>
> +          && S_ISDIR(stat_home_folder.st_mo<wbr>de)))<br>
> +    {<br>
> +        if (prefix_dir) heap_free(prefix_dir);<br>
> +        return;<br>
> +    }<br>
>  <br>
> -    xdg_desktop_dir = xdg_results ? xdg_results[num - 1] : NULL;<br>
> -    if (xdg_desktop_dir ||<br>
> -        (_SHAppendToUnixPath(szDesktop<wbr>Target, DesktopW) &&<br>
> -        !stat(szDesktopTarget, &statFolder) && S_ISDIR(statFolder.st_mode)))<br>
> +    /* Check for:<br>
> +     *    '$HOME/XXXX' (IDS directory)<br>
> +     * or '$HOME/XXXX' (XDG directory)<br>
> +     * or '$HOME/XXXX' (MacOS directory)<br>
> +     */<br>
> +    target_ok = FALSE;<br>
> +    strcpy(home_target, env_homedir);<br>
> +    if (append_to_unix_path(home_targ<wbr>et, MAKEINTRESOURCEW(ids_dir))<br>
> +        && (stat(home_target, &stat_folder) != -1)<br>
> +        && S_ISDIR(stat_folder.st_mode))<br>
>      {<br>
> -        hr = SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY|CSIDL_F<wbr>LAG_CREATE, NULL,<br>
> -                              SHGFP_TYPE_DEFAULT, wszTempPath);<br>
> -        if (SUCCEEDED(hr) && (pszDesktop = wine_get_unix_file_name(wszTem<wbr>pPath))) <br>
> -        {<br>
> -            remove(pszDesktop);<br>
> -            if (xdg_desktop_dir)<br>
> -                symlink(xdg_desktop_dir, pszDesktop);<br>
> -            else<br>
> -                symlink(szDesktopTarget, pszDesktop);<br>
> -            heap_free(pszDesktop);<br>
> -        }<br>
> +        target_ok = TRUE;<br>
> +    }<br>
> +    else if (xdg_dir && stat(xdg_dir, &stat_folder))<br>
> +    {<br>
> +        strcpy(home_target, xdg_dir);<br>
> +        /* Only link to the XDG directory, if it does not point directly<br>
> +         * to the user's HOME directory (XDG specification fallback path). */<br>
> +        target_ok = (stat_folder.st_ino != stat_home_folder.st_ino);<br>
> +    }<br>
> +    else<br>
> +    {<br>
> +        strcpy(home_target, env_homedir);<br>
> +        target_ok = append_to_unix_path(home_targe<wbr>t, ws_osx_dir)<br>
> +                    && (stat(home_target, &stat_folder) != -1)<br>
> +                    && S_ISDIR(stat_folder.st_mode);<br>
>      }<br>
>  <br>
> -    /* Free resources allocated by XDG_UserDirLookup() */<br>
> -    if (xdg_results)<br>
> +    if (target_ok)<br>
>      {<br>
> -        for (i = 0; i < num; i++)<br>
> -            heap_free(xdg_results[i]);<br>
> -        heap_free(xdg_results);<br>
> +        remove(prefix_dir);<br>
> +        symlink(home_target, prefix_dir);<br>
>      }<br>
> +<br>
> +<br>
> +    if (prefix_dir) heap_free(prefix_dir);<br>
>  }<br>
>  <br>
>  /*****************************<wbr>******************************<wbr>*******************<br>
> @@ -6126,15 +6133,139 @@ static void register_system_knownfolders(v<wbr>oid)<br>
>      }<br>
>  }<br>
>  <br>
> +/****************************<wbr>******************************<wbr>********************<br>
> + * parse_symlinks_env_variable  [Internal]<br>
> + * <br>
> + * PARAMS<br>
> + *  xdg_dir     [I]  Unix XDG directory name (without "XDG_" prefix and "_DIR" suffix).<br>
> + * <br>
> + * RETURNS<br>
> + *  TRUE   "WINESYMLINK" contains the specified XDG directory string.<br>
> + *  FALSE  "WINESYMLINK" does not contain the specified XDG directory string.<br>
> + *         Any error condition.<br>
> + * <br>
> + * Note: "WINESYMLINK", defaults to "ALL", when unset. This enables all symlinks.<br>
> + *       "WINESYMLINK" can also be manually set to "ALL", enabling all symlinks.<br>
> + * <br>
> + */<br>
> +static BOOL parse_symlinks_env_variable(co<wbr>nst char * xdg_dir)<br>
> +{<br>
> +    static const char * matchall_env_var = "ALL";<br>
> +    static char env_var[MAX_PATH+1];<br>
> +    static char * env_winesymlink = NULL;<br>
> +    char * sstr, * sstr_stop, * tstr, * word_start = NULL;<br>
> +    char seperator_ch = '\0';<br>
> +    BOOL end_string, in_word = FALSE, matched = FALSE;<br>
> +    size_t slength = 0, matchall_length, xdg_dir_slength;<br>
> +<br>
> +    if (!xdg_dir) return matched;<br>
> +<br>
> +    if (!env_winesymlink)<br>
> +    {<br>
> +        env_winesymlink = getenv("WINESYMLINK");<br>
> +        if (!env_winesymlink) env_winesymlink = (char*) matchall_env_var;<br>
> +        tstr = env_var;<br>
> +        sstr_stop = env_winesymlink + MAX_PATH;<br>
> +        for (sstr = env_winesymlink; *sstr && (sstr != sstr_stop); ++sstr)<br>
> +        {<br>
> +            if (!seperator_ch && (ispunct(*sstr) || isspace(*sstr)))<br>
> +                seperator_ch = *sstr;<br>
> +            if (!isalpha(*sstr)) continue;<br>
> +<br>
> +            if (seperator_ch && in_word)<br>
> +            {<br>
> +                *tstr++ = seperator_ch;<br>
> +                ++slength;<br>
> +            }<br>
> +            in_word = TRUE;<br>
> +            seperator_ch = '\0';<br>
> +            *tstr++ = toupper(*sstr);<br>
> +            ++slength;<br>
> +        }<br>
> +        env_var[slength] = '\0';<br>
> +    }<br>
> +<br>
> +    TRACE("processed env variable: %s\n", debugstr_a(&env_var[0]));<br>
> +    matchall_length = strlen(matchall_env_var);<br>
> +    xdg_dir_slength = strlen(xdg_dir);<br>
> +    end_string = !(env_var[0]);<br>
> +    for (sstr = &env_var[0]; !end_string; ++sstr)<br>
> +    {<br>
> +        end_string = !(*(sstr+1));<br>
> +        in_word = isalpha(*sstr);<br>
> +        if (in_word && !word_start) word_start = sstr;<br>
> +        in_word = in_word && !end_string;<br>
> +        if (in_word || !word_start) continue;<br>
> +<br>
> +        slength = ((size_t) (sstr-word_start))+(end_string ? 1 : 0);<br>
> +        matched = ( (slength == xdg_dir_slength) && (strncmp(word_start, xdg_dir, slength) == 0) )<br>
> +               || ( (slength == matchall_length) && (strncmp(word_start, "ALL",   slength) == 0) );<br>
> +        if (matched) break;<br>
> +<br>
> +        word_start = NULL;<br>
> +    }<br>
> +    TRACE("%s symlinking for %s\n", matched ? "Enabled" : "Disabled", debugstr_a(xdg_dir));<br>
> +<br>
> +    return matched;<br>
> +}<br>
> +<br>
> +/****************************<wbr>******************************<wbr>********************<br>
> + * create_homedir_symbolic_links  [Internal]<br>
> + * <br>
> + * Parse WINESYMLINK env variable, for each XDG directory argument. To test if symlinking is<br>
> + * enabled for that XDG directory / Wine Profile Folder.<br>
> + * Then calls the function create_homedir_symbolic_link to potentially symlink from a Shell Folder,<br>
> + * in the current Wineprefix, to a subdirectory of the current user's HOME directory.<br>
> + *<br>
> + * PARAMS<br>
> + *  xdg_dirnames   [I]  Pointer to an array of Unix XDG directory names<br>
> + *                      (without "XDG_" prefix and "_DIR" suffix).<br>
> + *  xdg_dir_count  [I]  Item count of array (above).<br>
> + * <br>
> + */<br>
> +static void create_homedir_symbolic_links(<wbr>const char * const xdg_dirnames[], const UINT xdg_dir_count)<br>
> +{<br>
> +    char ** xdg_dirs_array;<br>
> +    char * xdg_dir;<br>
> +    HRESULT hr;<br>
> +    UINT i;<br>
> +    BOOL env_enabled;<br>
> +<br>
> +    if (!xdg_dirnames) return;<br>
> +<br>
> +    hr = XDG_UserDirLookup(xdg_dirnames<wbr>, xdg_dir_count, &xdg_dirs_array);<br>
> +    if (FAILED(hr)) xdg_dirs_array = NULL;<br>
> +<br>
> +    for (i = 0; i < xdg_dir_count; ++i)<br>
> +    {<br>
> +        env_enabled = parse_symlinks_env_variable(xd<wbr>g_dirnames[i]);<br>
> +        xdg_dir = xdg_dirs_array ? xdg_dirs_array[i] : NULL;<br>
> +        if (!strcmp(xdg_dirnames[i],"DOCU<wbr>MENTS"))<br>
> +            create_homedir_symbolic_link(e<wbr>nv_enabled, IDS_PERSONAL, CSIDL_PERSONAL, xdg_dir, DocumentsW);<br>
> +        else if (!strcmp(xdg_dirnames[i],"PICT<wbr>URES"))<br>
> +            create_homedir_symbolic_link(e<wbr>nv_enabled, IDS_MYPICTURES, CSIDL_MYPICTURES, xdg_dir, PicturesW);<br>
> +        else if (!strcmp(xdg_dirnames[i],"VIDE<wbr>OS"))<br>
> +            create_homedir_symbolic_link(e<wbr>nv_enabled, IDS_MYVIDEOS, CSIDL_MYVIDEO, xdg_dir, MoviesW);<br>
> +        else if (!strcmp(xdg_dirnames[i],"MUSI<wbr>C"))<br>
> +            create_homedir_symbolic_link(e<wbr>nv_enabled, IDS_MYMUSIC, CSIDL_MYMUSIC, xdg_dir, MusicW);<br>
> +        else if (!strcmp(xdg_dirnames[i],"DESK<wbr>TOP"))<br>
> +            create_homedir_symbolic_link(e<wbr>nv_enabled, IDS_DESKTOPDIRECTORY, CSIDL_DESKTOPDIRECTORY, xdg_dir, DesktopW);<br>
> +        else<br>
> +            ERR("XDG directory name specifier invalid: %s\n", debugstr_a(xdg_dirnames[i]));<br>
> +        if (xdg_dir) heap_free(xdg_dirs_array[i]);<br>
> +    }<br>
> +    if (xdg_dirs_array) heap_free(xdg_dirs_array);<br>
> +}<br>
> +<br>
>  HRESULT SHELL_RegisterShellFolders(voi<wbr>d)<br>
>  {<br>
> +    static const char * const xdg_dirnames[] = { "DOCUMENTS", "PICTURES", "VIDEOS", "MUSIC", "DESKTOP" };<br>
> +    const UINT xdg_dir_count = 5;<br>
>      HRESULT hr;<br>
>  <br>
> -    /* Set up '$HOME' targeted symlinks for 'My Documents', 'My Pictures',<br>
> -     * 'My Videos', 'My Music' and 'Desktop' in advance, so that the<br>
> -     * _SHRegister*ShellFolders() functions will find everything nice and clean<br>
> -     * and thus will not attempt to create them in the profile directory. */<br>
> -    _SHCreateSymbolicLinks();<br>
> +    /* Early setup of symlinks from specific User Shell Folders, in<br>
> +     * current Wineprefix, to subdirecties of the user's HOME directory. */<br>
> +    create_homedir_symbolic_links(<wbr>xdg_dirnames, xdg_dir_count);<br>
>  <br>
>      hr = _SHRegisterUserShellFolders(TR<wbr>UE);<br>
>      if (SUCCEEDED(hr))<br>
> @@ -6147,5 +6278,6 @@ HRESULT SHELL_RegisterShellFolders(voi<wbr>d)<br>
>          hr = set_folder_attributes();<br>
>      if (SUCCEEDED(hr))<br>
>          register_system_knownfolders()<wbr>;<br>
> +<br>
>      return hr;<br>
>  }<br>
> -- <br>
> 2.17.1<br>
> <br>
> <br>
> <br>
</div></div></blockquote></div><br></div></div>