On 7 June 2018 at 11:57, Huw Davies <huw@codeweavers.com> wrote:
On Wed, Jun 06, 2018 at 08:46:49AM +0100, Rob Walker wrote:
> Fixes: https://bugs.winehq.org/show_bug.cgi?id=41668 (1)
>������ ������ ������ ������ https://bugs.winehq.org/show_bug.cgi?id=28216 (2)
>
> (1) Introduces a "WINESYMLINK" env variable that allows a user to
>������ ������ ������control what shell folder links are created to the HOME folder, when
>������ ������ ������a WINEPREFIX is initially created.
>������ ������ ������The Wineprefix Shell Folders, that Wine symlinks to the user's HOME directory,
>������ ������ ������are only (re-)created on each Wine boot if they: do not pre-exist or are broken
>������ ������ ������symbolic links.
>
> (2) The XDG Directory specification automatically falls back to the user HOME
>������ ������ ������directory for any XDG_*_DIR that does not exist (xdg-user-dirs-update).
>������ ������ ������The implemented solution is to block Wine from symlinking directly
>������ ������ ������to HOME directory.

Hi Rob,

There's an awful lot going on in this patch which makes it very
difficult to review.������ Please avoid re-naming and re-indenting
existing functions so that we can actually see the real changes.

Then please split the changes into separate patches.������ A clue
that you're not doing this is that you have a list of two
items in the commit message.������ If the patch is doing two
things, it should be two patches.

Huw.


Hi Huw,

Thanks for the valuable feedback! I think I spent quite a bit of time fiddling with this,
so I've probably "lost the wood for the trees" a bit... :-)

Just to clarify a style point, for the re-factored patches...

If I still want to split the _SHCreateSymbolicLinks() function into a re-factored
implementation that has an iterative wrapper function, is it OK to call them:

_SHCreateSymbolicLink()
_SHCreateSymbolicLinks()

??
This would introduce a new "Camel Case" style function name... But would obviously
be more consistent...

Thanks
Rob
������
>
> Test on Gentoo GNU/Linux.
>
> Signed-off-by: Rob Walker <bob.mt.wya@gmail.com>
> ---
>������ dlls/shell32/shellpath.c | 554 ++++++++++++++++++++++++---------------
>������ 1 file changed, 343 insertions(+), 211 deletions(-)
>
> diff --git a/dlls/shell32/shellpath.c b/dlls/shell32/shellpath.c
> index a551e93aa8..b8c74273c6 100644
> --- a/dlls/shell32/shellpath.c
> +++ b/dlls/shell32/shellpath.c
> @@ -4326,252 +4326,259 @@ static HRESULT _SHRegisterCommonShellFolders(void)
>������ }
>������
>������ /******************************************************************************
> - * _SHAppendToUnixPath������ [Internal]
> + * append_to_unix_path������ [Internal]
>������ ������*
> - * Helper function for _SHCreateSymbolicLinks. Appends pwszSubPath (or the
> - * corresponding resource, if IS_INTRESOURCE) to the unix base path 'szBasePath'
> - * and replaces backslashes with slashes.
> + * Helper function for create_home_subdir_symbolic_link.
> + * Appends 'subpath', or the corresponding resource (if it is a
> + * resource identifier), to the Unix base path 'base_dir'.
> + * Replace backslashes with forward slashes.
>������ ������*
>������ ������* PARAMS
> - *������ szBasePath������ [IO] The unix base path, which will be appended to (CP_UNXICP).
> - *������ pwszSubPath [I]������ Sub-path or resource id (use MAKEINTRESOURCEW).
> + *������ base_dir������ ������[IO] The Unix base path, which will be appended to (CP_UNXICP).
> + *������ subpath������ ������ [I]������ Sub-path or resource id (use MAKEINTRESOURCEW, pre-call).
>������ ������*
>������ ������* RETURNS
> - *������ Success: TRUE,
> + *������ Success: TRUE
>������ ������*������ Failure: FALSE
>������ ������*/
> -static inline BOOL _SHAppendToUnixPath(char *szBasePath, LPCWSTR pwszSubPath) {
> -������ ������ WCHAR wszSubPath[MAX_PATH];
> -������ ������ int cLen = strlen(szBasePath);
> -������ ������ char *pBackslash;
> -
> -������ ������ if (IS_INTRESOURCE(pwszSubPath)) {
> -������ ������ ������ ������ if (!LoadStringW(shell32_hInstance, LOWORD(pwszSubPath), wszSubPath, MAX_PATH)) {
> -������ ������ ������ ������ ������ ������ /* Fall back to hard coded defaults. */
> -������ ������ ������ ������ ������ ������ switch (LOWORD(pwszSubPath)) {
> -������ ������ ������ ������ ������ ������ ������ ������ case IDS_PERSONAL:
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(wszSubPath, DocumentsW);
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ break;
> -������ ������ ������ ������ ������ ������ ������ ������ case IDS_MYMUSIC:
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(wszSubPath, My_MusicW);
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ break;
> -������ ������ ������ ������ ������ ������ ������ ������ case IDS_MYPICTURES:
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(wszSubPath, My_PicturesW);
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ break;
> -������ ������ ������ ������ ������ ������ ������ ������ case IDS_MYVIDEOS:
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(wszSubPath, My_VideosW);
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ break;
> -������ ������ ������ ������ ������ ������ ������ ������ default:
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ERR("LoadString(%d) failed!\n", LOWORD(pwszSubPath));
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ return FALSE;
> -������ ������ ������ ������ ������ ������ }
> +static inline BOOL append_to_unix_path(char *base_dir, LPCWSTR subpath)
> +{
> +������ ������ WCHAR ws_subpath[MAX_PATH];
> +������ ������ int cLen = strlen(base_dir);
> +������ ������ char *back_slash;
> +
> +������ ������ if (!IS_INTRESOURCE(subpath))
> +������ ������ {
> +������ ������ ������ ������ lstrcpyW(ws_subpath, subpath);
> +������ ������ }
> +������ ������ else if (!LoadStringW(shell32_hInstance, LOWORD(subpath), ws_subpath, MAX_PATH))
> +������ ������ {
> +������ ������ ������ ������ /* Fall back to hard coded defaults. */
> +������ ������ ������ ������ switch (LOWORD(subpath)) {
> +������ ������ ������ ������ ������ ������ case IDS_PERSONAL:
> +������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(ws_subpath, DocumentsW);
> +������ ������ ������ ������ ������ ������ ������ ������ break;
> +������ ������ ������ ������ ������ ������ case IDS_MYMUSIC:
> +������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(ws_subpath, My_MusicW);
> +������ ������ ������ ������ ������ ������ ������ ������ break;
> +������ ������ ������ ������ ������ ������ case IDS_MYPICTURES:
> +������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(ws_subpath, My_PicturesW);
> +������ ������ ������ ������ ������ ������ ������ ������ break;
> +������ ������ ������ ������ ������ ������ case IDS_MYVIDEOS:
> +������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(ws_subpath, My_VideosW);
> +������ ������ ������ ������ ������ ������ ������ ������ break;
> +������ ������ ������ ������ ������ ������ case IDS_DESKTOP:
> +������ ������ ������ ������ ������ ������ ������ ������ lstrcpyW(ws_subpath, DesktopW);
> +������ ������ ������ ������ ������ ������ ������ ������ break;
> +������ ������ ������ ������ ������ ������ default:
> +������ ������ ������ ������ ������ ������ ������ ������ ERR("LoadString(%d) failed!\n", LOWORD(subpath));
> +������ ������ ������ ������ ������ ������ ������ ������ return FALSE;
>������ ������ ������ ������ ������ }
> -������ ������ } else {
> -������ ������ ������ ������ lstrcpyW(wszSubPath, pwszSubPath);
>������ ������ ������ }
> -
> -������ ������ if (szBasePath[cLen-1] != '/') szBasePath[cLen++] = '/';
> -
> -������ ������ if (!WideCharToMultiByte(CP_UNIXCP, 0, wszSubPath, -1, szBasePath + cLen,
> +
> +������ ������ if (!cLen || (base_dir[cLen-1] != '/')) base_dir[cLen++] = '/';
> +
> +������ ������ if (!WideCharToMultiByte(CP_UNIXCP, 0, ws_subpath, -1, base_dir + cLen,
>������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������FILENAME_MAX - cLen, NULL, NULL))
>������ ������ ������ {
>������ ������ ������ ������ ������ return FALSE;
>������ ������ ������ }
> -
> -������ ������ pBackslash = szBasePath + cLen;
> -������ ������ while ((pBackslash = strchr(pBackslash, '\\'))) *pBackslash = '/';
> -
> +
> +������ ������ back_slash = base_dir + cLen;
> +������ ������ while ((back_slash = strchr(back_slash, '\\'))) *back_slash = '/';
> +
>������ ������ ������ return TRUE;
>������ }
>������
>������ /******************************************************************************
> - * _SHCreateSymbolicLinks������ [Internal]
> + * compare_pathft_to_bootft������ [Internal]
>������ ������*
> - * Sets up symbol links for various shell folders to point into the users home
> - * directory. We do an educated guess about what the user would probably want:
> - * - If there is a 'My Documents' directory in $HOME, the user probably wants
> - *������ ������wine's 'My Documents' to point there. Furthermore, we imply that the user
> - *������ ������is a Windows lover and has no problem with wine creating 'My Pictures',
> - *������ ������'My Music' and 'My Videos' subfolders under '$HOME/My Documents', if those
> - *������ ������do not already exits. We put appropriate symbolic links in place for those,
> - *������ ������too.
> - * - If there is no 'My Documents' directory in $HOME, we let 'My Documents'
> - *������ ������point directly to $HOME. We assume the user to be a unix hacker who does not
> - *������ ������want wine to create anything anywhere besides the .wine directory. So, if
> - *������ ������there already is a 'My Music' directory in $HOME, we symlink the 'My Music'
> - *������ ������shell folder to it. But if not, then we check XDG_MUSIC_DIR - "well known"
> - *������ ������directory, and try to link to that. If that fails, then we symlink to
> - *������ ������$HOME directly. The same holds fo 'My Pictures' and 'My Videos'.
> - * - The Desktop shell folder is symlinked to XDG_DESKTOP_DIR. If that does not
> - *������ ������exist, then we try '$HOME/Desktop'. If that does not exist, then we leave
> - *������ ������it alone.
> - * ('My Music',... above in fact means LoadString(IDS_MYMUSIC))
> + * Function to compare the FILETIME of ws_path (a Windows path) to the (estimated)
> + * Wine System boot time.
> + *
> + * PARAMS
> + *������ ws_path������ ������ ������ ������ ������[I]������ The Windows path, for which provides the FS write time.
> + *������ time_difference [O]������ ������-1 Windows path last written before time of Wine boot.
> + *������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������0 Windows path last written at time of Wine boot.
> + *������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ +1 Windows path last written after time of Wine boot.
> + * RETURNS
> + *������ Success: S_OK
> + *������ Failure: E_FAIL
> + *
> + * NOTES
> + *������ ������Pre-allocate storage for time_difference pointer variable externally.
>������ ������*/
> -static void _SHCreateSymbolicLinks(void)
> -{
> -������ ������ UINT aidsMyStuff[] = { IDS_MYPICTURES, IDS_MYVIDEOS, IDS_MYMUSIC }, i;
> -������ ������ const WCHAR* MyOSXStuffW[] = { PicturesW, MoviesW, MusicW };
> -������ ������ int acsidlMyStuff[] = { CSIDL_MYPICTURES, CSIDL_MYVIDEO, CSIDL_MYMUSIC };
> -������ ������ static const char * const xdg_dirs[] = { "PICTURES", "VIDEOS", "MUSIC", "DOCUMENTS", "DESKTOP" };
> -������ ������ static const unsigned int num = ARRAY_SIZE(xdg_dirs);
> -������ ������ WCHAR wszTempPath[MAX_PATH];
> -������ ������ char szPersonalTarget[FILENAME_MAX], *pszPersonal;
> -������ ������ char szMyStuffTarget[FILENAME_MAX], *pszMyStuff;
> -������ ������ char szDesktopTarget[FILENAME_MAX], *pszDesktop;
> -������ ������ struct stat statFolder;
> -������ ������ const char *pszHome;
> -������ ������ HRESULT hr;
> -������ ������ char ** xdg_results;
> -������ ������ char * xdg_desktop_dir;
> -
> -������ ������ /* Create all necessary profile sub-dirs up to 'My Documents' and get the unix path. */
> -������ ������ hr = SHGetFolderPathW(NULL, CSIDL_PERSONAL|CSIDL_FLAG_CREATE, NULL,
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ SHGFP_TYPE_DEFAULT, wszTempPath);
> -������ ������ if (FAILED(hr)) return;
> -������ ������ pszPersonal = wine_get_unix_file_name(wszTempPath);
> -������ ������ if (!pszPersonal) return;
> +static HRESULT compare_pathft_to_bootft(const WCHAR * ws_path, int * time_difference)
> +{
> +������ ������ static ULONG64 time_system_start = 0;
> +������ ������ SYSTEMTIME system_time_st;
> +������ ������ FILETIME dummy_ft, system_time_ft, last_write_ft;
> +������ ������ ULONG64 tick_count, time_last_write;
> +������ ������ HANDLE fhandle;
> +������ ������ DWORD ft_errorc = 0;
>������
> -������ ������ hr = XDG_UserDirLookup(xdg_dirs, num, &xdg_results);
> -������ ������ if (FAILED(hr)) xdg_results = NULL;
> +������ ������ if (!time_difference) return E_FAIL;
>������
> -������ ������ pszHome = getenv("HOME");
> -������ ������ if (pszHome && !stat(pszHome, &statFolder) && S_ISDIR(statFolder.st_mode))
> +������ ������ if (!time_system_start)
>������ ������ ������ {
> -������ ������ ������ ������ while (1)
> +������ ������ ������ ������ tick_count = (ULONG64) GetTickCount64();
> +������ ������ ������ ������ GetSystemTime(&system_time_st);
> +������ ������ ������ ������ if (!SystemTimeToFileTime( &system_time_st, &system_time_ft))
>������ ������ ������ ������ ������ {
> -������ ������ ������ ������ ������ ������ /* Check if there's already a Wine-specific 'My Documents' folder */
> -������ ������ ������ ������ ������ ������ strcpy(szPersonalTarget, pszHome);
> -������ ������ ������ ������ ������ ������ if (_SHAppendToUnixPath(szPersonalTarget, MAKEINTRESOURCEW(IDS_PERSONAL)) &&
> -������ ������ ������ ������ ������ ������ ������ ������ !stat(szPersonalTarget, &statFolder) && S_ISDIR(statFolder.st_mode))
> -������ ������ ������ ������ ������ ������ {
> -������ ������ ������ ������ ������ ������ ������ ������ /* '$HOME/My Documents' exists. Create 'My Pictures',
> -������ ������ ������ ������ ������ ������ ������ ������ ������* 'My Videos' and 'My Music' subfolders or fail silently if
> -������ ������ ������ ������ ������ ������ ������ ������ ������* they already exist.
> -������ ������ ������ ������ ������ ������ ������ ������ ������*/
> -������ ������ ������ ������ ������ ������ ������ ������ for (i = 0; i < ARRAY_SIZE(aidsMyStuff); i++)
> -������ ������ ������ ������ ������ ������ ������ ������ {
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ strcpy(szMyStuffTarget, szPersonalTarget);
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])))
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ mkdir(szMyStuffTarget, 0777);
> -������ ������ ������ ������ ������ ������ ������ ������ }
> -������ ������ ������ ������ ������ ������ ������ ������ break;
> -������ ������ ������ ������ ������ ������ }
> -
> -������ ������ ������ ������ ������ ������ /* Try to point to the XDG Documents folder */
> -������ ������ ������ ������ ������ ������ if (xdg_results && xdg_results[num-2] &&
> -������ ������ ������ ������ ������ ������ ������ ������!stat(xdg_results[num-2], &statFolder) &&
> -������ ������ ������ ������ ������ ������ ������ ������S_ISDIR(statFolder.st_mode))
> -������ ������ ������ ������ ������ ������ {
> -������ ������ ������ ������ ������ ������ ������ ������ strcpy(szPersonalTarget, xdg_results[num-2]);
> -������ ������ ������ ������ ������ ������ ������ ������ break;
> -������ ������ ������ ������ ������ ������ }
> -
> -������ ������ ������ ������ ������ ������ /* Or the hardcoded / OS X Documents folder */
> -������ ������ ������ ������ ������ ������ strcpy(szPersonalTarget, pszHome);
> -������ ������ ������ ������ ������ ������ if (_SHAppendToUnixPath(szPersonalTarget, DocumentsW) &&
> -������ ������ ������ ������ ������ ������ ������ ������!stat(szPersonalTarget, &statFolder) &&
> -������ ������ ������ ������ ������ ������ ������ ������S_ISDIR(statFolder.st_mode))
> -������ ������ ������ ������ ������ ������ ������ ������ break;
> -
> -������ ������ ������ ������ ������ ������ /* As a last resort point to $HOME. */
> -������ ������ ������ ������ ������ ������ strcpy(szPersonalTarget, pszHome);
> -������ ������ ������ ������ ������ ������ break;
> +������ ������ ������ ������ ������ ������ ERR("SystemTimeToFileTime call failed (%d)\n", GetLastError());
> +������ ������ ������ ������ ������ ������ return E_FAIL;
>������ ������ ������ ������ ������ }
> -
> -������ ������ ������ ������ /* Replace 'My Documents' directory with a symlink or fail silently if not empty. */
> -������ ������ ������ ������ remove(pszPersonal);
> -������ ������ ������ ������ symlink(szPersonalTarget, pszPersonal);
> +������ ������ ������ ������ time_system_start = (((ULONG64)system_time_ft.dwHighDateTime) << 32)
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ + system_time_ft.dwLowDateTime
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ - (tick_count * 10000);
>������ ������ ������ }
> -������ ������ else
> +������ ������ fhandle = CreateFileW( ws_path, 0, FILE_SHARE_READ, NULL,
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ OPEN_EXISTING,������ FILE_FLAG_BACKUP_SEMANTICS, NULL);
> +������ ������ if (fhandle == INVALID_HANDLE_VALUE)
>������ ������ ������ {
> -������ ������ ������ ������ /* '$HOME' doesn't exist. Create 'My Pictures', 'My Videos' and 'My Music' subdirs
> -������ ������ ������ ������ ������* in '%USERPROFILE%\\My Documents' or fail silently if they already exist. */
> -������ ������ ������ ������ pszHome = NULL;
> -������ ������ ������ ������ strcpy(szPersonalTarget, pszPersonal);
> -������ ������ ������ ������ for (i = 0; i < ARRAY_SIZE(aidsMyStuff); i++) {
> -������ ������ ������ ������ ������ ������ strcpy(szMyStuffTarget, szPersonalTarget);
> -������ ������ ������ ������ ������ ������ if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])))
> -������ ������ ������ ������ ������ ������ ������ ������ mkdir(szMyStuffTarget, 0777);
> -������ ������ ������ ������ }
> +������ ������ ������ ������ ERR("Open file handle failed for path: %s (%d)\n",
> +������ ������ ������ ������ ������ ������ debugstr_w(ws_path), GetLastError());
> +������ ������ ������ ������ return E_FAIL;
>������ ������ ������ }
> -
> -������ ������ /* Create symbolic links for 'My Pictures', 'My Videos' and 'My Music'. */
> -������ ������ for (i=0; i < ARRAY_SIZE(aidsMyStuff); i++)
> +������ ������ if (!GetFileTime(fhandle, &dummy_ft, &dummy_ft, &last_write_ft))
> +������ ������ ������ ������ ft_errorc = GetLastError();
> +������ ������ if (!CloseHandle(fhandle))
>������ ������ ������ {
> -������ ������ ������ ������ /* Create the current 'My Whatever' folder and get its unix path. */
> -������ ������ ������ ������ hr = SHGetFolderPathW(NULL, acsidlMyStuff[i]|CSIDL_FLAG_CREATE, NULL,
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ SHGFP_TYPE_DEFAULT, wszTempPath);
> -������ ������ ������ ������ if (FAILED(hr)) continue;
> -
> -������ ������ ������ ������ pszMyStuff = wine_get_unix_file_name(wszTempPath);
> -������ ������ ������ ������ if (!pszMyStuff) continue;
> -������ ������ ������ ������
> -������ ������ ������ ������ while (1)
> -������ ������ ������ ������ {
> -������ ������ ������ ������ ������ ������ /* Check for the Wine-specific '$HOME/My Documents' subfolder */
> -������ ������ ������ ������ ������ ������ strcpy(szMyStuffTarget, szPersonalTarget);
> -������ ������ ������ ������ ������ ������ if (_SHAppendToUnixPath(szMyStuffTarget, MAKEINTRESOURCEW(aidsMyStuff[i])) &&
> -������ ������ ������ ������ ������ ������ ������ ������ !stat(szMyStuffTarget, &statFolder) && S_ISDIR(statFolder.st_mode))
> -������ ������ ������ ������ ������ ������ ������ ������ break;
> +������ ������ ������ ������ ERR("Close file handle failed: %p (%d)\n",
> +������ ������ ������ ������ ������ ������ fhandle, GetLastError());
> +������ ������ ������ ������ return E_FAIL;
> +������ ������ }
> +������ ������ if (ft_errorc)
> +������ ������ {
> +������ ������ ������ ������ ERR("Get file time failed: %p (%d)\n",
> +������ ������ ������ ������ ������ ������ fhandle, ft_errorc);
> +������ ������ ������ ������ return E_FAIL;
> +������ ������ }
> +������ ������ time_last_write = (((ULONG64)last_write_ft.dwHighDateTime) << 32)
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ + last_write_ft.dwLowDateTime;
> +������ ������ *time_difference = ((time_last_write > time_system_start) ?������ 1 :
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������((time_last_write < time_system_start) ? -1 :
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������0));
>������
> -������ ������ ������ ������ ������ ������ /* Try the XDG_XXX_DIR folder */
> -������ ������ ������ ������ ������ ������ if (xdg_results && xdg_results[i])
> -������ ������ ������ ������ ������ ������ {
> -������ ������ ������ ������ ������ ������ ������ ������ strcpy(szMyStuffTarget, xdg_results[i]);
> -������ ������ ������ ������ ������ ������ ������ ������ break;
> -������ ������ ������ ������ ������ ������ }
> +������ ������ return S_OK;
> +}
>������
> -������ ������ ������ ������ ������ ������ /* Or the OS X folder (these are never localized) */
> -������ ������ ������ ������ ������ ������ if (pszHome)
> -������ ������ ������ ������ ������ ������ {
> -������ ������ ������ ������ ������ ������ ������ ������ strcpy(szMyStuffTarget, pszHome);
> -������ ������ ������ ������ ������ ������ ������ ������ if (_SHAppendToUnixPath(szMyStuffTarget, MyOSXStuffW[i]) &&
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ !stat(szMyStuffTarget, &statFolder) &&
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ S_ISDIR(statFolder.st_mode))
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ break;
> -������ ������ ������ ������ ������ ������ }
> +/******************************************************************************
> + * create_homedir_symbolic_link������ [Internal]
> + *
> + * Creates a symbolic link from the current Wineprefix to an appropriate
> + * HOME subdirectory (if one is found).
> + *
> + *������ Creates 'XXXX' directory in Wineprefix.
> + *������ Then create a 'My XXXX' symbolic link in Wineprefix:
> + *������ 1) If '$HOME/XXXX' (IDS directory) exists then target this.
> + *������ 2) If '$HOME/XXXX' (XDG_XXXX_DIR) exists then target this.
> + *������ 3) If '$HOME/XXXX' (MacOS XXXX media directory) exists then target this.
> + *
> + *������ PARAMS
> + *������ env_enabled [I]
> + *������ ������ ������ ������ ������TRUE������ ������ ������ The user has enabled this Shell Folder to be symlinked (default).
> + *������ ������ ������ ������ FALSE������ ������ ������ The user has chosen to disable symlinking this Shell Folder.
> + *������ ������ ������ ������ ������ ������ ������ ������ ������ ������The Shell Folder will be created, in the current Wineprefix (if not pre-existing).
> + *������ ids_dir������ ������ ������[I]������ Windows Resource Identifier code for current Shell Folder.
> + *������ csidl_dir������ ������[I]������ Constant Special Item ID List identifier for current Shell Folder.
> + *������ xdg_dir������ ������ ������[I]������ Full path of external Unix XDG directory corresponding to current Shell Folder.
> + *������ ws_osx_dir������ [I]������ Fallback directory name to use, corresponding to current Shell Folder (OSX specific).
> + *
> + */
> +void create_homedir_symbolic_link(BOOL env_enabled,
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ UINT ids_dir,
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ int csidl_dir,
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ const char * xdg_dir,
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ const WCHAR * ws_osx_dir)
> +{
> +������ ������ static const char * env_homedir = NULL;
> +������ ������ WCHAR ws_temp_path[MAX_PATH];
> +������ ������ char home_target[FILENAME_MAX], * prefix_dir;
> +������ ������ struct stat stat_folder, stat_home_folder;
> +������ ������ HRESULT hr;
> +������ ������ BOOL target_ok;
> +������ ������ int time_difference;
>������
> -������ ������ ������ ������ ������ ������ /* As a last resort point to the same location as 'My Documents' */
> -������ ������ ������ ������ ������ ������ strcpy(szMyStuffTarget, szPersonalTarget);
> -������ ������ ������ ������ ������ ������ break;
> +������ ������ hr = SHGetFolderPathW(NULL, csidl_dir, NULL,
> +������ ������ ������ ������ ������ ������ ������ ������ SHGFP_TYPE_DEFAULT, ws_temp_path);
> +������ ������ if (SUCCEEDED(hr))
> +������ ������ {
> +������ ������ ������ ������ if (ids_dir != IDS_DESKTOPDIRECTORY) return;
> +
> +������ ������ ������ ������ if ((compare_pathft_to_bootft(ws_temp_path, &time_difference) == S_OK)
> +������ ������ ������ ������ ������ ������ && (time_difference < 0))
> +������ ������ ������ ������ {
> +������ ������ ������ ������ ������ ������ TRACE("%s directory created before wineboot\n",
> +������ ������ ������ ������ ������ ������ ������ ������ ������ debugstr_w(ws_temp_path));
> +������ ������ ������ ������ ������ ������ return;
>������ ������ ������ ������ ������ }
> -������ ������ ������ ������ remove(pszMyStuff);
> -������ ������ ������ ������ symlink(szMyStuffTarget, pszMyStuff);
> -������ ������ ������ ������ heap_free(pszMyStuff);
>������ ������ ������ }
> -
> -������ ������ /* Last but not least, the Desktop folder */
> -������ ������ if (pszHome)
> -������ ������ ������ ������ strcpy(szDesktopTarget, pszHome);
> +������ ������ else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND))
> +������ ������ {
> +������ ������ ������ ������ hr = SHGetFolderPathW(NULL, csidl_dir|CSIDL_FLAG_CREATE, NULL,
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ SHGFP_TYPE_DEFAULT, ws_temp_path);
> +������ ������ ������ ������ if (FAILED(hr)) return;
> +������ ������ }
>������ ������ ������ else
> -������ ������ ������ ������ strcpy(szDesktopTarget, pszPersonal);
> -������ ������ heap_free(pszPersonal);
> +������ ������ {
> +������ ������ ������ ������ ERR("Failed to get Wineprefix path corresponding to %d CSIDL\n",
> +������ ������ ������ ������ ������ ������ csidl_dir);
> +������ ������ ������ ������ return;
> +������ ������ }
> +������ ������ if (!env_enabled) return;
> +������ ������ prefix_dir = wine_get_unix_file_name(ws_temp_path);
> +������ ������ if (!prefix_dir)
> +������ ������ {
> +������ ������ ������ ������ ERR("Failed to get Unix Wineprefix directory for: %s\n",
> +������ ������ ������ ������ ������ ������ debugstr_w(ws_temp_path));
> +������ ������ ������ ������ return;
> +������ ������ }
> +������ ������ if (!env_homedir) env_homedir = getenv("HOME");
> +������ ������ if (!(env_homedir
> +������ ������ ������ ������ ������ && (stat(env_homedir, &stat_home_folder) != 1)
> +������ ������ ������ ������ ������ && S_ISDIR(stat_home_folder.st_mode)))
> +������ ������ {
> +������ ������ ������ ������ if (prefix_dir) heap_free(prefix_dir);
> +������ ������ ������ ������ return;
> +������ ������ }
>������
> -������ ������ xdg_desktop_dir = xdg_results ? xdg_results[num - 1] : NULL;
> -������ ������ if (xdg_desktop_dir ||
> -������ ������ ������ ������ (_SHAppendToUnixPath(szDesktopTarget, DesktopW) &&
> -������ ������ ������ ������ !stat(szDesktopTarget, &statFolder) && S_ISDIR(statFolder.st_mode)))
> +������ ������ /* Check for:
> +������ ������ ������*������ ������ '$HOME/XXXX' (IDS directory)
> +������ ������ ������* or '$HOME/XXXX' (XDG directory)
> +������ ������ ������* or '$HOME/XXXX' (MacOS directory)
> +������ ������ ������*/
> +������ ������ target_ok = FALSE;
> +������ ������ strcpy(home_target, env_homedir);
> +������ ������ if (append_to_unix_path(home_target, MAKEINTRESOURCEW(ids_dir))
> +������ ������ ������ ������ && (stat(home_target, &stat_folder) != -1)
> +������ ������ ������ ������ && S_ISDIR(stat_folder.st_mode))
>������ ������ ������ {
> -������ ������ ������ ������ hr = SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY|CSIDL_FLAG_CREATE, NULL,
> -������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ SHGFP_TYPE_DEFAULT, wszTempPath);
> -������ ������ ������ ������ if (SUCCEEDED(hr) && (pszDesktop = wine_get_unix_file_name(wszTempPath)))
> -������ ������ ������ ������ {
> -������ ������ ������ ������ ������ ������ remove(pszDesktop);
> -������ ������ ������ ������ ������ ������ if (xdg_desktop_dir)
> -������ ������ ������ ������ ������ ������ ������ ������ symlink(xdg_desktop_dir, pszDesktop);
> -������ ������ ������ ������ ������ ������ else
> -������ ������ ������ ������ ������ ������ ������ ������ symlink(szDesktopTarget, pszDesktop);
> -������ ������ ������ ������ ������ ������ heap_free(pszDesktop);
> -������ ������ ������ ������ }
> +������ ������ ������ ������ target_ok = TRUE;
> +������ ������ }
> +������ ������ else if (xdg_dir && stat(xdg_dir, &stat_folder))
> +������ ������ {
> +������ ������ ������ ������ strcpy(home_target, xdg_dir);
> +������ ������ ������ ������ /* Only link to the XDG directory, if it does not point directly
> +������ ������ ������ ������ ������* to the user's HOME directory (XDG specification fallback path). */
> +������ ������ ������ ������ target_ok = (stat_folder.st_ino != stat_home_folder.st_ino);
> +������ ������ }
> +������ ������ else
> +������ ������ {
> +������ ������ ������ ������ strcpy(home_target, env_homedir);
> +������ ������ ������ ������ target_ok = append_to_unix_path(home_target, ws_osx_dir)
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ && (stat(home_target, &stat_folder) != -1)
> +������ ������ ������ ������ ������ ������ ������ ������ ������ ������ && S_ISDIR(stat_folder.st_mode);
>������ ������ ������ }
>������
> -������ ������ /* Free resources allocated by XDG_UserDirLookup() */
> -������ ������ if (xdg_results)
> +������ ������ if (target_ok)
>������ ������ ������ {
> -������ ������ ������ ������ for (i = 0; i < num; i++)
> -������ ������ ������ ������ ������ ������ heap_free(xdg_results[i]);
> -������ ������ ������ ������ heap_free(xdg_results);
> +������ ������ ������ ������ remove(prefix_dir);
> +������ ������ ������ ������ symlink(home_target, prefix_dir);
>������ ������ ������ }
> +
> +
> +������ ������ if (prefix_dir) heap_free(prefix_dir);
>������ }
>������
>������ /******************************************************************************
> @@ -6126,15 +6133,139 @@ static void register_system_knownfolders(void)
>������ ������ ������ }
>������ }
>������
> +/******************************************************************************
> + * parse_symlinks_env_variable������ [Internal]
> + *
> + * PARAMS
> + *������ xdg_dir������ ������ ������[I]������ Unix XDG directory name (without "XDG_" prefix and "_DIR" suffix).
> + *
> + * RETURNS
> + *������ TRUE������ ������"WINESYMLINK" contains the specified XDG directory string.
> + *������ FALSE������ "WINESYMLINK" does not contain the specified XDG directory string.
> + *������ ������ ������ ������ ������Any error condition.
> + *
> + * Note: "WINESYMLINK", defaults to "ALL", when unset. This enables all symlinks.
> + *������ ������ ������ ������"WINESYMLINK" can also be manually set to "ALL", enabling all symlinks.
> + *
> + */
> +static BOOL parse_symlinks_env_variable(const char * xdg_dir)
> +{
> +������ ������ static const char * matchall_env_var = "ALL";
> +������ ������ static char env_var[MAX_PATH+1];
> +������ ������ static char * env_winesymlink = NULL;
> +������ ������ char * sstr, * sstr_stop, * tstr, * word_start = NULL;
> +������ ������ char seperator_ch = '\0';
> +������ ������ BOOL end_string, in_word = FALSE, matched = FALSE;
> +������ ������ size_t slength = 0, matchall_length, xdg_dir_slength;
> +
> +������ ������ if (!xdg_dir) return matched;
> +
> +������ ������ if (!env_winesymlink)
> +������ ������ {
> +������ ������ ������ ������ env_winesymlink = getenv("WINESYMLINK");
> +������ ������ ������ ������ if (!env_winesymlink) env_winesymlink = (char*) matchall_env_var;
> +������ ������ ������ ������ tstr = env_var;
> +������ ������ ������ ������ sstr_stop = env_winesymlink + MAX_PATH;
> +������ ������ ������ ������ for (sstr = env_winesymlink; *sstr && (sstr != sstr_stop); ++sstr)
> +������ ������ ������ ������ {
> +������ ������ ������ ������ ������ ������ if (!seperator_ch && (ispunct(*sstr) || isspace(*sstr)))
> +������ ������ ������ ������ ������ ������ ������ ������ seperator_ch = *sstr;
> +������ ������ ������ ������ ������ ������ if (!isalpha(*sstr)) continue;
> +
> +������ ������ ������ ������ ������ ������ if (seperator_ch && in_word)
> +������ ������ ������ ������ ������ ������ {
> +������ ������ ������ ������ ������ ������ ������ ������ *tstr++ = seperator_ch;
> +������ ������ ������ ������ ������ ������ ������ ������ ++slength;
> +������ ������ ������ ������ ������ ������ }
> +������ ������ ������ ������ ������ ������ in_word = TRUE;
> +������ ������ ������ ������ ������ ������ seperator_ch = '\0';
> +������ ������ ������ ������ ������ ������ *tstr++ = toupper(*sstr);
> +������ ������ ������ ������ ������ ������ ++slength;
> +������ ������ ������ ������ }
> +������ ������ ������ ������ env_var[slength] = '\0';
> +������ ������ }
> +
> +������ ������ TRACE("processed env variable: %s\n", debugstr_a(&env_var[0]));
> +������ ������ matchall_length = strlen(matchall_env_var);
> +������ ������ xdg_dir_slength = strlen(xdg_dir);
> +������ ������ end_string = !(env_var[0]);
> +������ ������ for (sstr = &env_var[0]; !end_string; ++sstr)
> +������ ������ {
> +������ ������ ������ ������ end_string = !(*(sstr+1));
> +������ ������ ������ ������ in_word = isalpha(*sstr);
> +������ ������ ������ ������ if (in_word && !word_start) word_start = sstr;
> +������ ������ ������ ������ in_word = in_word && !end_string;
> +������ ������ ������ ������ if (in_word || !word_start) continue;
> +
> +������ ������ ������ ������ slength = ((size_t) (sstr-word_start))+(end_string ? 1 : 0);
> +������ ������ ������ ������ matched = ( (slength == xdg_dir_slength) && (strncmp(word_start, xdg_dir, slength) == 0) )
> +������ ������ ������ ������ ������ ������ ������ ������|| ( (slength == matchall_length) && (strncmp(word_start, "ALL",������ ������slength) == 0) );
> +������ ������ ������ ������ if (matched) break;
> +
> +������ ������ ������ ������ word_start = NULL;
> +������ ������ }
> +������ ������ TRACE("%s symlinking for %s\n", matched ? "Enabled" : "Disabled", debugstr_a(xdg_dir));
> +
> +������ ������ return matched;
> +}
> +
> +/******************************************************************************
> + * create_homedir_symbolic_links������ [Internal]
> + *
> + * Parse WINESYMLINK env variable, for each XDG directory argument. To test if symlinking is
> + * enabled for that XDG directory / Wine Profile Folder.
> + * Then calls the function create_homedir_symbolic_link to potentially symlink from a Shell Folder,
> + * in the current Wineprefix, to a subdirectory of the current user's HOME directory.
> + *
> + * PARAMS
> + *������ xdg_dirnames������ ������[I]������ Pointer to an array of Unix XDG directory names
> + *������ ������ ������ ������ ������ ������ ������ ������ ������ ������ ������ (without "XDG_" prefix and "_DIR" suffix).
> + *������ xdg_dir_count������ [I]������ Item count of array (above).
> + *
> + */
> +static void create_homedir_symbolic_links(const char * const xdg_dirnames[], const UINT xdg_dir_count)
> +{
> +������ ������ char ** xdg_dirs_array;
> +������ ������ char * xdg_dir;
> +������ ������ HRESULT hr;
> +������ ������ UINT i;
> +������ ������ BOOL env_enabled;
> +
> +������ ������ if (!xdg_dirnames) return;
> +
> +������ ������ hr = XDG_UserDirLookup(xdg_dirnames, xdg_dir_count, &xdg_dirs_array);
> +������ ������ if (FAILED(hr)) xdg_dirs_array = NULL;
> +
> +������ ������ for (i = 0; i < xdg_dir_count; ++i)
> +������ ������ {
> +������ ������ ������ ������ env_enabled = parse_symlinks_env_variable(xdg_dirnames[i]);
> +������ ������ ������ ������ xdg_dir = xdg_dirs_array ? xdg_dirs_array[i] : NULL;
> +������ ������ ������ ������ if (!strcmp(xdg_dirnames[i],"DOCUMENTS"))
> +������ ������ ������ ������ ������ ������ create_homedir_symbolic_link(env_enabled, IDS_PERSONAL, CSIDL_PERSONAL, xdg_dir, DocumentsW);
> +������ ������ ������ ������ else if (!strcmp(xdg_dirnames[i],"PICTURES"))
> +������ ������ ������ ������ ������ ������ create_homedir_symbolic_link(env_enabled, IDS_MYPICTURES, CSIDL_MYPICTURES, xdg_dir, PicturesW);
> +������ ������ ������ ������ else if (!strcmp(xdg_dirnames[i],"VIDEOS"))
> +������ ������ ������ ������ ������ ������ create_homedir_symbolic_link(env_enabled, IDS_MYVIDEOS, CSIDL_MYVIDEO, xdg_dir, MoviesW);
> +������ ������ ������ ������ else if (!strcmp(xdg_dirnames[i],"MUSIC"))
> +������ ������ ������ ������ ������ ������ create_homedir_symbolic_link(env_enabled, IDS_MYMUSIC, CSIDL_MYMUSIC, xdg_dir, MusicW);
> +������ ������ ������ ������ else if (!strcmp(xdg_dirnames[i],"DESKTOP"))
> +������ ������ ������ ������ ������ ������ create_homedir_symbolic_link(env_enabled, IDS_DESKTOPDIRECTORY, CSIDL_DESKTOPDIRECTORY, xdg_dir, DesktopW);
> +������ ������ ������ ������ else
> +������ ������ ������ ������ ������ ������ ERR("XDG directory name specifier invalid: %s\n", debugstr_a(xdg_dirnames[i]));
> +������ ������ ������ ������ if (xdg_dir) heap_free(xdg_dirs_array[i]);
> +������ ������ }
> +������ ������ if (xdg_dirs_array) heap_free(xdg_dirs_array);
> +}
> +
>������ HRESULT SHELL_RegisterShellFolders(void)
>������ {
> +������ ������ static const char * const xdg_dirnames[] = { "DOCUMENTS", "PICTURES", "VIDEOS", "MUSIC", "DESKTOP" };
> +������ ������ const UINT xdg_dir_count = 5;
>������ ������ ������ HRESULT hr;
>������
> -������ ������ /* Set up '$HOME' targeted symlinks for 'My Documents', 'My Pictures',
> -������ ������ ������* 'My Videos', 'My Music' and 'Desktop' in advance, so that the
> -������ ������ ������* _SHRegister*ShellFolders() functions will find everything nice and clean
> -������ ������ ������* and thus will not attempt to create them in the profile directory. */
> -������ ������ _SHCreateSymbolicLinks();
> +������ ������ /* Early setup of symlinks from specific User Shell Folders, in
> +������ ������ ������* current Wineprefix, to subdirecties of the user's HOME directory. */
> +������ ������ create_homedir_symbolic_links(xdg_dirnames, xdg_dir_count);
>������
>������ ������ ������ hr = _SHRegisterUserShellFolders(TRUE);
>������ ������ ������ if (SUCCEEDED(hr))
> @@ -6147,5 +6278,6 @@ HRESULT SHELL_RegisterShellFolders(void)
>������ ������ ������ ������ ������ hr = set_folder_attributes();
>������ ������ ������ if (SUCCEEDED(hr))
>������ ������ ������ ������ ������ register_system_knownfolders();
> +
>������ ������ ������ return hr;
>������ }
> --
> 2.17.1
>
>
>