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.
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; }
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.
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:
- If '$HOME/XXXX' (IDS directory) exists then target this.
- If '$HOME/XXXX' (XDG_XXXX_DIR) exists then target this.
- 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
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:
- If '$HOME/XXXX' (IDS directory) exists then target this.
- If '$HOME/XXXX' (XDG_XXXX_DIR) exists then target this.
- 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
On 7 Jun 2018, at 16:11, Bob Wya bob.mt.wya@gmail.com wrote:
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...
For new functions you’d avoid CamelCase and use something like create_symbolic_link(). If might be ok to rename existing functions if the patch is small enough. The problem at the moment is that the patch is much too large.
Huw.