To: vim_dev@googlegroups.com Subject: Patch 8.2.0850 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.0850 Problem: MS-Windows: exepath() works different from cmd.exe. Solution: Make exepath() work better on MS-Windows. (closes #6115) Files: runtime/doc/eval.txt, src/os_win32.c, src/testdir/test_functions.vim *** ../vim-8.2.0849/runtime/doc/eval.txt 2020-05-24 13:10:14.303617017 +0200 --- runtime/doc/eval.txt 2020-05-30 18:26:55.161581298 +0200 *************** *** 4027,4033 **** On MS-Windows the ".exe", ".bat", etc. can optionally be included. Then the extensions in $PATHEXT are tried. Thus if "foo.exe" does not exist, "foo.exe.bat" can be found. If ! $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot by itself can be used in $PATHEXT to try using the name without an extension. When 'shell' looks like a Unix shell, then the name is also tried without adding an extension. --- 4034,4040 ---- On MS-Windows the ".exe", ".bat", etc. can optionally be included. Then the extensions in $PATHEXT are tried. Thus if "foo.exe" does not exist, "foo.exe.bat" can be found. If ! $PATHEXT is not set then ".com;.exe;.bat;.cmd" is used. A dot by itself can be used in $PATHEXT to try using the name without an extension. When 'shell' looks like a Unix shell, then the name is also tried without adding an extension. *** ../vim-8.2.0849/src/os_win32.c 2020-05-30 17:49:21.755140563 +0200 --- src/os_win32.c 2020-05-30 18:30:24.792906407 +0200 *************** *** 2080,2136 **** #endif /* * If "use_path" is TRUE: Return TRUE if "name" is in $PATH. * If "use_path" is FALSE: Return TRUE if "name" exists. * When returning TRUE and "path" is not NULL save the path and set "*path" to * the allocated memory. - * TODO: Should somehow check if it's really executable. */ static int ! executable_exists(char *name, char_u **path, int use_path) { ! WCHAR *p; ! WCHAR fnamew[_MAX_PATH]; ! WCHAR *dumw; ! WCHAR *wcurpath, *wnewpath; ! long n; ! if (!use_path) { ! if (mch_getperm((char_u *)name) != -1 && !mch_isdir((char_u *)name)) { ! if (path != NULL) { ! if (mch_isFullName((char_u *)name)) ! *path = vim_strsave((char_u *)name); ! else ! *path = FullName_save((char_u *)name, FALSE); } - return TRUE; } - return FALSE; } ! p = enc_to_utf16((char_u *)name, NULL); ! if (p == NULL) ! return FALSE; ! wcurpath = _wgetenv(L"PATH"); ! wnewpath = ALLOC_MULT(WCHAR, wcslen(wcurpath) + 3); ! if (wnewpath == NULL) ! return FALSE; ! wcscpy(wnewpath, L".;"); ! wcscat(wnewpath, wcurpath); ! n = (long)SearchPathW(wnewpath, p, NULL, _MAX_PATH, fnamew, &dumw); ! vim_free(wnewpath); ! vim_free(p); ! if (n == 0) ! return FALSE; ! if (GetFileAttributesW(fnamew) & FILE_ATTRIBUTE_DIRECTORY) ! return FALSE; ! if (path != NULL) ! *path = utf16_to_enc(fnamew, NULL); ! return TRUE; } #if (defined(__MINGW32__) && __MSVCRT_VERSION__ >= 0x800) || \ --- 2080,2279 ---- #endif /* + * Return TRUE if "name" is an executable file, FALSE if not or it doesn't exist. + * When returning TRUE and "path" is not NULL save the path and set "*path" to + * the allocated memory. + * TODO: Should somehow check if it's really executable. + */ + static int + executable_file(char *name, char_u **path) + { + if (mch_getperm((char_u *)name) != -1 && !mch_isdir((char_u *)name)) + { + if (path != NULL) + *path = FullName_save((char_u *)name, FALSE); + return TRUE; + } + return FALSE; + } + + /* * If "use_path" is TRUE: Return TRUE if "name" is in $PATH. * If "use_path" is FALSE: Return TRUE if "name" exists. + * If "use_pathext" is TRUE search "name" with extensions in $PATHEXT. * When returning TRUE and "path" is not NULL save the path and set "*path" to * the allocated memory. */ static int ! executable_exists(char *name, char_u **path, int use_path, int use_pathext) { ! // WinNT and later can use _MAX_PATH wide characters for a pathname, which ! // means that the maximum pathname is _MAX_PATH * 3 bytes when 'enc' is ! // UTF-8. ! char_u buf[_MAX_PATH * 3]; ! size_t len = STRLEN(name); ! size_t tmplen; ! char_u *p, *e, *e2; ! char_u *pathbuf = NULL; ! char_u *pathext = NULL; ! char_u *pathextbuf = NULL; ! int noext = FALSE; ! int retval = FALSE; ! ! if (len >= sizeof(buf)) // safety check ! return FALSE; ! ! // Using the name directly when a Unix-shell like 'shell'. ! if (strstr((char *)gettail(p_sh), "sh") != NULL) ! noext = TRUE; ! if (use_pathext) { ! pathext = mch_getenv("PATHEXT"); ! if (pathext == NULL) ! pathext = (char_u *)".com;.exe;.bat;.cmd"; ! ! if (noext == FALSE) { ! /* ! * Loop over all extensions in $PATHEXT. ! * Check "name" ends with extension. ! */ ! p = pathext; ! while (*p) { ! if (p[0] == ';' ! || (p[0] == '.' && (p[1] == NUL || p[1] == ';'))) ! { ! // Skip empty or single ".". ! ++p; ! continue; ! } ! e = vim_strchr(p, ';'); ! if (e == NULL) ! e = p + STRLEN(p); ! tmplen = e - p; ! ! if (_strnicoll(name + len - tmplen, (char *)p, tmplen) == 0) ! { ! noext = TRUE; ! break; ! } ! ! p = e; } } } ! // Prepend single "." to pathext, it's means no extension added. ! if (pathext == NULL) ! pathext = (char_u *)"."; ! else if (noext == TRUE) ! { ! if (pathextbuf == NULL) ! pathextbuf = alloc(STRLEN(pathext) + 3); ! if (pathextbuf == NULL) ! { ! retval = FALSE; ! goto theend; ! } ! STRCPY(pathextbuf, ".;"); ! STRCAT(pathextbuf, pathext); ! pathext = pathextbuf; ! } ! // Use $PATH when "use_path" is TRUE and "name" is basename. ! if (use_path && gettail((char_u *)name) == (char_u *)name) ! { ! p = mch_getenv("PATH"); ! if (p != NULL) ! { ! pathbuf = alloc(STRLEN(p) + 3); ! if (pathbuf == NULL) ! { ! retval = FALSE; ! goto theend; ! } ! STRCPY(pathbuf, ".;"); ! STRCAT(pathbuf, p); ! } ! } ! ! /* ! * Walk through all entries in $PATH to check if "name" exists there and ! * is an executable file. ! */ ! p = (pathbuf != NULL) ? pathbuf : (char_u *)"."; ! while (*p) ! { ! if (*p == ';') // Skip empty entry ! { ! ++p; ! continue; ! } ! e = vim_strchr(p, ';'); ! if (e == NULL) ! e = p + STRLEN(p); ! ! if (e - p + len + 2 > sizeof(buf)) ! { ! retval = FALSE; ! goto theend; ! } ! // A single "." that means current dir. ! if (e - p == 1 && *p == '.') ! STRCPY(buf, name); ! else ! { ! vim_strncpy(buf, p, e - p); ! add_pathsep(buf); ! STRCAT(buf, name); ! } ! tmplen = STRLEN(buf); ! ! /* ! * Loop over all extensions in $PATHEXT. ! * Check "name" with extension added. ! */ ! p = pathext; ! while (*p) ! { ! if (*p == ';') ! { ! // Skip empty entry ! ++p; ! continue; ! } ! e2 = vim_strchr(p, (int)';'); ! if (e2 == NULL) ! e2 = p + STRLEN(p); ! ! if (!(p[0] == '.' && (p[1] == NUL || p[1] == ';'))) ! { ! // Not a single "." that means no extension is added. ! if (e2 - p + tmplen + 1 > sizeof(buf)) ! { ! retval = FALSE; ! goto theend; ! } ! vim_strncpy(buf + tmplen, p, e2 - p); ! } ! if (executable_file((char *)buf, path)) ! { ! retval = TRUE; ! goto theend; ! } ! ! p = e2; ! } ! ! p = e; ! } ! ! theend: ! free(pathextbuf); ! free(pathbuf); ! return retval; } #if (defined(__MINGW32__) && __MSVCRT_VERSION__ >= 0x800) || \ *************** *** 2210,2216 **** vimrun_path = (char *)vim_strsave(vimrun_location); s_dont_use_vimrun = FALSE; } ! else if (executable_exists("vimrun.exe", NULL, TRUE)) s_dont_use_vimrun = FALSE; // Don't give the warning for a missing vimrun.exe right now, but only --- 2353,2359 ---- vimrun_path = (char *)vim_strsave(vimrun_location); s_dont_use_vimrun = FALSE; } ! else if (executable_exists("vimrun.exe", NULL, TRUE, FALSE)) s_dont_use_vimrun = FALSE; // Don't give the warning for a missing vimrun.exe right now, but only *************** *** 2224,2230 **** * If "finstr.exe" doesn't exist, use "grep -n" for 'grepprg'. * Otherwise the default "findstr /n" is used. */ ! if (!executable_exists("findstr.exe", NULL, TRUE)) set_option_value((char_u *)"grepprg", 0, (char_u *)"grep -n", 0); # ifdef FEAT_CLIPBOARD --- 2367,2373 ---- * If "finstr.exe" doesn't exist, use "grep -n" for 'grepprg'. * Otherwise the default "findstr /n" is used. */ ! if (!executable_exists("findstr.exe", NULL, TRUE, FALSE)) set_option_value((char_u *)"grepprg", 0, (char_u *)"grep -n", 0); # ifdef FEAT_CLIPBOARD *************** *** 3306,3374 **** int mch_can_exe(char_u *name, char_u **path, int use_path) { ! // WinNT and later can use _MAX_PATH wide characters for a pathname, which ! // means that the maximum pathname is _MAX_PATH * 3 bytes when 'enc' is ! // UTF-8. ! char_u buf[_MAX_PATH * 3]; ! int len = (int)STRLEN(name); ! char_u *p, *saved; ! ! if (len >= sizeof(buf)) // safety check ! return FALSE; ! ! // Try using the name directly when a Unix-shell like 'shell'. ! if (strstr((char *)gettail(p_sh), "sh") != NULL) ! if (executable_exists((char *)name, path, use_path)) ! return TRUE; ! ! /* ! * Loop over all extensions in $PATHEXT. ! */ ! p = mch_getenv("PATHEXT"); ! if (p == NULL) ! p = (char_u *)".com;.exe;.bat;.cmd"; ! saved = vim_strsave(p); ! if (saved == NULL) ! return FALSE; ! p = saved; ! while (*p) ! { ! char_u *tmp = vim_strchr(p, ';'); ! ! if (tmp != NULL) ! *tmp = NUL; ! if (_stricoll((char *)name + len - STRLEN(p), (char *)p) == 0 ! && executable_exists((char *)name, path, use_path)) ! { ! vim_free(saved); ! return TRUE; ! } ! if (tmp == NULL) ! break; ! p = tmp + 1; ! } ! vim_free(saved); ! ! vim_strncpy(buf, name, sizeof(buf) - 1); ! p = mch_getenv("PATHEXT"); ! if (p == NULL) ! p = (char_u *)".com;.exe;.bat;.cmd"; ! while (*p) ! { ! if (p[0] == '.' && (p[1] == NUL || p[1] == ';')) ! { ! // A single "." means no extension is added. ! buf[len] = NUL; ! ++p; ! if (*p) ! ++p; ! } ! else ! copy_option_part(&p, buf + len, sizeof(buf) - len, ";"); ! if (executable_exists((char *)buf, path, use_path)) ! return TRUE; ! } ! return FALSE; } /* --- 3449,3455 ---- int mch_can_exe(char_u *name, char_u **path, int use_path) { ! return executable_exists((char *)name, path, TRUE, TRUE); } /* *** ../vim-8.2.0849/src/testdir/test_functions.vim 2020-05-30 18:14:37.828521058 +0200 --- src/testdir/test_functions.vim 2020-05-30 18:26:55.161581298 +0200 *************** *** 1187,1192 **** --- 1187,1216 ---- call assert_equal(0, executable('notepad.exe.exe')) call assert_equal(0, executable('shell32.dll')) call assert_equal(0, executable('win.ini')) + + " get "notepad" path and remove the leading drive and sep. (ex. 'C:\') + let notepadcmd = exepath('notepad.exe') + let driveroot = notepadcmd[:2] + let notepadcmd = notepadcmd[3:] + new + " check that the relative path works in / + execute 'lcd' driveroot + call assert_equal(1, executable(notepadcmd)) + call assert_equal(driveroot .. notepadcmd, notepadcmd->exepath()) + bwipe + + " create "notepad.bat" + call mkdir('Xdir') + let notepadbat = fnamemodify('Xdir/notepad.bat', ':p') + call writefile([], notepadbat) + new + " check that the path and the pathext order is valid + lcd Xdir + let [pathext, $PATHEXT] = [$PATHEXT, '.com;.exe;.bat;.cmd'] + call assert_equal(notepadbat, exepath('notepad')) + let $PATHEXT = pathext + bwipe + eval 'Xdir'->delete('rf') elseif has('unix') call assert_equal(1, 'cat'->executable()) call assert_equal(0, executable('nodogshere')) *** ../vim-8.2.0849/src/version.c 2020-05-30 18:14:37.828521058 +0200 --- src/version.c 2020-05-30 18:29:37.805062059 +0200 *************** *** 748,749 **** --- 748,751 ---- { /* Add new patch number below this line */ + /**/ + 850, /**/ -- From the classified section of a city newspaper: Dog for sale: eats anything and is fond of children. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///