Cygwin: //server: revert to using WNet and support NFS shares
Continue using WSD for enumerating //, but switch back to
WNet for enumerating shares.
Add trying to enumerate NFS shares for machine names given as FQDN.
Only downcase server names, keep case of shares intact.
Move downcasing into dir_cache class.
Add a comment to explain an extrem weirdness in Windows.
Fixes: 205190a80b
("Cygwin: // and //server: switch to Network Discovery")
Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
This commit is contained in:
parent
8d2c1b4ce6
commit
7db1c6fc4e
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
wres = SHGetKnownFolderItem (FOLDERID_NetworkFolder, KF_FLAG_DEFAULT,
|
||||
NULL, IID_PPV_ARGS (&netparent));
|
||||
if (FAILED (wres))
|
||||
{
|
||||
ndi->err = hresult_to_errno (wres);
|
||||
goto out;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
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!
|
||||
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.
|
||||
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.
|
||||
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);
|
||||
}
|
||||
|
||||
dir->__handle = (char *) new dir_cache ();
|
||||
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 ();
|
||||
|
@ -227,17 +261,149 @@ out:
|
|||
return 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
cygthread *thr = new cygthread (thread_netdrive, &ndi, "netdrive");
|
||||
/* 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. */
|
||||
|
||||
/* 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);
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue