diff options
-rw-r--r-- | winsup/cygwin/autoload.cc | 7 | ||||
-rw-r--r-- | winsup/cygwin/fhandler/netdrive.cc | 304 | ||||
-rw-r--r-- | winsup/cygwin/release/3.6.0 | 9 |
3 files changed, 247 insertions, 73 deletions
diff --git a/winsup/cygwin/autoload.cc b/winsup/cygwin/autoload.cc index e79b6ad..c262c7e 100644 --- a/winsup/cygwin/autoload.cc +++ b/winsup/cygwin/autoload.cc @@ -502,6 +502,12 @@ LoadDLLfunc (ldap_value_free_len, wldap32) LoadDLLfunc (LdapGetLastError, wldap32) LoadDLLfunc (LdapMapErrorToWin32, wldap32) +LoadDLLfunc (WNetCloseEnum, mpr) +LoadDLLfunc (WNetEnumResourceW, mpr) +LoadDLLfunc (WNetGetProviderNameW, mpr) +LoadDLLfunc (WNetGetResourceInformationW, mpr) +LoadDLLfunc (WNetOpenEnumW, mpr) + LoadDLLfunc (DsEnumerateDomainTrustsW, netapi32) LoadDLLfunc (DsGetDcNameW, netapi32) LoadDLLfunc (NetApiBufferFree, netapi32) @@ -626,6 +632,7 @@ LoadDLLfunc (WSAEnumNetworkEvents, ws2_32) LoadDLLfunc (WSAEventSelect, ws2_32) LoadDLLfunc (WSAGetLastError, ws2_32) LoadDLLfunc (WSAIoctl, ws2_32) +LoadDLLfunc (WSAPoll, ws2_32) LoadDLLfunc (WSARecv, ws2_32) LoadDLLfunc (WSARecvFrom, ws2_32) LoadDLLfunc (WSASendMsg, ws2_32) diff --git a/winsup/cygwin/fhandler/netdrive.cc b/winsup/cygwin/fhandler/netdrive.cc index 1eff816..5328429 100644 --- a/winsup/cygwin/fhandler/netdrive.cc +++ b/winsup/cygwin/fhandler/netdrive.cc @@ -14,6 +14,7 @@ details. */ #include "dtable.h" #include "cygheap.h" #include "cygthread.h" +#include "tls_pbuf.h" #define USE_SYS_TYPES_FD_SET #include <shobjidl.h> @@ -28,8 +29,18 @@ details. */ /* SMBv1 is deprectated and not even installed by default anymore on Windows 10 and 11 machines or their servers. - So this fhandler class now uses Network Discovery, which, unfortunately, - requires to use the shell API. */ + As a result, neither WNetOpenEnumW() nor NetServerEnum() work as + expected anymore. + + So this fhandler class now uses Network Discovery to enumerate + the "//" directory, which, unfortunately, requires to use the + shell API. */ + +/* There's something REALLY fishy going on in Windows. If the NFS + enumeration via WNet functions is called *before* the share enumeration + via Shell function, the Shell functions will enumerate the NFS shares + instead of the SMB shares. Un-be-lie-va-ble! + FWIW, we reverted the SMB share enumeration using WNet. */ /* Define the required GUIDs here to avoid linking with libuuid.a */ const GUID FOLDERID_NetworkFolder = { @@ -60,7 +71,8 @@ public: free (entry[--num_entries]); free (entry); } - void add (wchar_t *str) + size_t count () const { return num_entries; } + void add (wchar_t *str, bool downcase = false) { if (num_entries >= max_entries) { @@ -76,9 +88,9 @@ public: entry[num_entries] = wcsdup (str); if (entry[num_entries]) { - wchar_t *p = entry[num_entries]; - while ((*p = towlower (*p))) - ++p; + if (downcase) + for (wchar_t *p = entry[num_entries]; (*p = towlower (*p)); ++p) + ; ++num_entries; } } @@ -96,6 +108,7 @@ struct netdriveinf { DIR *dir; int err; + DWORD provider; HANDLE sem; }; @@ -110,8 +123,55 @@ hresult_to_errno (HRESULT wres) return EACCES; } +/* Workaround incompatible definitions. */ +#define u_long __ms_u_long +#define WS_FIONBIO _IOW('f', 126, u_long) +#define WS_POLLOUT 0x10 + +static bool +server_is_running_nfs (const wchar_t *servername) +{ + /* Hack alarm: Only test TCP port 2049 */ + struct addrinfoW hints = { 0 }; + struct addrinfoW *ai = NULL, *aip; + bool ret = false; + INT wres; + + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + /* The services contains "nfs" only as UDP service... sigh. */ + wres = GetAddrInfoW (servername, L"2049", &hints, &ai); + if (wres) + return false; + for (aip = ai; !ret && aip; aip = aip->ai_next) + { + SOCKET sock = ::socket (aip->ai_family, aip->ai_socktype, + aip->ai_flags); + if (sock != INVALID_SOCKET) + { + __ms_u_long nonblocking = 1; + ::ioctlsocket (sock, WS_FIONBIO, &nonblocking); + wres = ::connect (sock, aip->ai_addr, aip->ai_addrlen); + if (wres == 0) + ret = true; + else if (WSAGetLastError () == WSAEWOULDBLOCK) + { + WSAPOLLFD fds = { .fd = sock, + .events = WS_POLLOUT }; + wres = WSAPoll (&fds, 1, 1500L); + if (wres > 0 && fds.revents == WS_POLLOUT) + ret = true; + } + ::closesocket (sock); + } + } + FreeAddrInfoW (ai); + return ret; +} + +/* Use only to enumerate the Network top level. */ static DWORD -thread_netdrive (void *arg) +thread_netdrive_wsd (void *arg) { netdriveinf *ndi = (netdriveinf *) arg; DIR *dir = ndi->dir; @@ -121,7 +181,6 @@ thread_netdrive (void *arg) ReleaseSemaphore (ndi->sem, 1, NULL); - size_t len = strlen (dir->__d_dirname); wres = CoInitialize (NULL); if (FAILED (wres)) { @@ -129,36 +188,15 @@ thread_netdrive (void *arg) goto out; } - if (len == 2) /* // */ - { - wres = SHGetKnownFolderItem (FOLDERID_NetworkFolder, KF_FLAG_DEFAULT, - NULL, IID_PPV_ARGS (&netparent)); - if (FAILED (wres)) - { - ndi->err = hresult_to_errno (wres); - goto out; - } - - } - else + wres = SHGetKnownFolderItem (FOLDERID_NetworkFolder, KF_FLAG_DEFAULT, + NULL, IID_PPV_ARGS (&netparent)); + if (FAILED (wres)) { - wchar_t name[CYG_MAX_PATH]; - - sys_mbstowcs (name, CYG_MAX_PATH, dir->__d_dirname); - name[0] = L'\\'; - name[1] = L'\\'; - wres = SHCreateItemFromParsingName (name, NULL, - IID_PPV_ARGS (&netparent)); - if (FAILED (wres)) - { - ndi->err = hresult_to_errno (wres); - goto out; - } - + ndi->err = hresult_to_errno (wres); + goto out; } - wres = netparent->BindToHandler (NULL, len == 2 ? BHID_StorageEnum - : BHID_EnumItems, + wres = netparent->BindToHandler (NULL, BHID_StorageEnum, IID_PPV_ARGS (&netitem_enum)); if (FAILED (wres)) { @@ -167,31 +205,26 @@ thread_netdrive (void *arg) goto out; } - if (len == 2) - { - netitem_enum->Reset (); - /* Don't look at me! - - Network discovery is very unreliable and the list of machines - returned is just fly-by-night, if the enumerator doesn't have - enough time. The fact that you see *most* (but not necessarily - *all*) machines on the network in Windows Explorer is a result of - the enumeration running in a loop. You can observe this when - rebooting a remote machine and it disappears and reappears in the - Explorer Network list. - - However, this is no option for the command line. We need to be able - to enumerate in a single go, since we can't just linger during - readdir() and reset the enumeration multiple times until we have a - supposedly full list. - - This makes the following Sleep necessary. Sleeping ~3secs after - Reset fills the enumeration with high probability with almost all - available machines. */ - Sleep (3000L); - } + netitem_enum->Reset (); + /* Don't look at me! - dir->__handle = (char *) new dir_cache (); + Network discovery is very unreliable and the list of machines + returned is just fly-by-night, if the enumerator doesn't have + enough time. The fact that you see *most* (but not necessarily + *all*) machines on the network in Windows Explorer is a result of + the enumeration running in a loop. You can observe this when + rebooting a remote machine and it disappears and reappears in the + Explorer Network list. + + However, this is no option for the command line. We need to be able + to enumerate in a single go, since we can't just linger during + readdir() and reset the enumeration multiple times until we have a + supposedly full list. + + This makes the following Sleep necessary. Sleeping ~3secs after + Reset fills the enumeration with high probability with almost all + available machines. */ + Sleep (3000L); do { @@ -207,7 +240,8 @@ thread_netdrive (void *arg) if (netitem[idx]->GetDisplayName (SIGDN_PARENTRELATIVEPARSING, &item_name) == S_OK) { - DIR_cache.add (item_name); + /* Skip "\\" on server names and downcase */ + DIR_cache.add (item_name + 2, true); CoTaskMemFree (item_name); } netitem[idx]->Release (); @@ -228,16 +262,148 @@ out: } static DWORD +thread_netdrive_wnet (void *arg) +{ + netdriveinf *ndi = (netdriveinf *) arg; + DIR *dir = ndi->dir; + DWORD wres; + + size_t entry_cache_size = DIR_cache.count (); + WCHAR provider[256], *dummy = NULL; + wchar_t srv_name[CYG_MAX_PATH]; + NETRESOURCEW nri = { 0 }; + LPNETRESOURCEW nro; + tmp_pathbuf tp; + HANDLE dom = NULL; + DWORD cnt, size; + + ReleaseSemaphore (ndi->sem, 1, NULL); + + wres = WNetGetProviderNameW (ndi->provider, provider, (size = 256, &size)); + if (wres != NO_ERROR) + { + ndi->err = geterrno_from_win_error (wres); + goto out; + } + + sys_mbstowcs (srv_name, CYG_MAX_PATH, dir->__d_dirname); + srv_name[0] = L'\\'; + srv_name[1] = L'\\'; + + if (ndi->provider == WNNC_NET_MS_NFS + && !server_is_running_nfs (srv_name + 2)) + { + ndi->err = ENOENT; + goto out; + } + + nri.lpRemoteName = srv_name; + nri.lpProvider = provider; + nri.dwType = RESOURCETYPE_DISK; + nro = (LPNETRESOURCEW) tp.c_get (); + wres = WNetGetResourceInformationW (&nri, nro, + (size = NT_MAX_PATH, &size), &dummy); + if (wres != NO_ERROR) + { + ndi->err = geterrno_from_win_error (wres); + goto out; + } + wres = WNetOpenEnumW (RESOURCE_GLOBALNET, RESOURCETYPE_DISK, + RESOURCEUSAGE_ALL, nro, &dom); + if (wres != NO_ERROR) + { + ndi->err = geterrno_from_win_error (wres); + goto out; + } + + while ((wres = WNetEnumResourceW (dom, (cnt = 1, &cnt), nro, + (size = NT_MAX_PATH, &size))) == NO_ERROR) + { + /* Skip server name and trailing backslash */ + wchar_t *name = nro->lpRemoteName + wcslen (srv_name) + 1; + size_t cache_idx; + + if (ndi->provider == WNNC_NET_MS_NFS) + { + wchar_t *nm = name; + /* Convert from "ANSI embedded in widechar" to multibyte and convert + back to widechar. */ + char mbname[wcslen (name) + 1]; + char *mb = mbname; + while ((*mb++ = *nm++)) + ; + sys_mbstowcs_alloc (&name, HEAP_NOTHEAP, mbname); + if (!name) + { + ndi->err = ENOMEM; + goto out; + } + /* NFS has deep links so convert embedded '\\' to '/' here */ + for (wchar_t *bs = name; (bs = wcschr (bs, L'\\')); *bs++ = L'/') + ; + } + /* If we already collected shares, drop duplicates. */ + for (cache_idx = 0; cache_idx < entry_cache_size; ++ cache_idx) + if (!wcscmp (name, DIR_cache[cache_idx])) // wcscasecmp? + break; + if (cache_idx >= entry_cache_size) + DIR_cache.add (name); + if (ndi->provider == WNNC_NET_MS_NFS) + free (name); + } +out: + if (dom) + WNetCloseEnum (dom); + ReleaseSemaphore (ndi->sem, 1, NULL); + return 0; +} + +static DWORD create_thread_and_wait (DIR *dir) { - netdriveinf ndi = { dir, 0, - CreateSemaphore (&sec_none_nih, 0, 2, NULL) }; + netdriveinf ndi = { dir, 0, 0, NULL }; + cygthread *thr; + + /* For the Network root, fetch WSD info. */ + if (strlen (dir->__d_dirname) == 2) + { + ndi.provider = WNNC_NET_LANMAN; + ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL); + thr = new cygthread (thread_netdrive_wsd, &ndi, "netdrive_wsd"); + if (thr->detach (ndi.sem)) + ndi.err = EINTR; + CloseHandle (ndi.sem); + goto out; + } + + /* For shares, use WNet functions. */ - cygthread *thr = new cygthread (thread_netdrive, &ndi, "netdrive"); + /* Try NFS first, if the name contains a dot (i. e., supposedly is a FQDN + as used in NFS server enumeration). */ + if (strchr (dir->__d_dirname, '.')) + { + ndi.provider = WNNC_NET_MS_NFS; + ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL); + thr = new cygthread (thread_netdrive_wnet, &ndi, "netdrive_nfs"); + if (thr->detach (ndi.sem)) + ndi.err = EINTR; + CloseHandle (ndi.sem); + + if (ndi.err == EINTR) + goto out; + + } + + /* Eventually, try SMB via WNet for share enumeration. */ + ndi.provider = WNNC_NET_LANMAN; + ndi.sem = CreateSemaphore (&sec_none_nih, 0, 2, NULL); + thr = new cygthread (thread_netdrive_wnet, &ndi, "netdrive_smb"); if (thr->detach (ndi.sem)) ndi.err = EINTR; CloseHandle (ndi.sem); - return ndi.err; + +out: + return DIR_cache.count() > 0 ? 0 : ndi.err; } virtual_ftype_t @@ -292,6 +458,7 @@ fhandler_netdrive::opendir (int fd) int ret; dir = fhandler_virtual::opendir (fd); + dir->__handle = (char *) new dir_cache (); if (dir && (ret = create_thread_and_wait (dir))) { free (dir->__d_dirname); @@ -315,19 +482,14 @@ fhandler_netdrive::readdir (DIR *dir, dirent *de) goto out; } + sys_wcstombs (de->d_name, sizeof de->d_name, DIR_cache[dir->__d_position]); if (strlen (dir->__d_dirname) == 2) - { - sys_wcstombs (de->d_name, sizeof de->d_name, - DIR_cache[dir->__d_position] + 2); - de->d_ino = hash_path_name (get_ino (), de->d_name); - } + de->d_ino = hash_path_name (get_ino (), de->d_name); else { char full[2 * CYG_MAX_PATH]; char *s; - sys_wcstombs (de->d_name, sizeof de->d_name, - DIR_cache[dir->__d_position]); s = stpcpy (full, dir->__d_dirname); *s++ = '/'; stpcpy (s, de->d_name); diff --git a/winsup/cygwin/release/3.6.0 b/winsup/cygwin/release/3.6.0 index b5bdcee..1e242ff 100644 --- a/winsup/cygwin/release/3.6.0 +++ b/winsup/cygwin/release/3.6.0 @@ -23,5 +23,10 @@ What changed: - Drop support for NT4 and Samba < 3.0.22. - Now that SMBv1 is ultimately deprecated and not installed by default - on latest Windows versions, enumerating network servers in // and shares - via //machine is now using Network Discovery just like Windows Explorer. + on latest Windows versions, use Network Discovery (i. e. WSD, "Web + Service Discovery") for enumerating network servers in //, just like + Windows Explorer. + +- If "server" is given as FQDN, and if "server" is an NFS server, + ls //server now also enumerates NFS shares. If "server" is given + as a flat name, only SMB shares are enumerated. |